{
  "openapi": "3.1.0",
  "info": {
    "title": "Sonora API",
    "version": "v1",
    "description": "Read and act on your Sonora workspace from your own systems. Every request carries an API key from Settings → API as a bearer token; the key names the workspace.\n\n`/v1` only grows: responses may gain fields, and enums and error codes may gain values. Ignore unknown fields, handle unknown values, and fall back on the HTTP status for an unknown code.\n\nA key carries scopes, and each endpoint names the one it needs:\n\n- `customers:read`: Read customers, their custom fields, account teams, health, eCSAT and enrichments\n- `contacts:read`: Read the people at your customers\n- `issues:read`: Read customer issues, their sources and quotes\n- `feature-requests:read`: Read customer feature requests, their sources and quotes\n- `meetings:read`: Read customer meetings, their attendees and summaries\n- `notes:read`: Read the notes your team shares on customers\n- `customers:write`: Create customers, change their fields (taking them over from CRM sync) and their account teams\n- `customers:archive`: Archive and unarchive customers, with their contacts\n- `notes:write`: Add shared notes to customers, and change or delete the notes written through the API\n- `contacts:write`: Mark people do not contact, so Sonora stops emailing them, and lift the mark\n- `issues:write`: Resolve and reopen issues no support ticket backs\n- `enrichments:manage`: Create, edit and delete enrichments, and run them on customers — runs spend your workspace's credits. An enrichment's prompt sees the whole customer, as in Sonora, so grant it as you would the permission\n- `custom-fields:manage`: Create, rename and delete customers' custom fields and add select options; deleting a field erases its values\n- `workflows:trigger`: Start API-triggered workflows\n- `workflows:read`: Read workflows, runs and contact roles"
  },
  "servers": [
    {
      "url": "https://api.usesonora.com/v1"
    }
  ],
  "security": [
    {
      "apiKey": []
    }
  ],
  "tags": [
    {
      "name": "Account",
      "description": "The key and the workspace it belongs to."
    },
    {
      "name": "Workflows",
      "description": "API-triggered workflows, and starting runs of them."
    },
    {
      "name": "Workflow runs",
      "description": "Runs and their progress, emails and engagement."
    },
    {
      "name": "Customers",
      "description": "Customer accounts, their custom fields and their account teams."
    },
    {
      "name": "Enrichments",
      "description": "The fields Sonora's AI fills in on customers, and what each one means."
    },
    {
      "name": "Contacts",
      "description": "The people at your customers."
    },
    {
      "name": "Issues",
      "description": "Problems customers raised, distilled from tickets, emails, calls and chats."
    },
    {
      "name": "Feature requests",
      "description": "Capabilities customers asked for, distilled from the same conversations."
    },
    {
      "name": "Meetings",
      "description": "Customer meetings: when, who attended, and what was said."
    },
    {
      "name": "Notes",
      "description": "The notes your team shares on a customer."
    }
  ],
  "paths": {
    "/contact-roles": {
      "get": {
        "operationId": "listContactRoles",
        "tags": [
          "Workflows"
        ],
        "summary": "List contact roles",
        "description": "The contact field a start call's `recipients.roles` resolve against, and the role slugs a call may send.\n\nRequires the `workflows:read` scope.",
        "x-sonora-scope": "workflows:read",
        "security": [
          {
            "apiKey": []
          },
          {
            "oauth": [
              "workflows:read"
            ]
          }
        ],
        "responses": {
          "200": {
            "description": "The role field and its roles.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ContactRoles"
                }
              }
            }
          },
          "401": {
            "description": "`unauthorized`: The `Authorization` header is missing, or the key is invalid, revoked or expired.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "`insufficient_scope`: The key or token lacks the scope this endpoint needs, and `details.required` names it; or an OAuth access token called an endpoint only an API key can.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "`rate_limited`: Too many requests for this key; retry after `Retry-After` seconds.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "`internal_error`: Something went wrong on Sonora's side. Retry, and quote `requestId` if it persists.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/contacts": {
      "get": {
        "operationId": "listContacts",
        "tags": [
          "Contacts"
        ],
        "summary": "List contacts",
        "description": "The people at your customers. Newest first, or — with `sort=updatedAt` — oldest change first, to keep another system in step with Sonora. People who have left their customer are included (`departedAt` set); archived contacts are left out unless `includeArchived=true`.\n\nRequires the `contacts:read` scope.",
        "x-sonora-scope": "contacts:read",
        "security": [
          {
            "apiKey": []
          },
          {
            "oauth": [
              "contacts:read"
            ]
          }
        ],
        "parameters": [
          {
            "name": "customerId",
            "in": "query",
            "required": false,
            "description": "Only the people at this customer (its Sonora id).",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "email",
            "in": "query",
            "required": false,
            "description": "The person's email address, matched without regard to case.",
            "schema": {
              "type": "string",
              "format": "email"
            }
          },
          {
            "name": "departed",
            "in": "query",
            "required": false,
            "description": "`true`: only people who have left their customer. `false`: only people still there. Omit for both.",
            "schema": {
              "type": "string",
              "enum": [
                "true",
                "false"
              ]
            }
          },
          {
            "name": "includeArchived",
            "in": "query",
            "required": false,
            "description": "Also list archived contacts. A sync should set it, so it sees a contact leave.",
            "schema": {
              "type": "string",
              "enum": [
                "true",
                "false"
              ]
            }
          },
          {
            "name": "createdAfter",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "format": "date-time"
            }
          },
          {
            "name": "updatedAfter",
            "in": "query",
            "required": false,
            "description": "Contacts changed at or after this instant. With `sort=updatedAt`, resume a sync from the last `updatedAt` you saw, a few minutes back.",
            "schema": {
              "type": "string",
              "format": "date-time"
            }
          },
          {
            "name": "sort",
            "in": "query",
            "required": false,
            "description": "`-createdAt`: newest first (the default). `updatedAt`: oldest change first, for syncing.",
            "schema": {
              "default": "-createdAt",
              "type": "string",
              "enum": [
                "-createdAt",
                "updatedAt"
              ]
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Page size, 1–100.",
            "schema": {
              "default": 25,
              "type": "integer",
              "minimum": 1,
              "maximum": 100
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "required": false,
            "description": "The previous page's `nextCursor`, unchanged. Omit for the first page.",
            "schema": {
              "type": "string",
              "maxLength": 200
            }
          },
          {
            "name": "include",
            "in": "query",
            "required": false,
            "description": "Comma-separated optional parts of the response: `totalCount`.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "A page of contacts.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ContactList"
                }
              }
            }
          },
          "400": {
            "description": "`invalid_request`: A parameter or body field is missing, malformed or unknown; `details.issues` names each.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "`unauthorized`: The `Authorization` header is missing, or the key is invalid, revoked or expired.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "`insufficient_scope`: The key or token lacks the scope this endpoint needs, and `details.required` names it; or an OAuth access token called an endpoint only an API key can.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "`rate_limited`: Too many requests for this key; retry after `Retry-After` seconds.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "`internal_error`: Something went wrong on Sonora's side. Retry, and quote `requestId` if it persists.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/contacts/{contactId}": {
      "get": {
        "operationId": "getContact",
        "tags": [
          "Contacts"
        ],
        "summary": "Get a contact",
        "description": "One contact, departed or archived or not.\n\nRequires the `contacts:read` scope.",
        "x-sonora-scope": "contacts:read",
        "security": [
          {
            "apiKey": []
          },
          {
            "oauth": [
              "contacts:read"
            ]
          }
        ],
        "parameters": [
          {
            "name": "contactId",
            "in": "path",
            "required": true,
            "description": "The contact's Sonora id.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The contact.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Contact"
                }
              }
            }
          },
          "401": {
            "description": "`unauthorized`: The `Authorization` header is missing, or the key is invalid, revoked or expired.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "`insufficient_scope`: The key or token lacks the scope this endpoint needs, and `details.required` names it; or an OAuth access token called an endpoint only an API key can.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "`contact_not_found`: No contact with that id.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "`rate_limited`: Too many requests for this key; retry after `Retry-After` seconds.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "`internal_error`: Something went wrong on Sonora's side. Retry, and quote `requestId` if it persists.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/contacts/{contactId}/do-not-contact": {
      "put": {
        "operationId": "markContactDoNotContact",
        "tags": [
          "Contacts"
        ],
        "summary": "Mark a contact do not contact",
        "description": "Mark the contact do not contact, as the person page's menu in Sonora does: no workflow or draft emails them again until someone lifts the mark. The mark is on their email address, so it holds however a workflow or draft comes to name them. Their emails and meetings still come in. Marking someone already marked changes nothing, and keeps who marked them and why.\n\nRequires the `contacts:write` scope. API keys only.",
        "x-sonora-scope": "contacts:write",
        "parameters": [
          {
            "name": "contactId",
            "in": "path",
            "required": true,
            "description": "The contact's Sonora id.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The contact, marked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Contact"
                }
              }
            }
          },
          "401": {
            "description": "`unauthorized`: The `Authorization` header is missing, or the key is invalid, revoked or expired.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "`insufficient_scope`: The key or token lacks the scope this endpoint needs, and `details.required` names it; or an OAuth access token called an endpoint only an API key can.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "`contact_not_found`: No contact with that id.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "`contact_has_no_email`: The contact has no email address, and a do-not-contact mark lives on the address.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "`rate_limited`: Too many requests for this key; retry after `Retry-After` seconds.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "`internal_error`: Something went wrong on Sonora's side. Retry, and quote `requestId` if it persists.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "delete": {
        "operationId": "clearContactDoNotContact",
        "tags": [
          "Contacts"
        ],
        "summary": "Clear a contact's do-not-contact mark",
        "description": "Lift the mark, so workflows and drafts include the contact again from their next send. An unsubscribe or a bounce on the same address stays in place. Clearing a mark that isn't there changes nothing.\n\nRequires the `contacts:write` scope. API keys only.",
        "x-sonora-scope": "contacts:write",
        "parameters": [
          {
            "name": "contactId",
            "in": "path",
            "required": true,
            "description": "The contact's Sonora id.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The contact, unmarked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Contact"
                }
              }
            }
          },
          "401": {
            "description": "`unauthorized`: The `Authorization` header is missing, or the key is invalid, revoked or expired.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "`insufficient_scope`: The key or token lacks the scope this endpoint needs, and `details.required` names it; or an OAuth access token called an endpoint only an API key can.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "`contact_not_found`: No contact with that id.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "`rate_limited`: Too many requests for this key; retry after `Retry-After` seconds.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "`internal_error`: Something went wrong on Sonora's side. Retry, and quote `requestId` if it persists.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/customer-fields": {
      "get": {
        "operationId": "listCustomerFields",
        "tags": [
          "Customers"
        ],
        "summary": "List custom fields",
        "description": "The custom fields a customer's `customFields` may carry: each key, its label and type, and a select field's options.\n\nRequires the `customers:read` scope.",
        "x-sonora-scope": "customers:read",
        "security": [
          {
            "apiKey": []
          },
          {
            "oauth": [
              "customers:read"
            ]
          }
        ],
        "responses": {
          "200": {
            "description": "Every visible custom field.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CustomerFieldList"
                }
              }
            }
          },
          "401": {
            "description": "`unauthorized`: The `Authorization` header is missing, or the key is invalid, revoked or expired.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "`insufficient_scope`: The key or token lacks the scope this endpoint needs, and `details.required` names it; or an OAuth access token called an endpoint only an API key can.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "`rate_limited`: Too many requests for this key; retry after `Retry-After` seconds.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "`internal_error`: Something went wrong on Sonora's side. Retry, and quote `requestId` if it persists.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "post": {
        "operationId": "createCustomerField",
        "tags": [
          "Customers"
        ],
        "summary": "Create a custom field",
        "description": "Add a custom field to every customer, as **Add column** on the customers page does. Its key is made from the label — `Renewal risk` becomes `renewal_risk`, or `renewal_risk_2` if an earlier field still owns that key — and the answer says which. Every customer starts without a value. A label another field already shows is `409 customer_field_exists`. Send an `Idempotency-Key` to make a retry safe.\n\nRequires the `custom-fields:manage` scope. API keys only.",
        "x-sonora-scope": "custom-fields:manage",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "1–255 printable characters. A retry with the same key and request answers with the first call's result instead of acting twice.",
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 255
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateCustomerFieldRequest"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The field, created.",
            "headers": {
              "Idempotent-Replayed": {
                "description": "`true` when this answers a retry with an earlier call's `Idempotency-Key`: the first call's body, replayed.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CustomerField"
                }
              }
            }
          },
          "400": {
            "description": "`invalid_json`: The request body is not valid JSON.\n\n`invalid_request`: A parameter or body field is missing, malformed or unknown; `details.issues` names each.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "`unauthorized`: The `Authorization` header is missing, or the key is invalid, revoked or expired.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "`insufficient_scope`: The key or token lacks the scope this endpoint needs, and `details.required` names it; or an OAuth access token called an endpoint only an API key can.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "`customer_field_exists`: A custom field already has that label; `details.key` names it when it's one the API shows.\n\n`idempotency_key_in_use`: A request with this `Idempotency-Key` is still being processed. Retry once it has finished.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "413": {
            "description": "`payload_too_large`: The request body is too large; `details.maxBytes` gives the limit.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "`idempotency_key_mismatch`: The `Idempotency-Key` was used before with a different request.\n\n`invalid_fields`: A field's value can't be saved, so nothing was; `details.issues` names each field and why.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "`rate_limited`: Too many requests for this key; retry after `Retry-After` seconds.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "`internal_error`: Something went wrong on Sonora's side. Retry, and quote `requestId` if it persists.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/customer-fields/{key}": {
      "patch": {
        "operationId": "updateCustomerField",
        "tags": [
          "Customers"
        ],
        "summary": "Update a custom field",
        "description": "Rename a custom field, or add options to a select field. The key and every customer's value stay as they are, so nothing reading the field by key notices a rename. A field's type can't change, and a select's options can be added and reordered but not removed: customers may hold them. A label another field already shows is `409 customer_field_exists`.\n\nRequires the `custom-fields:manage` scope. API keys only.",
        "x-sonora-scope": "custom-fields:manage",
        "parameters": [
          {
            "name": "key",
            "in": "path",
            "required": true,
            "description": "The field's key, as `GET /customer-fields` lists it.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UpdateCustomerFieldRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The field, as saved.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CustomerField"
                }
              }
            }
          },
          "400": {
            "description": "`invalid_json`: The request body is not valid JSON.\n\n`invalid_request`: A parameter or body field is missing, malformed or unknown; `details.issues` names each.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "`unauthorized`: The `Authorization` header is missing, or the key is invalid, revoked or expired.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "`insufficient_scope`: The key or token lacks the scope this endpoint needs, and `details.required` names it; or an OAuth access token called an endpoint only an API key can.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "`customer_field_not_found`: No custom field with that key, or not one Sonora shows: fields an import hid or marked sensitive are never found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "`customer_field_exists`: A custom field already has that label; `details.key` names it when it's one the API shows.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "413": {
            "description": "`payload_too_large`: The request body is too large; `details.maxBytes` gives the limit.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "`invalid_fields`: A field's value can't be saved, so nothing was; `details.issues` names each field and why.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "`rate_limited`: Too many requests for this key; retry after `Retry-After` seconds.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "`internal_error`: Something went wrong on Sonora's side. Retry, and quote `requestId` if it persists.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "delete": {
        "operationId": "deleteCustomerField",
        "tags": [
          "Customers"
        ],
        "summary": "Delete a custom field",
        "description": "Delete a custom field, as deleting its column in Sonora does: **every customer's value for it is erased**, and can't be brought back. Edits that took the field over from sync end with it. An import that maps the field may define it again on its next run.\n\nRequires the `custom-fields:manage` scope. API keys only.",
        "x-sonora-scope": "custom-fields:manage",
        "parameters": [
          {
            "name": "key",
            "in": "path",
            "required": true,
            "description": "The field's key, as `GET /customer-fields` lists it.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "204": {
            "description": "The field and its values are gone."
          },
          "401": {
            "description": "`unauthorized`: The `Authorization` header is missing, or the key is invalid, revoked or expired.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "`insufficient_scope`: The key or token lacks the scope this endpoint needs, and `details.required` names it; or an OAuth access token called an endpoint only an API key can.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "`customer_field_not_found`: No custom field with that key, or not one Sonora shows: fields an import hid or marked sensitive are never found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "`rate_limited`: Too many requests for this key; retry after `Retry-After` seconds.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "`internal_error`: Something went wrong on Sonora's side. Retry, and quote `requestId` if it persists.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/customers": {
      "get": {
        "operationId": "listCustomers",
        "tags": [
          "Customers"
        ],
        "summary": "List customers",
        "description": "Customers newest first, or — with `sort=updatedAt` — oldest change first, to keep another system in step with Sonora. Archived customers are left out unless `includeArchived=true`.\n\nRequires the `customers:read` scope.",
        "x-sonora-scope": "customers:read",
        "security": [
          {
            "apiKey": []
          },
          {
            "oauth": [
              "customers:read"
            ]
          }
        ],
        "parameters": [
          {
            "name": "search",
            "in": "query",
            "required": false,
            "description": "Matches the name, website or an email domain, as the customers page's search does.",
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 200
            }
          },
          {
            "name": "stage",
            "in": "query",
            "required": false,
            "description": "Comma-separated stages, matched exactly (ignoring case); a customer in any of them matches.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "tier",
            "in": "query",
            "required": false,
            "description": "Comma-separated tiers, matched exactly (ignoring case).",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "owner",
            "in": "query",
            "required": false,
            "description": "The owner's email address.",
            "schema": {
              "type": "string",
              "format": "email"
            }
          },
          {
            "name": "includeArchived",
            "in": "query",
            "required": false,
            "description": "Also list archived customers. A sync should set it, so it sees a customer leave.",
            "schema": {
              "type": "string",
              "enum": [
                "true",
                "false"
              ]
            }
          },
          {
            "name": "createdAfter",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "format": "date-time"
            }
          },
          {
            "name": "updatedAfter",
            "in": "query",
            "required": false,
            "description": "Customers changed at or after this instant. With `sort=updatedAt`, resume a sync from the last `updatedAt` you saw, a few minutes back.",
            "schema": {
              "type": "string",
              "format": "date-time"
            }
          },
          {
            "name": "sort",
            "in": "query",
            "required": false,
            "description": "`-createdAt`: newest first (the default). `updatedAt`: oldest change first, for syncing.",
            "schema": {
              "default": "-createdAt",
              "type": "string",
              "enum": [
                "-createdAt",
                "updatedAt"
              ]
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Page size, 1–100.",
            "schema": {
              "default": 25,
              "type": "integer",
              "minimum": 1,
              "maximum": 100
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "required": false,
            "description": "The previous page's `nextCursor`, unchanged. Omit for the first page.",
            "schema": {
              "type": "string",
              "maxLength": 200
            }
          },
          {
            "name": "include",
            "in": "query",
            "required": false,
            "description": "Comma-separated optional parts of the response: `totalCount`, `enrichments`.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "A page of customers.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CustomerList"
                }
              }
            }
          },
          "400": {
            "description": "`invalid_request`: A parameter or body field is missing, malformed or unknown; `details.issues` names each.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "`unauthorized`: The `Authorization` header is missing, or the key is invalid, revoked or expired.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "`insufficient_scope`: The key or token lacks the scope this endpoint needs, and `details.required` names it; or an OAuth access token called an endpoint only an API key can.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "`rate_limited`: Too many requests for this key; retry after `Retry-After` seconds.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "`internal_error`: Something went wrong on Sonora's side. Retry, and quote `requestId` if it persists.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "post": {
        "operationId": "createCustomer",
        "tags": [
          "Customers"
        ],
        "summary": "Create a customer",
        "description": "Add a customer by its domain, as **Add customer** in Sonora does. The domain becomes its website and email domain, so the calls, emails and tickets Sonora sees from that domain land on it. A domain another customer already has — archived or not — is `409 customer_exists`, naming that customer. Set the rest of its fields with `PATCH /customers/{customerId}`. Send an `Idempotency-Key` to make a retry safe.\n\nRequires the `customers:write` scope. API keys only.",
        "x-sonora-scope": "customers:write",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "1–255 printable characters. A retry with the same key and request answers with the first call's result instead of acting twice.",
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 255
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateCustomerRequest"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The customer, created.",
            "headers": {
              "ETag": {
                "description": "A fingerprint of the fields `PATCH` can change. Send it back as `If-Match` to update only if none of them has changed since.",
                "schema": {
                  "type": "string"
                }
              },
              "Idempotent-Replayed": {
                "description": "`true` when this answers a retry with an earlier call's `Idempotency-Key`: the first call's body, replayed.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CustomerDetail"
                }
              }
            }
          },
          "400": {
            "description": "`invalid_json`: The request body is not valid JSON.\n\n`invalid_request`: A parameter or body field is missing, malformed or unknown; `details.issues` names each.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "`unauthorized`: The `Authorization` header is missing, or the key is invalid, revoked or expired.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "`insufficient_scope`: The key or token lacks the scope this endpoint needs, and `details.required` names it; or an OAuth access token called an endpoint only an API key can.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "`customer_exists`: A customer already has that domain; `details.customerId` names it (archived or not).\n\n`idempotency_key_in_use`: A request with this `Idempotency-Key` is still being processed. Retry once it has finished.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "413": {
            "description": "`payload_too_large`: The request body is too large; `details.maxBytes` gives the limit.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "`idempotency_key_mismatch`: The `Idempotency-Key` was used before with a different request.\n\n`invalid_fields`: A field's value can't be saved, so nothing was; `details.issues` names each field and why.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "`rate_limited`: Too many requests for this key; retry after `Retry-After` seconds.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "`internal_error`: Something went wrong on Sonora's side. Retry, and quote `requestId` if it persists.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/customers/{customerId}": {
      "get": {
        "operationId": "getCustomer",
        "tags": [
          "Customers"
        ],
        "summary": "Get a customer",
        "description": "One customer, with its account team, health and eCSAT; add `?include=enrichments` for its enrichment values. Name it by its Sonora id, or by your own system's id with `idType`. A Sonora, Salesforce or HubSpot id finds a customer whether or not it is archived; a `domain` or `custom_field:<key>` value matches live customers only, since an archived account can share it with a live one.\n\nRequires the `customers:read` scope.",
        "x-sonora-scope": "customers:read",
        "security": [
          {
            "apiKey": []
          },
          {
            "oauth": [
              "customers:read"
            ]
          }
        ],
        "parameters": [
          {
            "name": "customerId",
            "in": "path",
            "required": true,
            "description": "The customer's Sonora id — or, with `idType`, its id in another system, or its domain.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "idType",
            "in": "query",
            "required": false,
            "description": "How `id` names the account: `sonora` (the default; Sonora's customer id), `salesforce`, `hubspot`, `domain`, or `custom_field:<key>`. So `GET /customers/001Ab…?idType=salesforce` finds a customer by its Salesforce Account id.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "include",
            "in": "query",
            "required": false,
            "description": "Comma-separated optional parts of the response: `enrichments`.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The customer.",
            "headers": {
              "ETag": {
                "description": "A fingerprint of the fields `PATCH` can change. Send it back as `If-Match` to update only if none of them has changed since.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CustomerDetail"
                }
              }
            }
          },
          "400": {
            "description": "`invalid_request`: A parameter or body field is missing, malformed or unknown; `details.issues` names each.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "`unauthorized`: The `Authorization` header is missing, or the key is invalid, revoked or expired.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "`insufficient_scope`: The key or token lacks the scope this endpoint needs, and `details.required` names it; or an OAuth access token called an endpoint only an API key can.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "`customer_not_found`: No customer matches the id (and `idType`) in the path.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "`rate_limited`: Too many requests for this key; retry after `Retry-After` seconds.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "`internal_error`: Something went wrong on Sonora's side. Retry, and quote `requestId` if it persists.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "patch": {
        "operationId": "updateCustomer",
        "tags": [
          "Customers"
        ],
        "summary": "Update a customer",
        "description": "Change a customer's fields, all or nothing. A field you change is taken over from your CRM exactly as an edit in Sonora is: its value sticks, and the CRM's later changes to it are held back until someone resumes sync on it in Sonora. A field you send unchanged is left alone, so sending a whole record back only takes over what differs. Send `If-Match` with the `ETag` you read to refuse the write if the customer changed in between. The key needs `customers:read` as well as `customers:write`, since the response is the customer as saved.\n\nRequires the `customers:write` scope. API keys only.",
        "x-sonora-scope": "customers:write",
        "parameters": [
          {
            "name": "customerId",
            "in": "path",
            "required": true,
            "description": "The customer's Sonora id — or, with `idType`, its id in another system, or its domain.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "idType",
            "in": "query",
            "required": false,
            "description": "How `id` names the account: `sonora` (the default; Sonora's customer id), `salesforce`, `hubspot`, `domain`, or `custom_field:<key>`. So `GET /customers/001Ab…?idType=salesforce` finds a customer by its Salesforce Account id.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "If-Match",
            "in": "header",
            "required": false,
            "description": "The `ETag` from your last read. If the resource has changed since, nothing is written and the call answers `412 precondition_failed`. Without it, the last write wins.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UpdateCustomerRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The customer, as saved.",
            "headers": {
              "ETag": {
                "description": "A fingerprint of the fields `PATCH` can change. Send it back as `If-Match` to update only if none of them has changed since.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CustomerDetail"
                }
              }
            }
          },
          "400": {
            "description": "`invalid_json`: The request body is not valid JSON.\n\n`invalid_request`: A parameter or body field is missing, malformed or unknown; `details.issues` names each.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "`unauthorized`: The `Authorization` header is missing, or the key is invalid, revoked or expired.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "`insufficient_scope`: The key or token lacks the scope this endpoint needs, and `details.required` names it; or an OAuth access token called an endpoint only an API key can.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "`customer_not_found`: No customer matches the id (and `idType`) in the path.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "`customer_archived`: The customer is archived; unarchive it before changing it.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "412": {
            "description": "`precondition_failed`: The resource changed since the `ETag` that `If-Match` names. Read it again, then retry.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "413": {
            "description": "`payload_too_large`: The request body is too large; `details.maxBytes` gives the limit.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "`invalid_fields`: A field's value can't be saved, so nothing was; `details.issues` names each field and why.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "`rate_limited`: Too many requests for this key; retry after `Retry-After` seconds.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "`internal_error`: Something went wrong on Sonora's side. Retry, and quote `requestId` if it persists.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/customers/{customerId}/account-team/{email}": {
      "put": {
        "operationId": "addAccountTeamMember",
        "tags": [
          "Customers"
        ],
        "summary": "Add an account team member",
        "description": "Put someone in your workspace on the customer's account team, or change their role there. Adding a member who is already on the team changes nothing. The owner is always on the team, so naming them is `409 account_team_owner`.\n\nRequires the `customers:write` scope. API keys only.",
        "x-sonora-scope": "customers:write",
        "parameters": [
          {
            "name": "customerId",
            "in": "path",
            "required": true,
            "description": "The customer's Sonora id — or, with `idType`, its id in another system, or its domain.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "email",
            "in": "path",
            "required": true,
            "description": "The person's email address, URL-encoded: `tess%40yourco.com`.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "idType",
            "in": "query",
            "required": false,
            "description": "How `id` names the account: `sonora` (the default; Sonora's customer id), `salesforce`, `hubspot`, `domain`, or `custom_field:<key>`. So `GET /customers/001Ab…?idType=salesforce` finds a customer by its Salesforce Account id.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/AddAccountTeamMemberRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The customer, with its account team.",
            "headers": {
              "ETag": {
                "description": "A fingerprint of the fields `PATCH` can change. Send it back as `If-Match` to update only if none of them has changed since.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CustomerDetail"
                }
              }
            }
          },
          "400": {
            "description": "`invalid_json`: The request body is not valid JSON.\n\n`invalid_request`: A parameter or body field is missing, malformed or unknown; `details.issues` names each.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "`unauthorized`: The `Authorization` header is missing, or the key is invalid, revoked or expired.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "`insufficient_scope`: The key or token lacks the scope this endpoint needs, and `details.required` names it; or an OAuth access token called an endpoint only an API key can.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "`customer_not_found`: No customer matches the id (and `idType`) in the path.\n\n`person_not_found`: No one in your workspace has the email the path names.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "`account_team_owner`: The person owns the customer, and the owner is always on its account team. Change the owner first.\n\n`customer_archived`: The customer is archived; unarchive it before changing it.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "413": {
            "description": "`payload_too_large`: The request body is too large; `details.maxBytes` gives the limit.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "`rate_limited`: Too many requests for this key; retry after `Retry-After` seconds.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "`internal_error`: Something went wrong on Sonora's side. Retry, and quote `requestId` if it persists.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "delete": {
        "operationId": "removeAccountTeamMember",
        "tags": [
          "Customers"
        ],
        "summary": "Remove an account team member",
        "description": "Take someone off the customer's account team. Removing someone who isn't on it changes nothing. A member your CRM syncs stays off: the next sync doesn't put them back.\n\nRequires the `customers:write` scope. API keys only.",
        "x-sonora-scope": "customers:write",
        "parameters": [
          {
            "name": "customerId",
            "in": "path",
            "required": true,
            "description": "The customer's Sonora id — or, with `idType`, its id in another system, or its domain.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "email",
            "in": "path",
            "required": true,
            "description": "The person's email address, URL-encoded: `tess%40yourco.com`.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "idType",
            "in": "query",
            "required": false,
            "description": "How `id` names the account: `sonora` (the default; Sonora's customer id), `salesforce`, `hubspot`, `domain`, or `custom_field:<key>`. So `GET /customers/001Ab…?idType=salesforce` finds a customer by its Salesforce Account id.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "204": {
            "description": "They're off the account team."
          },
          "400": {
            "description": "`invalid_request`: A parameter or body field is missing, malformed or unknown; `details.issues` names each.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "`unauthorized`: The `Authorization` header is missing, or the key is invalid, revoked or expired.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "`insufficient_scope`: The key or token lacks the scope this endpoint needs, and `details.required` names it; or an OAuth access token called an endpoint only an API key can.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "`customer_not_found`: No customer matches the id (and `idType`) in the path.\n\n`person_not_found`: No one in your workspace has the email the path names.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "`account_team_owner`: The person owns the customer, and the owner is always on its account team. Change the owner first.\n\n`customer_archived`: The customer is archived; unarchive it before changing it.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "`rate_limited`: Too many requests for this key; retry after `Retry-After` seconds.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "`internal_error`: Something went wrong on Sonora's side. Retry, and quote `requestId` if it persists.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/customers/{customerId}/archive": {
      "post": {
        "operationId": "archiveCustomer",
        "tags": [
          "Customers"
        ],
        "summary": "Archive a customer",
        "description": "Archive a customer, as archiving it in Sonora does: it leaves the customer lists, its contacts are archived with it, and the workflow actions waiting on it are dismissed. Your CRM's imports never restore a customer archived this way. Archiving an archived customer changes nothing.\n\nRequires the `customers:archive` scope. API keys only.",
        "x-sonora-scope": "customers:archive",
        "parameters": [
          {
            "name": "customerId",
            "in": "path",
            "required": true,
            "description": "The customer's Sonora id — or, with `idType`, its id in another system, or its domain.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "idType",
            "in": "query",
            "required": false,
            "description": "How `id` names the account: `sonora` (the default; Sonora's customer id), `salesforce`, `hubspot`, `domain`, or `custom_field:<key>`. So `GET /customers/001Ab…?idType=salesforce` finds a customer by its Salesforce Account id.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The customer, archived.",
            "headers": {
              "ETag": {
                "description": "A fingerprint of the fields `PATCH` can change. Send it back as `If-Match` to update only if none of them has changed since.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CustomerDetail"
                }
              }
            }
          },
          "400": {
            "description": "`invalid_request`: A parameter or body field is missing, malformed or unknown; `details.issues` names each.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "`unauthorized`: The `Authorization` header is missing, or the key is invalid, revoked or expired.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "`insufficient_scope`: The key or token lacks the scope this endpoint needs, and `details.required` names it; or an OAuth access token called an endpoint only an API key can.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "`customer_not_found`: No customer matches the id (and `idType`) in the path.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "`rate_limited`: Too many requests for this key; retry after `Retry-After` seconds.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "`internal_error`: Something went wrong on Sonora's side. Retry, and quote `requestId` if it persists.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/customers/{customerId}/contacts": {
      "get": {
        "operationId": "listCustomerContacts",
        "tags": [
          "Contacts"
        ],
        "summary": "List a customer's contacts",
        "description": "The people at one customer, named by its Sonora id or, with `idType`, your own system's id for it. Newest first, or — with `sort=updatedAt` — oldest change first, to keep another system in step with Sonora. People who have left their customer are included (`departedAt` set); archived contacts are left out unless `includeArchived=true`.\n\nRequires the `contacts:read` scope.",
        "x-sonora-scope": "contacts:read",
        "security": [
          {
            "apiKey": []
          },
          {
            "oauth": [
              "contacts:read"
            ]
          }
        ],
        "parameters": [
          {
            "name": "customerId",
            "in": "path",
            "required": true,
            "description": "The customer's Sonora id — or, with `idType`, its id in another system, or its domain.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "idType",
            "in": "query",
            "required": false,
            "description": "How `id` names the account: `sonora` (the default; Sonora's customer id), `salesforce`, `hubspot`, `domain`, or `custom_field:<key>`. So `GET /customers/001Ab…?idType=salesforce` finds a customer by its Salesforce Account id.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "email",
            "in": "query",
            "required": false,
            "description": "The person's email address, matched without regard to case.",
            "schema": {
              "type": "string",
              "format": "email"
            }
          },
          {
            "name": "departed",
            "in": "query",
            "required": false,
            "description": "`true`: only people who have left their customer. `false`: only people still there. Omit for both.",
            "schema": {
              "type": "string",
              "enum": [
                "true",
                "false"
              ]
            }
          },
          {
            "name": "includeArchived",
            "in": "query",
            "required": false,
            "description": "Also list archived contacts. A sync should set it, so it sees a contact leave.",
            "schema": {
              "type": "string",
              "enum": [
                "true",
                "false"
              ]
            }
          },
          {
            "name": "createdAfter",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "format": "date-time"
            }
          },
          {
            "name": "updatedAfter",
            "in": "query",
            "required": false,
            "description": "Contacts changed at or after this instant. With `sort=updatedAt`, resume a sync from the last `updatedAt` you saw, a few minutes back.",
            "schema": {
              "type": "string",
              "format": "date-time"
            }
          },
          {
            "name": "sort",
            "in": "query",
            "required": false,
            "description": "`-createdAt`: newest first (the default). `updatedAt`: oldest change first, for syncing.",
            "schema": {
              "default": "-createdAt",
              "type": "string",
              "enum": [
                "-createdAt",
                "updatedAt"
              ]
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Page size, 1–100.",
            "schema": {
              "default": 25,
              "type": "integer",
              "minimum": 1,
              "maximum": 100
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "required": false,
            "description": "The previous page's `nextCursor`, unchanged. Omit for the first page.",
            "schema": {
              "type": "string",
              "maxLength": 200
            }
          },
          {
            "name": "include",
            "in": "query",
            "required": false,
            "description": "Comma-separated optional parts of the response: `totalCount`.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "A page of contacts.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ContactList"
                }
              }
            }
          },
          "400": {
            "description": "`invalid_request`: A parameter or body field is missing, malformed or unknown; `details.issues` names each.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "`unauthorized`: The `Authorization` header is missing, or the key is invalid, revoked or expired.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "`insufficient_scope`: The key or token lacks the scope this endpoint needs, and `details.required` names it; or an OAuth access token called an endpoint only an API key can.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "`customer_not_found`: No customer matches the id (and `idType`) in the path.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "`rate_limited`: Too many requests for this key; retry after `Retry-After` seconds.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "`internal_error`: Something went wrong on Sonora's side. Retry, and quote `requestId` if it persists.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/customers/{customerId}/feature-requests": {
      "get": {
        "operationId": "listCustomerFeatureRequests",
        "tags": [
          "Feature requests"
        ],
        "summary": "List a customer's feature requests",
        "description": "One customer's feature requests, archived customer or not, with the filters and order of `GET /feature-requests`. Name the customer by its Sonora id, or by your own system's id with `idType`.\n\nRequires the `feature-requests:read` scope.",
        "x-sonora-scope": "feature-requests:read",
        "security": [
          {
            "apiKey": []
          },
          {
            "oauth": [
              "feature-requests:read"
            ]
          }
        ],
        "parameters": [
          {
            "name": "customerId",
            "in": "path",
            "required": true,
            "description": "The customer's Sonora id — or, with `idType`, its id in another system, or its domain.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "status",
            "in": "query",
            "required": false,
            "description": "Comma-separated statuses, matched without regard to case; feature requests in any of them match.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "priority",
            "in": "query",
            "required": false,
            "description": "Comma-separated priorities, matched without case.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "createdAfter",
            "in": "query",
            "required": false,
            "description": "Only feature requests Sonora recorded at or after this instant (`createdAt`).",
            "schema": {
              "type": "string",
              "format": "date-time"
            }
          },
          {
            "name": "updatedAfter",
            "in": "query",
            "required": false,
            "description": "Only feature requests changed at or after this instant. With `sort=updatedAt`, resume a sync from the last `updatedAt` you saw, a few minutes back.",
            "schema": {
              "type": "string",
              "format": "date-time"
            }
          },
          {
            "name": "sort",
            "in": "query",
            "required": false,
            "description": "`-createdAt`: newest first (the default). `updatedAt`: oldest change first, for syncing.",
            "schema": {
              "default": "-createdAt",
              "type": "string",
              "enum": [
                "-createdAt",
                "updatedAt"
              ]
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Page size, 1–100.",
            "schema": {
              "default": 25,
              "type": "integer",
              "minimum": 1,
              "maximum": 100
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "required": false,
            "description": "The previous page's `nextCursor`, unchanged. Omit for the first page.",
            "schema": {
              "type": "string",
              "maxLength": 200
            }
          },
          {
            "name": "include",
            "in": "query",
            "required": false,
            "description": "Comma-separated optional parts of the response: `totalCount`, `quotes`.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "idType",
            "in": "query",
            "required": false,
            "description": "How `id` names the account: `sonora` (the default; Sonora's customer id), `salesforce`, `hubspot`, `domain`, or `custom_field:<key>`. So `GET /customers/001Ab…?idType=salesforce` finds a customer by its Salesforce Account id.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "A page of the customer's feature requests.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/FeatureRequestList"
                }
              }
            }
          },
          "400": {
            "description": "`invalid_request`: A parameter or body field is missing, malformed or unknown; `details.issues` names each.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "`unauthorized`: The `Authorization` header is missing, or the key is invalid, revoked or expired.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "`insufficient_scope`: The key or token lacks the scope this endpoint needs, and `details.required` names it; or an OAuth access token called an endpoint only an API key can.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "`customer_not_found`: No customer matches the id (and `idType`) in the path.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "`rate_limited`: Too many requests for this key; retry after `Retry-After` seconds.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "`internal_error`: Something went wrong on Sonora's side. Retry, and quote `requestId` if it persists.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/customers/{customerId}/issues": {
      "get": {
        "operationId": "listCustomerIssues",
        "tags": [
          "Issues"
        ],
        "summary": "List a customer's issues",
        "description": "One customer's issues, archived customer or not, with the filters and order of `GET /issues`. Name the customer by its Sonora id, or by your own system's id with `idType`.\n\nRequires the `issues:read` scope.",
        "x-sonora-scope": "issues:read",
        "security": [
          {
            "apiKey": []
          },
          {
            "oauth": [
              "issues:read"
            ]
          }
        ],
        "parameters": [
          {
            "name": "customerId",
            "in": "path",
            "required": true,
            "description": "The customer's Sonora id — or, with `idType`, its id in another system, or its domain.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "status",
            "in": "query",
            "required": false,
            "description": "Comma-separated statuses, matched without regard to case; issues in any of them match.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "priority",
            "in": "query",
            "required": false,
            "description": "Comma-separated priorities, matched without case.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "createdAfter",
            "in": "query",
            "required": false,
            "description": "Only issues Sonora recorded at or after this instant (`createdAt`).",
            "schema": {
              "type": "string",
              "format": "date-time"
            }
          },
          {
            "name": "updatedAfter",
            "in": "query",
            "required": false,
            "description": "Only issues changed at or after this instant. With `sort=updatedAt`, resume a sync from the last `updatedAt` you saw, a few minutes back.",
            "schema": {
              "type": "string",
              "format": "date-time"
            }
          },
          {
            "name": "sort",
            "in": "query",
            "required": false,
            "description": "`-createdAt`: newest first (the default). `updatedAt`: oldest change first, for syncing.",
            "schema": {
              "default": "-createdAt",
              "type": "string",
              "enum": [
                "-createdAt",
                "updatedAt"
              ]
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Page size, 1–100.",
            "schema": {
              "default": 25,
              "type": "integer",
              "minimum": 1,
              "maximum": 100
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "required": false,
            "description": "The previous page's `nextCursor`, unchanged. Omit for the first page.",
            "schema": {
              "type": "string",
              "maxLength": 200
            }
          },
          {
            "name": "include",
            "in": "query",
            "required": false,
            "description": "Comma-separated optional parts of the response: `totalCount`, `quotes`.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "idType",
            "in": "query",
            "required": false,
            "description": "How `id` names the account: `sonora` (the default; Sonora's customer id), `salesforce`, `hubspot`, `domain`, or `custom_field:<key>`. So `GET /customers/001Ab…?idType=salesforce` finds a customer by its Salesforce Account id.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "A page of the customer's issues.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/IssueList"
                }
              }
            }
          },
          "400": {
            "description": "`invalid_request`: A parameter or body field is missing, malformed or unknown; `details.issues` names each.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "`unauthorized`: The `Authorization` header is missing, or the key is invalid, revoked or expired.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "`insufficient_scope`: The key or token lacks the scope this endpoint needs, and `details.required` names it; or an OAuth access token called an endpoint only an API key can.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "`customer_not_found`: No customer matches the id (and `idType`) in the path.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "`rate_limited`: Too many requests for this key; retry after `Retry-After` seconds.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "`internal_error`: Something went wrong on Sonora's side. Retry, and quote `requestId` if it persists.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/customers/{customerId}/meetings": {
      "get": {
        "operationId": "listCustomerMeetings",
        "tags": [
          "Meetings"
        ],
        "summary": "List a customer's meetings",
        "description": "One customer's meetings, most recent first. Name the customer by its Sonora id, or by your own system's id with `idType`.\n\nRequires the `meetings:read` scope.",
        "x-sonora-scope": "meetings:read",
        "security": [
          {
            "apiKey": []
          },
          {
            "oauth": [
              "meetings:read"
            ]
          }
        ],
        "parameters": [
          {
            "name": "customerId",
            "in": "path",
            "required": true,
            "description": "The customer's Sonora id — or, with `idType`, its id in another system, or its domain.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "idType",
            "in": "query",
            "required": false,
            "description": "How `id` names the account: `sonora` (the default; Sonora's customer id), `salesforce`, `hubspot`, `domain`, or `custom_field:<key>`.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "startAfter",
            "in": "query",
            "required": false,
            "description": "Meetings starting at or after this instant. A meeting with no start time goes by when Sonora recorded it.",
            "schema": {
              "type": "string",
              "format": "date-time"
            }
          },
          {
            "name": "startBefore",
            "in": "query",
            "required": false,
            "description": "Meetings starting before this instant.",
            "schema": {
              "type": "string",
              "format": "date-time"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Page size, 1–100.",
            "schema": {
              "default": 25,
              "type": "integer",
              "minimum": 1,
              "maximum": 100
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "required": false,
            "description": "The previous page's `nextCursor`, unchanged. Omit for the first page.",
            "schema": {
              "type": "string",
              "maxLength": 200
            }
          },
          {
            "name": "include",
            "in": "query",
            "required": false,
            "description": "Comma-separated optional parts of the response: `totalCount`.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "A page of meetings.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/MeetingList"
                }
              }
            }
          },
          "400": {
            "description": "`invalid_request`: A parameter or body field is missing, malformed or unknown; `details.issues` names each.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "`unauthorized`: The `Authorization` header is missing, or the key is invalid, revoked or expired.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "`insufficient_scope`: The key or token lacks the scope this endpoint needs, and `details.required` names it; or an OAuth access token called an endpoint only an API key can.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "`customer_not_found`: No customer matches the id (and `idType`) in the path.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "`rate_limited`: Too many requests for this key; retry after `Retry-After` seconds.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "`internal_error`: Something went wrong on Sonora's side. Retry, and quote `requestId` if it persists.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/customers/{customerId}/notes": {
      "get": {
        "operationId": "listCustomerNotes",
        "tags": [
          "Notes"
        ],
        "summary": "List a customer's notes",
        "description": "The notes your team shares on one customer, newest first. Private notes are left out. Name the customer by its Sonora id, or by your own system's id with `idType`.\n\nRequires the `notes:read` scope.",
        "x-sonora-scope": "notes:read",
        "security": [
          {
            "apiKey": []
          },
          {
            "oauth": [
              "notes:read"
            ]
          }
        ],
        "parameters": [
          {
            "name": "customerId",
            "in": "path",
            "required": true,
            "description": "The customer's Sonora id — or, with `idType`, its id in another system, or its domain.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "idType",
            "in": "query",
            "required": false,
            "description": "How `id` names the account: `sonora` (the default; Sonora's customer id), `salesforce`, `hubspot`, `domain`, or `custom_field:<key>`.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Page size, 1–100.",
            "schema": {
              "default": 25,
              "type": "integer",
              "minimum": 1,
              "maximum": 100
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "required": false,
            "description": "The previous page's `nextCursor`, unchanged. Omit for the first page.",
            "schema": {
              "type": "string",
              "maxLength": 200
            }
          },
          {
            "name": "include",
            "in": "query",
            "required": false,
            "description": "Comma-separated optional parts of the response: `totalCount`.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "A page of notes.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/NoteList"
                }
              }
            }
          },
          "400": {
            "description": "`invalid_request`: A parameter or body field is missing, malformed or unknown; `details.issues` names each.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "`unauthorized`: The `Authorization` header is missing, or the key is invalid, revoked or expired.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "`insufficient_scope`: The key or token lacks the scope this endpoint needs, and `details.required` names it; or an OAuth access token called an endpoint only an API key can.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "`customer_not_found`: No customer matches the id (and `idType`) in the path.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "`rate_limited`: Too many requests for this key; retry after `Retry-After` seconds.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "`internal_error`: Something went wrong on Sonora's side. Retry, and quote `requestId` if it persists.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "post": {
        "operationId": "createCustomerNote",
        "tags": [
          "Notes"
        ],
        "summary": "Add a note to a customer",
        "description": "Write a shared note on a customer, from Markdown. Everyone in your workspace who can read notes sees it on the customer, with the key named as its author. Send an `Idempotency-Key` to make a retry safe.\n\nRequires the `notes:write` scope. API keys only.",
        "x-sonora-scope": "notes:write",
        "parameters": [
          {
            "name": "customerId",
            "in": "path",
            "required": true,
            "description": "The customer's Sonora id — or, with `idType`, its id in another system, or its domain.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "idType",
            "in": "query",
            "required": false,
            "description": "How `id` names the account: `sonora` (the default; Sonora's customer id), `salesforce`, `hubspot`, `domain`, or `custom_field:<key>`. So `GET /customers/001Ab…?idType=salesforce` finds a customer by its Salesforce Account id.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "1–255 printable characters. A retry with the same key and request answers with the first call's result instead of acting twice.",
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 255
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateNoteRequest"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The note, created.",
            "headers": {
              "ETag": {
                "description": "A fingerprint of the note's title and body. Send it back as `If-Match` to update only if neither has changed since.",
                "schema": {
                  "type": "string"
                }
              },
              "Idempotent-Replayed": {
                "description": "`true` when this answers a retry with an earlier call's `Idempotency-Key`: the first call's body, replayed.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Note"
                }
              }
            }
          },
          "400": {
            "description": "`invalid_json`: The request body is not valid JSON.\n\n`invalid_request`: A parameter or body field is missing, malformed or unknown; `details.issues` names each.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "`unauthorized`: The `Authorization` header is missing, or the key is invalid, revoked or expired.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "`insufficient_scope`: The key or token lacks the scope this endpoint needs, and `details.required` names it; or an OAuth access token called an endpoint only an API key can.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "`customer_not_found`: No customer matches the id (and `idType`) in the path.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "`idempotency_key_in_use`: A request with this `Idempotency-Key` is still being processed. Retry once it has finished.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "413": {
            "description": "`payload_too_large`: The request body is too large; `details.maxBytes` gives the limit.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "`idempotency_key_mismatch`: The `Idempotency-Key` was used before with a different request.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "`rate_limited`: Too many requests for this key; retry after `Retry-After` seconds.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "`internal_error`: Something went wrong on Sonora's side. Retry, and quote `requestId` if it persists.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/customers/{customerId}/unarchive": {
      "post": {
        "operationId": "unarchiveCustomer",
        "tags": [
          "Customers"
        ],
        "summary": "Unarchive a customer",
        "description": "Restore an archived customer, however it was archived: it returns to the lists, with the contacts and workflow actions its archive took down. Unarchiving a live customer changes nothing.\n\nRequires the `customers:archive` scope. API keys only.",
        "x-sonora-scope": "customers:archive",
        "parameters": [
          {
            "name": "customerId",
            "in": "path",
            "required": true,
            "description": "The customer's Sonora id — or, with `idType`, its id in another system, or its domain.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "idType",
            "in": "query",
            "required": false,
            "description": "How `id` names the account: `sonora` (the default; Sonora's customer id), `salesforce`, `hubspot`, `domain`, or `custom_field:<key>`. So `GET /customers/001Ab…?idType=salesforce` finds a customer by its Salesforce Account id.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The customer, restored.",
            "headers": {
              "ETag": {
                "description": "A fingerprint of the fields `PATCH` can change. Send it back as `If-Match` to update only if none of them has changed since.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CustomerDetail"
                }
              }
            }
          },
          "400": {
            "description": "`invalid_request`: A parameter or body field is missing, malformed or unknown; `details.issues` names each.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "`unauthorized`: The `Authorization` header is missing, or the key is invalid, revoked or expired.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "`insufficient_scope`: The key or token lacks the scope this endpoint needs, and `details.required` names it; or an OAuth access token called an endpoint only an API key can.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "`customer_not_found`: No customer matches the id (and `idType`) in the path.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "`rate_limited`: Too many requests for this key; retry after `Retry-After` seconds.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "`internal_error`: Something went wrong on Sonora's side. Retry, and quote `requestId` if it persists.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/enrichment-configs": {
      "get": {
        "operationId": "listEnrichmentConfigs",
        "tags": [
          "Enrichments"
        ],
        "summary": "List enrichments",
        "description": "The enrichments on customers, oldest first: what each is called, what it means and what its value looks like. `GET /customers?include=enrichments` carries their values. Enrichments a person kept private, and Sonora's own internal ones, are never listed.\n\nRequires the `customers:read` scope.",
        "x-sonora-scope": "customers:read",
        "security": [
          {
            "apiKey": []
          },
          {
            "oauth": [
              "customers:read"
            ]
          }
        ],
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Page size, 1–100.",
            "schema": {
              "default": 25,
              "type": "integer",
              "minimum": 1,
              "maximum": 100
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "required": false,
            "description": "The previous page's `nextCursor`, unchanged. Omit for the first page.",
            "schema": {
              "type": "string",
              "maxLength": 200
            }
          },
          {
            "name": "include",
            "in": "query",
            "required": false,
            "description": "Comma-separated optional parts of the response: `totalCount`.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "A page of enrichments.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/EnrichmentConfigList"
                }
              }
            }
          },
          "400": {
            "description": "`invalid_request`: A parameter or body field is missing, malformed or unknown; `details.issues` names each.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "`unauthorized`: The `Authorization` header is missing, or the key is invalid, revoked or expired.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "`insufficient_scope`: The key or token lacks the scope this endpoint needs, and `details.required` names it; or an OAuth access token called an endpoint only an API key can.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "`rate_limited`: Too many requests for this key; retry after `Retry-After` seconds.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "`internal_error`: Something went wrong on Sonora's side. Retry, and quote `requestId` if it persists.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "post": {
        "operationId": "createEnrichmentConfig",
        "tags": [
          "Enrichments"
        ],
        "summary": "Create an enrichment",
        "description": "Add an enrichment to your customers: a question the AI answers for each one, the shape of the answer, and what it may read. It is visible to everyone in the workspace, like one made in Sonora and shared. Creating it runs nothing and spends no credits — start a run with `POST /enrichment-configs/{configId}/runs`. Send an `Idempotency-Key` to make a retry safe.\n\nRequires the `enrichments:manage` scope. API keys only.",
        "x-sonora-scope": "enrichments:manage",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "1–255 printable characters. A retry with the same key and request answers with the first call's result instead of acting twice.",
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 255
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateEnrichmentConfigRequest"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The enrichment, created.",
            "headers": {
              "ETag": {
                "description": "A fingerprint of the enrichment's current version. Send it back as `If-Match` to update only if nobody has edited it since.",
                "schema": {
                  "type": "string"
                }
              },
              "Idempotent-Replayed": {
                "description": "`true` when this answers a retry with an earlier call's `Idempotency-Key`: the first call's body, replayed.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/EnrichmentConfig"
                }
              }
            }
          },
          "400": {
            "description": "`invalid_json`: The request body is not valid JSON.\n\n`invalid_request`: A parameter or body field is missing, malformed or unknown; `details.issues` names each.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "`unauthorized`: The `Authorization` header is missing, or the key is invalid, revoked or expired.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "`insufficient_scope`: The key or token lacks the scope this endpoint needs, and `details.required` names it; or an OAuth access token called an endpoint only an API key can.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "`idempotency_key_in_use`: A request with this `Idempotency-Key` is still being processed. Retry once it has finished.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "413": {
            "description": "`payload_too_large`: The request body is too large; `details.maxBytes` gives the limit.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "`idempotency_key_mismatch`: The `Idempotency-Key` was used before with a different request.\n\n`invalid_fields`: A field's value can't be saved, so nothing was; `details.issues` names each field and why.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "`rate_limited`: Too many requests for this key; retry after `Retry-After` seconds.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "`internal_error`: Something went wrong on Sonora's side. Retry, and quote `requestId` if it persists.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/enrichment-configs/{configId}": {
      "get": {
        "operationId": "getEnrichmentConfig",
        "tags": [
          "Enrichments"
        ],
        "summary": "Get an enrichment",
        "description": "One enrichment, by the `id` its values carry as `configId`.\n\nRequires the `customers:read` scope.",
        "x-sonora-scope": "customers:read",
        "security": [
          {
            "apiKey": []
          },
          {
            "oauth": [
              "customers:read"
            ]
          }
        ],
        "parameters": [
          {
            "name": "configId",
            "in": "path",
            "required": true,
            "description": "The enrichment's `id`.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The enrichment.",
            "headers": {
              "ETag": {
                "description": "A fingerprint of the enrichment's current version. Send it back as `If-Match` to update only if nobody has edited it since.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/EnrichmentConfig"
                }
              }
            }
          },
          "401": {
            "description": "`unauthorized`: The `Authorization` header is missing, or the key is invalid, revoked or expired.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "`insufficient_scope`: The key or token lacks the scope this endpoint needs, and `details.required` names it; or an OAuth access token called an endpoint only an API key can.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "`enrichment_config_not_found`: No enrichment with that id, or not one a key can read.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "`rate_limited`: Too many requests for this key; retry after `Retry-After` seconds.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "`internal_error`: Something went wrong on Sonora's side. Retry, and quote `requestId` if it persists.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "patch": {
        "operationId": "updateEnrichmentConfig",
        "tags": [
          "Enrichments"
        ],
        "summary": "Update an enrichment",
        "description": "Change an enrichment's settings, as an edit in Sonora does: the change is a new version, its `id` stays the same, and values already worked out keep the settings they were worked out under until the next run. An enrichment Sonora built from something other than a prompt only takes `title` and `description`. Send `If-Match` with the `ETag` you read to refuse the change if someone edited it in between.\n\nRequires the `enrichments:manage` scope. API keys only.",
        "x-sonora-scope": "enrichments:manage",
        "parameters": [
          {
            "name": "configId",
            "in": "path",
            "required": true,
            "description": "The enrichment's `id`.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "If-Match",
            "in": "header",
            "required": false,
            "description": "The `ETag` from your last read. If the resource has changed since, nothing is written and the call answers `412 precondition_failed`. Without it, the last write wins.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UpdateEnrichmentConfigRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The enrichment, as saved.",
            "headers": {
              "ETag": {
                "description": "A fingerprint of the enrichment's current version. Send it back as `If-Match` to update only if nobody has edited it since.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/EnrichmentConfig"
                }
              }
            }
          },
          "400": {
            "description": "`invalid_json`: The request body is not valid JSON.\n\n`invalid_request`: A parameter or body field is missing, malformed or unknown; `details.issues` names each.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "`unauthorized`: The `Authorization` header is missing, or the key is invalid, revoked or expired.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "`insufficient_scope`: The key or token lacks the scope this endpoint needs, and `details.required` names it; or an OAuth access token called an endpoint only an API key can.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "`enrichment_config_not_found`: No enrichment with that id, or not one a key can read.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "`enrichment_config_busy`: The enrichment was being edited at the same moment, so nothing changed. Retry the call.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "412": {
            "description": "`precondition_failed`: The resource changed since the `ETag` that `If-Match` names. Read it again, then retry.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "413": {
            "description": "`payload_too_large`: The request body is too large; `details.maxBytes` gives the limit.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "`invalid_fields`: A field's value can't be saved, so nothing was; `details.issues` names each field and why.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "`rate_limited`: Too many requests for this key; retry after `Retry-After` seconds.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "`internal_error`: Something went wrong on Sonora's side. Retry, and quote `requestId` if it persists.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "delete": {
        "operationId": "deleteEnrichmentConfig",
        "tags": [
          "Enrichments"
        ],
        "summary": "Delete an enrichment",
        "description": "Delete an enrichment, as deleting it in Sonora does: it leaves the customers page and the API, its schedule stops, and its values stop appearing. One that fills a field in your Salesforce export mapping is `409 enrichment_config_mapped` unless you send `confirm=true`, which removes the mapping too.\n\nRequires the `enrichments:manage` scope. API keys only.",
        "x-sonora-scope": "enrichments:manage",
        "parameters": [
          {
            "name": "configId",
            "in": "path",
            "required": true,
            "description": "The enrichment's `id`.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "confirm",
            "in": "query",
            "required": false,
            "description": "Delete it even though it fills a field in your Salesforce export mapping, removing that mapping too.",
            "schema": {
              "type": "string",
              "enum": [
                "true",
                "false"
              ]
            }
          }
        ],
        "responses": {
          "204": {
            "description": "The enrichment is deleted."
          },
          "400": {
            "description": "`invalid_request`: A parameter or body field is missing, malformed or unknown; `details.issues` names each.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "`unauthorized`: The `Authorization` header is missing, or the key is invalid, revoked or expired.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "`insufficient_scope`: The key or token lacks the scope this endpoint needs, and `details.required` names it; or an OAuth access token called an endpoint only an API key can.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "`enrichment_config_not_found`: No enrichment with that id, or not one a key can read.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "`enrichment_config_busy`: The enrichment was being edited at the same moment, so nothing changed. Retry the call.\n\n`enrichment_config_mapped`: The enrichment fills a field in your Salesforce export mapping. Send `confirm=true` to delete it and that mapping.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "`rate_limited`: Too many requests for this key; retry after `Retry-After` seconds.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "`internal_error`: Something went wrong on Sonora's side. Retry, and quote `requestId` if it persists.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/enrichment-configs/{configId}/runs": {
      "post": {
        "operationId": "runEnrichmentConfig",
        "tags": [
          "Enrichments"
        ],
        "summary": "Run an enrichment",
        "description": "Queue the enrichment to be worked out for some customers, or all of them, as running it in Sonora does. **Runs spend credits**: usually 1 per customer, or 4 when the enrichment reads `web_search`, and more for some enrichments made in Sonora, which read sources the API doesn't name. If your workspace's credits can't cover the run, nothing is queued and the call answers `402 credits_exhausted`. Values arrive on the customers as they are worked out — read them with `GET /customers?include=enrichments`. Send an `Idempotency-Key` so a retry of a run that answered doesn't run and charge twice; see [Run an enrichment](/enrichments/#run-an-enrichment) for a call that got no answer.\n\nRequires the `enrichments:manage` scope. API keys only.",
        "x-sonora-scope": "enrichments:manage",
        "parameters": [
          {
            "name": "configId",
            "in": "path",
            "required": true,
            "description": "The enrichment's `id`.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "1–255 printable characters. A retry with the same key and request answers with the first call's result instead of acting twice.",
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 255
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/RunEnrichmentConfigRequest"
              }
            }
          }
        },
        "responses": {
          "202": {
            "description": "The run is queued.",
            "headers": {
              "Idempotent-Replayed": {
                "description": "`true` when this answers a retry with an earlier call's `Idempotency-Key`: the first call's body, replayed.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/EnrichmentRun"
                }
              }
            }
          },
          "400": {
            "description": "`invalid_json`: The request body is not valid JSON.\n\n`invalid_request`: A parameter or body field is missing, malformed or unknown; `details.issues` names each.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "`unauthorized`: The `Authorization` header is missing, or the key is invalid, revoked or expired.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "402": {
            "description": "`credits_exhausted`: Your workspace's credits can't cover the run, so nothing was queued; `details.required` and `details.remaining` give the credits.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "`insufficient_scope`: The key or token lacks the scope this endpoint needs, and `details.required` names it; or an OAuth access token called an endpoint only an API key can.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "`enrichment_config_not_found`: No enrichment with that id, or not one a key can read.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "`idempotency_key_in_use`: A request with this `Idempotency-Key` is still being processed. Retry once it has finished.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "413": {
            "description": "`payload_too_large`: The request body is too large; `details.maxBytes` gives the limit.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "`idempotency_key_mismatch`: The `Idempotency-Key` was used before with a different request.\n\n`invalid_fields`: A field's value can't be saved, so nothing was; `details.issues` names each field and why.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "`rate_limited`: Too many requests for this key; retry after `Retry-After` seconds.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "`internal_error`: Something went wrong on Sonora's side. Retry, and quote `requestId` if it persists.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/feature-requests": {
      "get": {
        "operationId": "listFeatureRequests",
        "tags": [
          "Feature requests"
        ],
        "summary": "List feature requests",
        "description": "Feature requests across every customer, newest first, or — with `sort=updatedAt` — oldest change first, to keep another system in step. Requests of archived customers are left out unless `includeArchived=true`.\n\nRequires the `feature-requests:read` scope.",
        "x-sonora-scope": "feature-requests:read",
        "security": [
          {
            "apiKey": []
          },
          {
            "oauth": [
              "feature-requests:read"
            ]
          }
        ],
        "parameters": [
          {
            "name": "customerId",
            "in": "query",
            "required": false,
            "description": "Only this customer's feature requests, by Sonora id. To name the customer by your own system's id, use `GET /customers/{customerId}/…` with `idType`.",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "status",
            "in": "query",
            "required": false,
            "description": "Comma-separated statuses, matched without regard to case; feature requests in any of them match.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "priority",
            "in": "query",
            "required": false,
            "description": "Comma-separated priorities, matched without case.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "includeArchived",
            "in": "query",
            "required": false,
            "description": "Also list feature requests of archived customers. A sync should set it.",
            "schema": {
              "type": "string",
              "enum": [
                "true",
                "false"
              ]
            }
          },
          {
            "name": "createdAfter",
            "in": "query",
            "required": false,
            "description": "Only feature requests Sonora recorded at or after this instant (`createdAt`).",
            "schema": {
              "type": "string",
              "format": "date-time"
            }
          },
          {
            "name": "updatedAfter",
            "in": "query",
            "required": false,
            "description": "Only feature requests changed at or after this instant. With `sort=updatedAt`, resume a sync from the last `updatedAt` you saw, a few minutes back.",
            "schema": {
              "type": "string",
              "format": "date-time"
            }
          },
          {
            "name": "sort",
            "in": "query",
            "required": false,
            "description": "`-createdAt`: newest first (the default). `updatedAt`: oldest change first, for syncing.",
            "schema": {
              "default": "-createdAt",
              "type": "string",
              "enum": [
                "-createdAt",
                "updatedAt"
              ]
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Page size, 1–100.",
            "schema": {
              "default": 25,
              "type": "integer",
              "minimum": 1,
              "maximum": 100
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "required": false,
            "description": "The previous page's `nextCursor`, unchanged. Omit for the first page.",
            "schema": {
              "type": "string",
              "maxLength": 200
            }
          },
          {
            "name": "include",
            "in": "query",
            "required": false,
            "description": "Comma-separated optional parts of the response: `totalCount`, `quotes`.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "A page of feature requests.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/FeatureRequestList"
                }
              }
            }
          },
          "400": {
            "description": "`invalid_request`: A parameter or body field is missing, malformed or unknown; `details.issues` names each.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "`unauthorized`: The `Authorization` header is missing, or the key is invalid, revoked or expired.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "`insufficient_scope`: The key or token lacks the scope this endpoint needs, and `details.required` names it; or an OAuth access token called an endpoint only an API key can.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "`rate_limited`: Too many requests for this key; retry after `Retry-After` seconds.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "`internal_error`: Something went wrong on Sonora's side. Retry, and quote `requestId` if it persists.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/feature-requests/{featureRequestId}": {
      "get": {
        "operationId": "getFeatureRequest",
        "tags": [
          "Feature requests"
        ],
        "summary": "Get a feature request",
        "description": "One feature request with its sources, and its quotes with `include=quotes`.\n\nRequires the `feature-requests:read` scope.",
        "x-sonora-scope": "feature-requests:read",
        "security": [
          {
            "apiKey": []
          },
          {
            "oauth": [
              "feature-requests:read"
            ]
          }
        ],
        "parameters": [
          {
            "name": "featureRequestId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "include",
            "in": "query",
            "required": false,
            "description": "Comma-separated optional parts of the response: `quotes`.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The feature request.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/FeatureRequest"
                }
              }
            }
          },
          "400": {
            "description": "`invalid_request`: A parameter or body field is missing, malformed or unknown; `details.issues` names each.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "`unauthorized`: The `Authorization` header is missing, or the key is invalid, revoked or expired.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "`insufficient_scope`: The key or token lacks the scope this endpoint needs, and `details.required` names it; or an OAuth access token called an endpoint only an API key can.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "`feature_request_not_found`: No feature request with that id.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "`rate_limited`: Too many requests for this key; retry after `Retry-After` seconds.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "`internal_error`: Something went wrong on Sonora's side. Retry, and quote `requestId` if it persists.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/issues": {
      "get": {
        "operationId": "listIssues",
        "tags": [
          "Issues"
        ],
        "summary": "List issues",
        "description": "Issues across every customer, newest first, or — with `sort=updatedAt` — oldest change first, to keep another system in step. Issues of archived customers are left out unless `includeArchived=true`.\n\nRequires the `issues:read` scope.",
        "x-sonora-scope": "issues:read",
        "security": [
          {
            "apiKey": []
          },
          {
            "oauth": [
              "issues:read"
            ]
          }
        ],
        "parameters": [
          {
            "name": "customerId",
            "in": "query",
            "required": false,
            "description": "Only this customer's issues, by Sonora id. To name the customer by your own system's id, use `GET /customers/{customerId}/…` with `idType`.",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "status",
            "in": "query",
            "required": false,
            "description": "Comma-separated statuses, matched without regard to case; issues in any of them match.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "priority",
            "in": "query",
            "required": false,
            "description": "Comma-separated priorities, matched without case.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "includeArchived",
            "in": "query",
            "required": false,
            "description": "Also list issues of archived customers. A sync should set it.",
            "schema": {
              "type": "string",
              "enum": [
                "true",
                "false"
              ]
            }
          },
          {
            "name": "createdAfter",
            "in": "query",
            "required": false,
            "description": "Only issues Sonora recorded at or after this instant (`createdAt`).",
            "schema": {
              "type": "string",
              "format": "date-time"
            }
          },
          {
            "name": "updatedAfter",
            "in": "query",
            "required": false,
            "description": "Only issues changed at or after this instant. With `sort=updatedAt`, resume a sync from the last `updatedAt` you saw, a few minutes back.",
            "schema": {
              "type": "string",
              "format": "date-time"
            }
          },
          {
            "name": "sort",
            "in": "query",
            "required": false,
            "description": "`-createdAt`: newest first (the default). `updatedAt`: oldest change first, for syncing.",
            "schema": {
              "default": "-createdAt",
              "type": "string",
              "enum": [
                "-createdAt",
                "updatedAt"
              ]
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Page size, 1–100.",
            "schema": {
              "default": 25,
              "type": "integer",
              "minimum": 1,
              "maximum": 100
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "required": false,
            "description": "The previous page's `nextCursor`, unchanged. Omit for the first page.",
            "schema": {
              "type": "string",
              "maxLength": 200
            }
          },
          {
            "name": "include",
            "in": "query",
            "required": false,
            "description": "Comma-separated optional parts of the response: `totalCount`, `quotes`.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "A page of issues.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/IssueList"
                }
              }
            }
          },
          "400": {
            "description": "`invalid_request`: A parameter or body field is missing, malformed or unknown; `details.issues` names each.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "`unauthorized`: The `Authorization` header is missing, or the key is invalid, revoked or expired.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "`insufficient_scope`: The key or token lacks the scope this endpoint needs, and `details.required` names it; or an OAuth access token called an endpoint only an API key can.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "`rate_limited`: Too many requests for this key; retry after `Retry-After` seconds.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "`internal_error`: Something went wrong on Sonora's side. Retry, and quote `requestId` if it persists.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/issues/{issueId}": {
      "get": {
        "operationId": "getIssue",
        "tags": [
          "Issues"
        ],
        "summary": "Get an issue",
        "description": "One issue with its sources, and its quotes with `include=quotes`.\n\nRequires the `issues:read` scope.",
        "x-sonora-scope": "issues:read",
        "security": [
          {
            "apiKey": []
          },
          {
            "oauth": [
              "issues:read"
            ]
          }
        ],
        "parameters": [
          {
            "name": "issueId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "include",
            "in": "query",
            "required": false,
            "description": "Comma-separated optional parts of the response: `quotes`.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The issue.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Issue"
                }
              }
            }
          },
          "400": {
            "description": "`invalid_request`: A parameter or body field is missing, malformed or unknown; `details.issues` names each.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "`unauthorized`: The `Authorization` header is missing, or the key is invalid, revoked or expired.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "`insufficient_scope`: The key or token lacks the scope this endpoint needs, and `details.required` names it; or an OAuth access token called an endpoint only an API key can.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "`issue_not_found`: No issue with that id.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "`rate_limited`: Too many requests for this key; retry after `Retry-After` seconds.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "`internal_error`: Something went wrong on Sonora's side. Retry, and quote `requestId` if it persists.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "patch": {
        "operationId": "updateIssue",
        "tags": [
          "Issues"
        ],
        "summary": "Resolve or reopen an issue",
        "description": "Mark an issue resolved, or reopen it, as the issue panel in Sonora does. Only for an issue no support ticket backs: a ticket's own status sets its issue's on every sync, so changing it here is `409 status_managed_by_source`. Setting the status the issue already has changes nothing. A resolved issue still reopens on its own when a conversation raises it again.\n\nRequires the `issues:write` scope. API keys only.",
        "x-sonora-scope": "issues:write",
        "parameters": [
          {
            "name": "issueId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UpdateIssueRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The issue, with its new status.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Issue"
                }
              }
            }
          },
          "400": {
            "description": "`invalid_json`: The request body is not valid JSON.\n\n`invalid_request`: A parameter or body field is missing, malformed or unknown; `details.issues` names each.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "`unauthorized`: The `Authorization` header is missing, or the key is invalid, revoked or expired.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "`insufficient_scope`: The key or token lacks the scope this endpoint needs, and `details.required` names it; or an OAuth access token called an endpoint only an API key can.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "`issue_not_found`: No issue with that id.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "`status_managed_by_source`: A support ticket backs the issue, and the ticket's own status sets the issue's. Change it in the support tool.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "413": {
            "description": "`payload_too_large`: The request body is too large; `details.maxBytes` gives the limit.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "`rate_limited`: Too many requests for this key; retry after `Retry-After` seconds.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "`internal_error`: Something went wrong on Sonora's side. Retry, and quote `requestId` if it persists.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/me": {
      "get": {
        "operationId": "getMe",
        "tags": [
          "Account"
        ],
        "summary": "Check a key",
        "description": "The workspace the key belongs to and the key's own scopes. Any valid key may call it.\n\nAny valid API key may call this endpoint; an OAuth access token may not.",
        "x-sonora-scope": null,
        "responses": {
          "200": {
            "description": "The key works.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Me"
                }
              }
            }
          },
          "401": {
            "description": "`unauthorized`: The `Authorization` header is missing, or the key is invalid, revoked or expired.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "`insufficient_scope`: The key or token lacks the scope this endpoint needs, and `details.required` names it; or an OAuth access token called an endpoint only an API key can.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "`rate_limited`: Too many requests for this key; retry after `Retry-After` seconds.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "`internal_error`: Something went wrong on Sonora's side. Retry, and quote `requestId` if it persists.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/meetings": {
      "get": {
        "operationId": "listMeetings",
        "tags": [
          "Meetings"
        ],
        "summary": "List meetings",
        "description": "Meetings with your customers, most recent first. Personal and internal meetings — ones with no customer — are never listed, and neither are deleted or archived ones.\n\nRequires the `meetings:read` scope.",
        "x-sonora-scope": "meetings:read",
        "security": [
          {
            "apiKey": []
          },
          {
            "oauth": [
              "meetings:read"
            ]
          }
        ],
        "parameters": [
          {
            "name": "customerId",
            "in": "query",
            "required": false,
            "description": "Only this customer's meetings, by Sonora id. To name the customer by your own system's id, use `GET /customers/{customerId}/meetings`.",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "startAfter",
            "in": "query",
            "required": false,
            "description": "Meetings starting at or after this instant. A meeting with no start time goes by when Sonora recorded it.",
            "schema": {
              "type": "string",
              "format": "date-time"
            }
          },
          {
            "name": "startBefore",
            "in": "query",
            "required": false,
            "description": "Meetings starting before this instant.",
            "schema": {
              "type": "string",
              "format": "date-time"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Page size, 1–100.",
            "schema": {
              "default": 25,
              "type": "integer",
              "minimum": 1,
              "maximum": 100
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "required": false,
            "description": "The previous page's `nextCursor`, unchanged. Omit for the first page.",
            "schema": {
              "type": "string",
              "maxLength": 200
            }
          },
          {
            "name": "include",
            "in": "query",
            "required": false,
            "description": "Comma-separated optional parts of the response: `totalCount`.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "A page of meetings.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/MeetingList"
                }
              }
            }
          },
          "400": {
            "description": "`invalid_request`: A parameter or body field is missing, malformed or unknown; `details.issues` names each.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "`unauthorized`: The `Authorization` header is missing, or the key is invalid, revoked or expired.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "`insufficient_scope`: The key or token lacks the scope this endpoint needs, and `details.required` names it; or an OAuth access token called an endpoint only an API key can.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "`rate_limited`: Too many requests for this key; retry after `Retry-After` seconds.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "`internal_error`: Something went wrong on Sonora's side. Retry, and quote `requestId` if it persists.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/meetings/{meetingId}": {
      "get": {
        "operationId": "getMeeting",
        "tags": [
          "Meetings"
        ],
        "summary": "Get a meeting",
        "description": "One customer meeting, with its attendees and summary.\n\nRequires the `meetings:read` scope.",
        "x-sonora-scope": "meetings:read",
        "security": [
          {
            "apiKey": []
          },
          {
            "oauth": [
              "meetings:read"
            ]
          }
        ],
        "parameters": [
          {
            "name": "meetingId",
            "in": "path",
            "required": true,
            "description": "The meeting's Sonora id.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The meeting.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Meeting"
                }
              }
            }
          },
          "401": {
            "description": "`unauthorized`: The `Authorization` header is missing, or the key is invalid, revoked or expired.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "`insufficient_scope`: The key or token lacks the scope this endpoint needs, and `details.required` names it; or an OAuth access token called an endpoint only an API key can.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "`meeting_not_found`: No customer meeting with that id. Personal and internal meetings are never found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "`rate_limited`: Too many requests for this key; retry after `Retry-After` seconds.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "`internal_error`: Something went wrong on Sonora's side. Retry, and quote `requestId` if it persists.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/notes/{noteId}": {
      "get": {
        "operationId": "getNote",
        "tags": [
          "Notes"
        ],
        "summary": "Get a note",
        "description": "One shared note. A private note answers `404 note_not_found`, as a missing one does.\n\nRequires the `notes:read` scope.",
        "x-sonora-scope": "notes:read",
        "security": [
          {
            "apiKey": []
          },
          {
            "oauth": [
              "notes:read"
            ]
          }
        ],
        "parameters": [
          {
            "name": "noteId",
            "in": "path",
            "required": true,
            "description": "The note's Sonora id.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The note.",
            "headers": {
              "ETag": {
                "description": "A fingerprint of the note's title and body. Send it back as `If-Match` to update only if neither has changed since.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Note"
                }
              }
            }
          },
          "401": {
            "description": "`unauthorized`: The `Authorization` header is missing, or the key is invalid, revoked or expired.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "`insufficient_scope`: The key or token lacks the scope this endpoint needs, and `details.required` names it; or an OAuth access token called an endpoint only an API key can.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "`note_not_found`: No shared note with that id. Private notes are never found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "`rate_limited`: Too many requests for this key; retry after `Retry-After` seconds.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "`internal_error`: Something went wrong on Sonora's side. Retry, and quote `requestId` if it persists.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "patch": {
        "operationId": "updateNote",
        "tags": [
          "Notes"
        ],
        "summary": "Update a note",
        "description": "Change the title or body of a note written through the API — by any of your workspace's keys. A note someone wrote in Sonora is theirs to change: `403 note_not_editable`. Send `If-Match` with the `ETag` you read to refuse the write if the note changed in between; without it, the last write wins.\n\nRequires the `notes:write` scope. API keys only.",
        "x-sonora-scope": "notes:write",
        "parameters": [
          {
            "name": "noteId",
            "in": "path",
            "required": true,
            "description": "The note's Sonora id.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "If-Match",
            "in": "header",
            "required": false,
            "description": "The `ETag` from your last read. If the resource has changed since, nothing is written and the call answers `412 precondition_failed`. Without it, the last write wins.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UpdateNoteRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The note, as saved.",
            "headers": {
              "ETag": {
                "description": "A fingerprint of the note's title and body. Send it back as `If-Match` to update only if neither has changed since.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Note"
                }
              }
            }
          },
          "400": {
            "description": "`invalid_json`: The request body is not valid JSON.\n\n`invalid_request`: A parameter or body field is missing, malformed or unknown; `details.issues` names each.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "`unauthorized`: The `Authorization` header is missing, or the key is invalid, revoked or expired.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "`insufficient_scope`: The key or token lacks the scope this endpoint needs, and `details.required` names it; or an OAuth access token called an endpoint only an API key can.\n\n`note_not_editable`: The note was written by someone in Sonora. A key may change or delete only notes written through the API.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "`note_not_found`: No shared note with that id. Private notes are never found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "412": {
            "description": "`precondition_failed`: The resource changed since the `ETag` that `If-Match` names. Read it again, then retry.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "413": {
            "description": "`payload_too_large`: The request body is too large; `details.maxBytes` gives the limit.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "`rate_limited`: Too many requests for this key; retry after `Retry-After` seconds.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "`internal_error`: Something went wrong on Sonora's side. Retry, and quote `requestId` if it persists.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "delete": {
        "operationId": "deleteNote",
        "tags": [
          "Notes"
        ],
        "summary": "Delete a note",
        "description": "Delete a note written through the API, for good, as deleting it in Sonora does. A note someone wrote in Sonora is theirs to delete: `403 note_not_editable`.\n\nRequires the `notes:write` scope. API keys only.",
        "x-sonora-scope": "notes:write",
        "parameters": [
          {
            "name": "noteId",
            "in": "path",
            "required": true,
            "description": "The note's Sonora id.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "204": {
            "description": "The note is deleted."
          },
          "401": {
            "description": "`unauthorized`: The `Authorization` header is missing, or the key is invalid, revoked or expired.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "`insufficient_scope`: The key or token lacks the scope this endpoint needs, and `details.required` names it; or an OAuth access token called an endpoint only an API key can.\n\n`note_not_editable`: The note was written by someone in Sonora. A key may change or delete only notes written through the API.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "`note_not_found`: No shared note with that id. Private notes are never found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "`rate_limited`: Too many requests for this key; retry after `Retry-After` seconds.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "`internal_error`: Something went wrong on Sonora's side. Retry, and quote `requestId` if it persists.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/workflow-runs": {
      "get": {
        "operationId": "listWorkflowRuns",
        "tags": [
          "Workflow runs"
        ],
        "summary": "List runs",
        "description": "Root runs, newest first, filtered by workflow, account, person, origin, status and creation time. An unknown workflow or account is an empty page, not a 404, so a count of runs for it is 0.\n\nRequires the `workflows:read` scope.",
        "x-sonora-scope": "workflows:read",
        "security": [
          {
            "apiKey": []
          },
          {
            "oauth": [
              "workflows:read"
            ]
          }
        ],
        "parameters": [
          {
            "name": "workflowId",
            "in": "query",
            "required": false,
            "description": "A workflow's chain root id.",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "accountId",
            "in": "query",
            "required": false,
            "description": "Runs about this account.",
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 255
            }
          },
          {
            "name": "accountIdType",
            "in": "query",
            "required": false,
            "description": "How `accountId` names the account (as `idType` on a start call); needs `accountId`.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "contactId",
            "in": "query",
            "required": false,
            "description": "Runs that reached this contact.",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "contactEmail",
            "in": "query",
            "required": false,
            "description": "Runs that reached this address.",
            "schema": {
              "type": "string",
              "format": "email"
            }
          },
          {
            "name": "origin",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "auto",
                "manual",
                "api"
              ]
            }
          },
          {
            "name": "status",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "active",
                "completed",
                "failed",
                "cancelled"
              ]
            }
          },
          {
            "name": "createdAfter",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "format": "date-time"
            }
          },
          {
            "name": "createdBefore",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "format": "date-time"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Page size, 1–100.",
            "schema": {
              "default": 25,
              "type": "integer",
              "minimum": 1,
              "maximum": 100
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "required": false,
            "description": "The previous page's `nextCursor`, unchanged. Omit for the first page.",
            "schema": {
              "type": "string",
              "maxLength": 200
            }
          },
          {
            "name": "include",
            "in": "query",
            "required": false,
            "description": "Comma-separated optional parts of the response: `totalCount`.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "A page of runs.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WorkflowRunList"
                }
              }
            }
          },
          "400": {
            "description": "`invalid_request`: A parameter or body field is missing, malformed or unknown; `details.issues` names each.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "`unauthorized`: The `Authorization` header is missing, or the key is invalid, revoked or expired.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "`insufficient_scope`: The key or token lacks the scope this endpoint needs, and `details.required` names it; or an OAuth access token called an endpoint only an API key can.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "`rate_limited`: Too many requests for this key; retry after `Retry-After` seconds.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "`internal_error`: Something went wrong on Sonora's side. Retry, and quote `requestId` if it persists.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/workflow-runs/{runId}": {
      "get": {
        "operationId": "getWorkflowRun",
        "tags": [
          "Workflow runs"
        ],
        "summary": "Get a run",
        "description": "One run: its trigger, steps, what it is waiting on, and every email it produced with per-recipient engagement. A fan-out branch's id or a private workflow's run is 404.\n\nRequires the `workflows:read` scope.",
        "x-sonora-scope": "workflows:read",
        "security": [
          {
            "apiKey": []
          },
          {
            "oauth": [
              "workflows:read"
            ]
          }
        ],
        "parameters": [
          {
            "name": "runId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The run.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WorkflowRun"
                }
              }
            }
          },
          "401": {
            "description": "`unauthorized`: The `Authorization` header is missing, or the key is invalid, revoked or expired.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "`insufficient_scope`: The key or token lacks the scope this endpoint needs, and `details.required` names it; or an OAuth access token called an endpoint only an API key can.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "`run_not_found`: No workflow run with that id.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "`rate_limited`: Too many requests for this key; retry after `Retry-After` seconds.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "`internal_error`: Something went wrong on Sonora's side. Retry, and quote `requestId` if it persists.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/workflows": {
      "get": {
        "operationId": "listWorkflows",
        "tags": [
          "Workflows"
        ],
        "summary": "List API-triggered workflows",
        "description": "The workflows a caller can start through the API, newest edit first, with the variables each takes.\n\nRequires the `workflows:read` scope.",
        "x-sonora-scope": "workflows:read",
        "security": [
          {
            "apiKey": []
          },
          {
            "oauth": [
              "workflows:read"
            ]
          }
        ],
        "responses": {
          "200": {
            "description": "Every startable workflow.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WorkflowList"
                }
              }
            }
          },
          "401": {
            "description": "`unauthorized`: The `Authorization` header is missing, or the key is invalid, revoked or expired.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "`insufficient_scope`: The key or token lacks the scope this endpoint needs, and `details.required` names it; or an OAuth access token called an endpoint only an API key can.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "`rate_limited`: Too many requests for this key; retry after `Retry-After` seconds.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "`internal_error`: Something went wrong on Sonora's side. Retry, and quote `requestId` if it persists.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/workflows/{workflowId}/runs": {
      "post": {
        "operationId": "startWorkflowRun",
        "tags": [
          "Workflows"
        ],
        "summary": "Start a run",
        "description": "Start the workflow for one account, or — with a JSON array body — for up to 100 accounts at once (`207`, one result per element, each what that account's own call would have answered). The progress of a run is `GET /workflow-runs/{runId}`, under `workflows:read`.\n\nRequires the `workflows:trigger` scope. API keys only.",
        "x-sonora-scope": "workflows:trigger",
        "parameters": [
          {
            "name": "workflowId",
            "in": "path",
            "required": true,
            "description": "The workflow's chain root id (`GET /workflows`); a version id is 404.",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "1–255 printable characters. A retry with the same key and request answers with the first call's result instead of acting twice.",
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 255
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "anyOf": [
                  {
                    "$ref": "#/components/schemas/StartRunRequest"
                  },
                  {
                    "$ref": "#/components/schemas/StartRunBatchRequest"
                  }
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "An idempotent replay: the body the original call got.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WorkflowRunAcceptance"
                }
              }
            }
          },
          "202": {
            "description": "The run started.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WorkflowRunAcceptance"
                }
              }
            }
          },
          "207": {
            "description": "A batch: one result per element, in order.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/StartRunBatchResponse"
                }
              }
            }
          },
          "400": {
            "description": "`invalid_json`: The request body is not valid JSON.\n\n`invalid_request`: A parameter or body field is missing, malformed or unknown; `details.issues` names each.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "`unauthorized`: The `Authorization` header is missing, or the key is invalid, revoked or expired.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "`insufficient_scope`: The key or token lacks the scope this endpoint needs, and `details.required` names it; or an OAuth access token called an endpoint only an API key can.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "`account_not_found`: No account matches the id (and `idType`) the call named.\n\n`workflow_not_found`: No API-triggered workflow with that id.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "`recently_triggered`: Everyone the call named was reached by this workflow within its resend window.\n\n`run_active`: The account already has an active run of this workflow.\n\n`workflow_has_no_owner`: The workflow's author was removed; someone must take it over before it can send.\n\n`workflow_inactive`: The workflow is a draft; activate it first.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "413": {
            "description": "`payload_too_large`: The request body is too large; `details.maxBytes` gives the limit.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "`account_archived`: The account is archived.\n\n`idempotency_key_mismatch`: The `Idempotency-Key` was used before with a different request.\n\n`invalid_variable`: A variable's value has the wrong type.\n\n`missing_variable`: A required variable is missing.\n\n`no_recipients`: Nobody the call named can be emailed.\n\n`recipient_not_found`: An email or contact id the call named isn't a contact at the account.\n\n`recipients_not_accepted`: The workflow doesn't send to people a call names, so recipients would be ignored.\n\n`recipients_required`: The workflow sends to the people a call names, and the call named nobody.\n\n`role_field_not_configured`: The call named roles, but no contact role field is set on Settings → API.\n\n`too_many_recipients`: The call names more people than a run may reach.\n\n`unknown_role`: A role isn't one of the role field's slugs; `details.validRoles` lists them.\n\n`unknown_variable`: A variable isn't one the workflow declares.\n\n`workflow_not_api_triggered`: The workflow isn't started by the API.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "`rate_limited`: Too many requests for this key; retry after `Retry-After` seconds.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "`internal_error`: Something went wrong on Sonora's side. Retry, and quote `requestId` if it persists.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "apiKey": {
        "type": "http",
        "scheme": "bearer",
        "description": "An API key from Settings → API: `Authorization: Bearer sonora_sk_…`."
      },
      "oauth": {
        "type": "oauth2",
        "description": "An OAuth access token for the person signed in, read-only: `Authorization: Bearer sonora_at_…`. It reads the workspace they choose at consent, with the read scopes it was granted that their role in Sonora covers. Today Sonora's own API docs are the only client offered these scopes; their Try it panel signs in with it.",
        "flows": {
          "authorizationCode": {
            "authorizationUrl": "https://app.usesonora.com/oauth/authorize",
            "tokenUrl": "https://app.usesonora.com/oauth/token",
            "scopes": {
              "customers:read": "Read customers, their custom fields, account teams, health, eCSAT and enrichments",
              "contacts:read": "Read the people at your customers",
              "issues:read": "Read customer issues, their sources and quotes",
              "feature-requests:read": "Read customer feature requests, their sources and quotes",
              "meetings:read": "Read customer meetings, their attendees and summaries",
              "notes:read": "Read the notes your team shares on customers",
              "workflows:read": "Read workflows, runs and contact roles"
            }
          }
        }
      }
    },
    "schemas": {
      "Contact": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "customerId": {
            "description": "The customer the person works at. Null when Sonora hasn't matched them to one.",
            "type": [
              "string",
              "null"
            ]
          },
          "firstName": {
            "type": [
              "string",
              "null"
            ]
          },
          "lastName": {
            "type": [
              "string",
              "null"
            ]
          },
          "name": {
            "description": "First and last name together; null when Sonora has neither.",
            "type": [
              "string",
              "null"
            ]
          },
          "email": {
            "type": [
              "string",
              "null"
            ]
          },
          "phone": {
            "type": [
              "string",
              "null"
            ]
          },
          "title": {
            "description": "Job title.",
            "type": [
              "string",
              "null"
            ]
          },
          "linkedinUrl": {
            "type": [
              "string",
              "null"
            ]
          },
          "isChampion": {
            "type": "boolean",
            "description": "Marked as a champion at their account."
          },
          "departedAt": {
            "anyOf": [
              {
                "type": "string",
                "format": "date-time"
              },
              {
                "type": "null"
              }
            ],
            "description": "Set when the person has left the customer. Departed contacts are listed unless `departed=false`."
          },
          "archivedAt": {
            "anyOf": [
              {
                "type": "string",
                "format": "date-time"
              },
              {
                "type": "null"
              }
            ],
            "description": "Set when the contact is archived — almost always because its customer was. Archived contacts only list with `includeArchived=true`."
          },
          "doNotContact": {
            "type": "boolean",
            "description": "Someone on your team marked the person do-not-contact, so Sonora never emails them. The mark is on the address, so changing it doesn't move `updatedAt`."
          },
          "externalIds": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ExternalId"
            }
          },
          "engagementsCount": {
            "type": "integer",
            "description": "Calls, emails, meetings and tickets Sonora has seen the person in."
          },
          "lastEngagementAt": {
            "anyOf": [
              {
                "type": "string",
                "format": "date-time"
              },
              {
                "type": "null"
              }
            ]
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "updatedAt": {
            "type": "string",
            "format": "date-time",
            "description": "Moves on any change to the record, including Sonora's own bookkeeping (engagement counts)."
          },
          "links": {
            "type": "object",
            "properties": {
              "self": {
                "type": "string",
                "description": "This resource's canonical URL on the API host."
              }
            },
            "required": [
              "self"
            ]
          }
        },
        "required": [
          "id",
          "customerId",
          "firstName",
          "lastName",
          "name",
          "email",
          "phone",
          "title",
          "linkedinUrl",
          "isChampion",
          "departedAt",
          "archivedAt",
          "doNotContact",
          "externalIds",
          "engagementsCount",
          "lastEngagementAt",
          "createdAt",
          "updatedAt",
          "links"
        ],
        "description": "A person at one of your customers."
      },
      "ContactList": {
        "type": "object",
        "properties": {
          "data": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Contact"
            }
          },
          "totalCount": {
            "description": "Only with `?include=totalCount`: every match under the same filters.",
            "type": "integer"
          },
          "nextCursor": {
            "description": "Pass as `cursor` for the next page; null on the last page.",
            "type": [
              "string",
              "null"
            ]
          }
        },
        "required": [
          "data",
          "nextCursor"
        ]
      },
      "ContactRoles": {
        "type": "object",
        "properties": {
          "field": {
            "anyOf": [
              {
                "type": "object",
                "properties": {
                  "id": {
                    "type": "string"
                  },
                  "name": {
                    "type": "string"
                  }
                },
                "required": [
                  "id",
                  "name"
                ]
              },
              {
                "type": "null"
              }
            ],
            "description": "The contact field roles resolve against; null until an admin picks one."
          },
          "roles": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "value": {
                  "type": "string"
                },
                "slug": {
                  "type": "string",
                  "description": "What a call sends."
                }
              },
              "required": [
                "value",
                "slug"
              ]
            }
          }
        },
        "required": [
          "field",
          "roles"
        ],
        "description": "The contact role field and the role slugs a start call may send."
      },
      "Customer": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "websiteUrl": {
            "type": [
              "string",
              "null"
            ]
          },
          "emailDomains": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Domains whose email addresses belong to this customer."
          },
          "arr": {
            "description": "Annual recurring revenue.",
            "type": [
              "number",
              "null"
            ]
          },
          "stage": {
            "type": [
              "string",
              "null"
            ]
          },
          "tier": {
            "type": [
              "string",
              "null"
            ]
          },
          "owner": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/Person"
              },
              {
                "type": "null"
              }
            ]
          },
          "renewalDate": {
            "anyOf": [
              {
                "type": "string",
                "format": "date"
              },
              {
                "type": "null"
              }
            ]
          },
          "contractStartDate": {
            "anyOf": [
              {
                "type": "string",
                "format": "date"
              },
              {
                "type": "null"
              }
            ]
          },
          "contractLengthDays": {
            "anyOf": [
              {
                "type": "integer"
              },
              {
                "type": "null"
              }
            ]
          },
          "summary": {
            "description": "Sonora's running summary of the account.",
            "type": [
              "string",
              "null"
            ]
          },
          "customFields": {
            "type": "object",
            "additionalProperties": {
              "description": "As the field's `type` says (`GET /customer-fields`). A `user` field is a list of people: `[{ name, email }]`."
            },
            "description": "Your workspace's custom fields, by key — only fields visible in Sonora."
          },
          "externalIds": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ExternalId"
            }
          },
          "health": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/CustomerHealth"
              },
              {
                "type": "null"
              }
            ],
            "description": "Null when your workspace doesn't score health, or hasn't scored this customer yet."
          },
          "ecsat": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/Ecsat"
              },
              {
                "type": "null"
              }
            ],
            "description": "Null when none of the customer's touchpoints in the window were scored."
          },
          "enrichments": {
            "description": "Only with `?include=enrichments`: the latest value of each enrichment that has one, in `GET /enrichment-configs` order.",
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/EnrichmentValue"
            }
          },
          "engagementsCount": {
            "type": "integer",
            "description": "Calls, emails, meetings and tickets Sonora has seen."
          },
          "lastEngagementAt": {
            "anyOf": [
              {
                "type": "string",
                "format": "date-time"
              },
              {
                "type": "null"
              }
            ]
          },
          "archivedAt": {
            "anyOf": [
              {
                "type": "string",
                "format": "date-time"
              },
              {
                "type": "null"
              }
            ],
            "description": "Set when the customer is archived; archived customers only list with `includeArchived=true`."
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "updatedAt": {
            "type": "string",
            "format": "date-time",
            "description": "Moves on any change to the record, including Sonora's own bookkeeping (engagement counts), so a sync may see a customer again with no field you read changed."
          },
          "links": {
            "type": "object",
            "properties": {
              "self": {
                "type": "string",
                "description": "This resource's canonical URL on the API host."
              }
            },
            "required": [
              "self"
            ]
          }
        },
        "required": [
          "id",
          "name",
          "websiteUrl",
          "emailDomains",
          "arr",
          "stage",
          "tier",
          "owner",
          "renewalDate",
          "contractStartDate",
          "contractLengthDays",
          "summary",
          "customFields",
          "externalIds",
          "health",
          "ecsat",
          "engagementsCount",
          "lastEngagementAt",
          "archivedAt",
          "createdAt",
          "updatedAt",
          "links"
        ],
        "description": "A customer account."
      },
      "CustomerDetail": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "websiteUrl": {
            "type": [
              "string",
              "null"
            ]
          },
          "emailDomains": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Domains whose email addresses belong to this customer."
          },
          "arr": {
            "description": "Annual recurring revenue.",
            "type": [
              "number",
              "null"
            ]
          },
          "stage": {
            "type": [
              "string",
              "null"
            ]
          },
          "tier": {
            "type": [
              "string",
              "null"
            ]
          },
          "owner": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/Person"
              },
              {
                "type": "null"
              }
            ]
          },
          "renewalDate": {
            "anyOf": [
              {
                "type": "string",
                "format": "date"
              },
              {
                "type": "null"
              }
            ]
          },
          "contractStartDate": {
            "anyOf": [
              {
                "type": "string",
                "format": "date"
              },
              {
                "type": "null"
              }
            ]
          },
          "contractLengthDays": {
            "anyOf": [
              {
                "type": "integer"
              },
              {
                "type": "null"
              }
            ]
          },
          "summary": {
            "description": "Sonora's running summary of the account.",
            "type": [
              "string",
              "null"
            ]
          },
          "customFields": {
            "type": "object",
            "additionalProperties": {
              "description": "As the field's `type` says (`GET /customer-fields`). A `user` field is a list of people: `[{ name, email }]`."
            },
            "description": "Your workspace's custom fields, by key — only fields visible in Sonora."
          },
          "externalIds": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ExternalId"
            }
          },
          "health": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/CustomerHealth"
              },
              {
                "type": "null"
              }
            ],
            "description": "Null when your workspace doesn't score health, or hasn't scored this customer yet."
          },
          "ecsat": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/Ecsat"
              },
              {
                "type": "null"
              }
            ],
            "description": "Null when none of the customer's touchpoints in the window were scored."
          },
          "enrichments": {
            "description": "Only with `?include=enrichments`: the latest value of each enrichment that has one, in `GET /enrichment-configs` order.",
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/EnrichmentValue"
            }
          },
          "engagementsCount": {
            "type": "integer",
            "description": "Calls, emails, meetings and tickets Sonora has seen."
          },
          "lastEngagementAt": {
            "anyOf": [
              {
                "type": "string",
                "format": "date-time"
              },
              {
                "type": "null"
              }
            ]
          },
          "archivedAt": {
            "anyOf": [
              {
                "type": "string",
                "format": "date-time"
              },
              {
                "type": "null"
              }
            ],
            "description": "Set when the customer is archived; archived customers only list with `includeArchived=true`."
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "updatedAt": {
            "type": "string",
            "format": "date-time",
            "description": "Moves on any change to the record, including Sonora's own bookkeeping (engagement counts), so a sync may see a customer again with no field you read changed."
          },
          "links": {
            "type": "object",
            "properties": {
              "self": {
                "type": "string",
                "description": "This resource's canonical URL on the API host."
              }
            },
            "required": [
              "self"
            ]
          },
          "accountTeam": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "name": {
                  "type": [
                    "string",
                    "null"
                  ]
                },
                "email": {
                  "description": "Null for a name typed in by hand or synced without a user.",
                  "type": [
                    "string",
                    "null"
                  ]
                },
                "role": {
                  "type": [
                    "string",
                    "null"
                  ]
                }
              },
              "required": [
                "name",
                "email",
                "role"
              ]
            },
            "description": "The account team beyond the owner, with each member's role where the CRM gives one."
          },
          "overriddenFields": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Fields an edit — in Sonora or through this API — has taken over from sync: your CRM's changes to them are held back until someone resumes sync on the field in Sonora. Standard fields by name (`arr`, `owner`), custom fields as `customFields.<key>`."
          }
        },
        "required": [
          "id",
          "name",
          "websiteUrl",
          "emailDomains",
          "arr",
          "stage",
          "tier",
          "owner",
          "renewalDate",
          "contractStartDate",
          "contractLengthDays",
          "summary",
          "customFields",
          "externalIds",
          "health",
          "ecsat",
          "engagementsCount",
          "lastEngagementAt",
          "archivedAt",
          "createdAt",
          "updatedAt",
          "links",
          "accountTeam",
          "overriddenFields"
        ],
        "description": "A customer account, with its account team and the fields edits have taken over."
      },
      "CustomerField": {
        "type": "object",
        "properties": {
          "key": {
            "type": "string",
            "description": "The key in a customer's `customFields`."
          },
          "label": {
            "type": "string"
          },
          "type": {
            "type": "string",
            "enum": [
              "string",
              "number",
              "boolean",
              "date",
              "datetime",
              "select",
              "user",
              "array",
              "object"
            ],
            "description": "`date` is `YYYY-MM-DD`; `datetime` an ISO 8601 instant; `select` one of `options` (or several, with `multiple`); `user` a list of people."
          },
          "format": {
            "description": "How the value is meant: `currency`, `percentage`, `email`, `url`, `phone`, … Null when it's plain.",
            "type": [
              "string",
              "null"
            ]
          },
          "options": {
            "anyOf": [
              {
                "type": "array",
                "items": {
                  "type": "string"
                }
              },
              {
                "type": "null"
              }
            ],
            "description": "`select` only: the values the field allows."
          },
          "multiple": {
            "type": "boolean",
            "description": "Whether the value is a list: a multi-select, or a `user` field holding several people."
          }
        },
        "required": [
          "key",
          "label",
          "type",
          "format",
          "options",
          "multiple"
        ],
        "description": "A custom field on customers."
      },
      "CustomerFieldList": {
        "type": "object",
        "properties": {
          "data": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/CustomerField"
            }
          }
        },
        "required": [
          "data"
        ]
      },
      "CustomerHealth": {
        "type": "object",
        "properties": {
          "state": {
            "type": "string",
            "description": "The state's key on your workspace's health scale — by default `thriving`, `healthy`, `emerging_concerns` or `at_risk` — or `no_data` when nothing could be scored."
          },
          "label": {
            "type": "string",
            "description": "The state as Sonora shows it: `Thriving`, `At Risk`, …"
          },
          "score": {
            "description": "0–10, higher is healthier. Null when there is nothing to score (`no_data`).",
            "type": [
              "number",
              "null"
            ]
          },
          "atRisk": {
            "type": "boolean",
            "description": "Whether your scale counts this state as at risk."
          },
          "computedAt": {
            "type": "string",
            "format": "date-time"
          }
        },
        "required": [
          "state",
          "label",
          "score",
          "atRisk",
          "computedAt"
        ],
        "description": "The customer's health, as the customers page shows it."
      },
      "CustomerList": {
        "type": "object",
        "properties": {
          "data": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Customer"
            }
          },
          "totalCount": {
            "description": "Only with `?include=totalCount`: every match under the same filters.",
            "type": "integer"
          },
          "nextCursor": {
            "description": "Pass as `cursor` for the next page; null on the last page.",
            "type": [
              "string",
              "null"
            ]
          }
        },
        "required": [
          "data",
          "nextCursor"
        ]
      },
      "CustomerRef": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "name": {
            "type": "string"
          }
        },
        "required": [
          "id",
          "name"
        ],
        "description": "The customer a record belongs to; `GET /customers/{customerId}` has the rest of it."
      },
      "Ecsat": {
        "type": "object",
        "properties": {
          "score": {
            "type": "number",
            "description": "The average estimated satisfaction of the customer's scored calls, emails and tickets, 0–10, to one decimal."
          },
          "windowDays": {
            "type": "number",
            "const": 90,
            "description": "The days back the average covers."
          }
        },
        "required": [
          "score",
          "windowDays"
        ],
        "description": "eCSAT: satisfaction Sonora estimates from what the customer says, averaged over recent touchpoints."
      },
      "EnrichmentConfig": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "description": "Stable across edits to the enrichment: the `configId` its values carry."
          },
          "title": {
            "type": "string"
          },
          "description": {
            "type": [
              "string",
              "null"
            ]
          },
          "entityType": {
            "type": "string",
            "enum": [
              "customer"
            ],
            "description": "The kind of record it describes. Only `customer` today."
          },
          "outputType": {
            "type": "string",
            "enum": [
              "short_text",
              "long_text",
              "number",
              "score",
              "boolean",
              "date",
              "email",
              "url",
              "select"
            ],
            "description": "What the value is: `short_text`/`long_text` a string, `number` a number, `score` 1–10 (10 strongest), `boolean`, `date` `YYYY-MM-DD`, `email`, `url`, or `select` one of `options` (a list of them with `multiple`)."
          },
          "options": {
            "anyOf": [
              {
                "type": "array",
                "items": {
                  "type": "string"
                }
              },
              {
                "type": "null"
              }
            ],
            "description": "`select` only: the values it picks from."
          },
          "multiple": {
            "type": "boolean",
            "description": "`select` only: whether a value is a list of options."
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "updatedAt": {
            "type": "string",
            "format": "date-time",
            "description": "When the enrichment was last edited."
          },
          "links": {
            "type": "object",
            "properties": {
              "self": {
                "type": "string",
                "description": "This resource's canonical URL on the API host."
              }
            },
            "required": [
              "self"
            ]
          }
        },
        "required": [
          "id",
          "title",
          "description",
          "entityType",
          "outputType",
          "options",
          "multiple",
          "createdAt",
          "updatedAt",
          "links"
        ],
        "description": "An enrichment: a field Sonora's AI fills in on each record, from your calls, emails and tickets."
      },
      "EnrichmentConfigList": {
        "type": "object",
        "properties": {
          "data": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/EnrichmentConfig"
            }
          },
          "totalCount": {
            "description": "Only with `?include=totalCount`: every match under the same filters.",
            "type": "integer"
          },
          "nextCursor": {
            "description": "Pass as `cursor` for the next page; null on the last page.",
            "type": [
              "string",
              "null"
            ]
          }
        },
        "required": [
          "data",
          "nextCursor"
        ]
      },
      "EnrichmentRun": {
        "type": "object",
        "properties": {
          "queued": {
            "type": "integer",
            "description": "How many customers the run was queued for — fewer than you named when some are outside the enrichment's segment, or already have a value with `onlyMissing`."
          }
        },
        "required": [
          "queued"
        ],
        "description": "A run, queued: each customer's value arrives as it is worked out."
      },
      "EnrichmentValue": {
        "type": "object",
        "properties": {
          "configId": {
            "type": "string",
            "description": "The enrichment's `id` (`GET /enrichment-configs/{configId}`)."
          },
          "title": {
            "type": "string"
          },
          "outputType": {
            "type": "string",
            "enum": [
              "short_text",
              "long_text",
              "number",
              "score",
              "boolean",
              "date",
              "email",
              "url",
              "select"
            ],
            "description": "What the value is: `short_text`/`long_text` a string, `number` a number, `score` 1–10 (10 strongest), `boolean`, `date` `YYYY-MM-DD`, `email`, `url`, or `select` one of `options` (a list of them with `multiple`)."
          },
          "value": {
            "description": "As `outputType` says. Null when there wasn't enough to go on, for any `outputType`."
          },
          "reason": {
            "description": "Why the AI gave this value, when it said.",
            "type": [
              "string",
              "null"
            ]
          },
          "computedAt": {
            "type": "string",
            "format": "date-time"
          }
        },
        "required": [
          "configId",
          "title",
          "outputType",
          "value",
          "reason",
          "computedAt"
        ],
        "description": "A record's latest value of one enrichment."
      },
      "Error": {
        "type": "object",
        "properties": {
          "error": {
            "type": "object",
            "properties": {
              "code": {
                "type": "string",
                "description": "Stable snake_case code to branch on; see the error table."
              },
              "message": {
                "type": "string",
                "description": "For humans; may change."
              },
              "details": {
                "type": "object",
                "additionalProperties": {}
              },
              "requestId": {
                "description": "Quote it when asking for help.",
                "type": [
                  "string",
                  "null"
                ]
              }
            },
            "required": [
              "code",
              "message",
              "requestId"
            ]
          }
        },
        "required": [
          "error"
        ],
        "description": "The one error shape every endpoint answers with."
      },
      "ExternalId": {
        "type": "object",
        "properties": {
          "source": {
            "type": "string",
            "description": "The system the id is from: `salesforce`, `hubspot`, `zendesk`, `intercom`, …"
          },
          "id": {
            "type": "string"
          }
        },
        "required": [
          "source",
          "id"
        ],
        "description": "The record's id in another system."
      },
      "FeatureRequest": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "customer": {
            "$ref": "#/components/schemas/CustomerRef"
          },
          "title": {
            "type": "string"
          },
          "summary": {
            "type": [
              "string",
              "null"
            ]
          },
          "category": {
            "type": [
              "string",
              "null"
            ]
          },
          "status": {
            "type": "string",
            "description": "`open`, `pending` and `in_review` are still waiting on an answer; others vary."
          },
          "priority": {
            "type": [
              "string",
              "null"
            ]
          },
          "requestedAt": {
            "anyOf": [
              {
                "type": "string",
                "format": "date-time"
              },
              {
                "type": "null"
              }
            ],
            "description": "When the request was recorded as made."
          },
          "requestedBy": {
            "description": "Who asked, where the source names them; otherwise what found it (`Meeting Transcript Analysis`).",
            "type": [
              "string",
              "null"
            ]
          },
          "estimatedValue": {
            "description": "What the request is worth, in your workspace's currency, where one was set.",
            "type": [
              "number",
              "null"
            ]
          },
          "occurredAt": {
            "anyOf": [
              {
                "type": "string",
                "format": "date-time"
              },
              {
                "type": "null"
              }
            ],
            "description": "When the request was first raised, in its earliest source's own system."
          },
          "lastActivityAt": {
            "anyOf": [
              {
                "type": "string",
                "format": "date-time"
              },
              {
                "type": "null"
              }
            ],
            "description": "The latest update in its sources' own systems, where Sonora tracks one."
          },
          "sourceCount": {
            "type": "integer"
          },
          "sources": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/FeedbackSource"
            },
            "description": "Every source, oldest link first."
          },
          "quotes": {
            "description": "Only with `?include=quotes`: the customer's own words, source by source.",
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/FeedbackQuote"
            }
          },
          "createdAt": {
            "type": "string",
            "format": "date-time",
            "description": "When Sonora recorded the request."
          },
          "updatedAt": {
            "type": "string",
            "format": "date-time",
            "description": "Moves when the request itself changes. A new source being linked may not move it."
          },
          "links": {
            "type": "object",
            "properties": {
              "self": {
                "type": "string",
                "description": "This resource's canonical URL on the API host."
              }
            },
            "required": [
              "self"
            ]
          }
        },
        "required": [
          "id",
          "customer",
          "title",
          "summary",
          "category",
          "status",
          "priority",
          "requestedAt",
          "requestedBy",
          "estimatedValue",
          "occurredAt",
          "lastActivityAt",
          "sourceCount",
          "sources",
          "createdAt",
          "updatedAt",
          "links"
        ],
        "description": "A capability a customer asked for."
      },
      "FeatureRequestList": {
        "type": "object",
        "properties": {
          "data": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/FeatureRequest"
            }
          },
          "totalCount": {
            "description": "Only with `?include=totalCount`: every match under the same filters.",
            "type": "integer"
          },
          "nextCursor": {
            "description": "Pass as `cursor` for the next page; null on the last page.",
            "type": [
              "string",
              "null"
            ]
          }
        },
        "required": [
          "data",
          "nextCursor"
        ]
      },
      "FeedbackQuote": {
        "type": "object",
        "properties": {
          "text": {
            "type": "string",
            "description": "What the person said or wrote, verbatim."
          },
          "personName": {
            "type": [
              "string",
              "null"
            ]
          },
          "personTitle": {
            "type": [
              "string",
              "null"
            ]
          },
          "personEmail": {
            "description": "A person at the customer, where Sonora knows their address.",
            "type": [
              "string",
              "null"
            ]
          }
        },
        "required": [
          "text",
          "personName",
          "personTitle",
          "personEmail"
        ],
        "description": "A customer's own words behind an issue or feature request."
      },
      "FeedbackSource": {
        "type": "object",
        "properties": {
          "type": {
            "type": "string",
            "description": "Where Sonora found it: `zendesk_support_tickets`, `salesforce_support_cases`, `intercom_tickets`, `pylon_issues`, `gmail`, `gong_calls`, `slack_conversations`, `aha_ideas`, … New types are added as Sonora reads new systems."
          },
          "externalId": {
            "description": "The record's id in that system — a ticket, case, message or idea id. Null where Sonora keeps none.",
            "type": [
              "string",
              "null"
            ]
          }
        },
        "required": [
          "type",
          "externalId"
        ],
        "description": "A ticket, email, call or conversation an issue or feature request was found in."
      },
      "Issue": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "customer": {
            "$ref": "#/components/schemas/CustomerRef"
          },
          "title": {
            "type": "string"
          },
          "summary": {
            "type": "string"
          },
          "category": {
            "type": [
              "string",
              "null"
            ]
          },
          "status": {
            "description": "As the source system spells it (`open`, `Pending`, `Closed - Duplicate`, …); null when it gave none.",
            "type": [
              "string",
              "null"
            ]
          },
          "priority": {
            "description": "As the source system or Sonora's analysis set it.",
            "type": [
              "string",
              "null"
            ]
          },
          "occurredAt": {
            "anyOf": [
              {
                "type": "string",
                "format": "date-time"
              },
              {
                "type": "null"
              }
            ],
            "description": "When the issue was first raised: its earliest source's date in that source's own system."
          },
          "lastActivityAt": {
            "anyOf": [
              {
                "type": "string",
                "format": "date-time"
              },
              {
                "type": "null"
              }
            ],
            "description": "The latest update among its sources, in their own systems."
          },
          "sourceCount": {
            "type": "integer"
          },
          "sources": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/FeedbackSource"
            },
            "description": "Every source, oldest link first."
          },
          "quotes": {
            "description": "Only with `?include=quotes`: the customer's own words, in the issue's order.",
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/FeedbackQuote"
            }
          },
          "createdAt": {
            "type": "string",
            "format": "date-time",
            "description": "When Sonora recorded the issue."
          },
          "updatedAt": {
            "type": "string",
            "format": "date-time",
            "description": "Moves on any change to the issue, including a new source being linked."
          },
          "links": {
            "type": "object",
            "properties": {
              "self": {
                "type": "string",
                "description": "This resource's canonical URL on the API host."
              }
            },
            "required": [
              "self"
            ]
          }
        },
        "required": [
          "id",
          "customer",
          "title",
          "summary",
          "category",
          "status",
          "priority",
          "occurredAt",
          "lastActivityAt",
          "sourceCount",
          "sources",
          "createdAt",
          "updatedAt",
          "links"
        ],
        "description": "A problem a customer raised."
      },
      "IssueList": {
        "type": "object",
        "properties": {
          "data": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Issue"
            }
          },
          "totalCount": {
            "description": "Only with `?include=totalCount`: every match under the same filters.",
            "type": "integer"
          },
          "nextCursor": {
            "description": "Pass as `cursor` for the next page; null on the last page.",
            "type": [
              "string",
              "null"
            ]
          }
        },
        "required": [
          "data",
          "nextCursor"
        ]
      },
      "Me": {
        "type": "object",
        "properties": {
          "tenant": {
            "type": "object",
            "properties": {
              "id": {
                "type": "string"
              },
              "name": {
                "type": "string"
              },
              "subdomain": {
                "type": "string"
              }
            },
            "required": [
              "id",
              "name",
              "subdomain"
            ]
          },
          "apiKey": {
            "type": "object",
            "properties": {
              "id": {
                "type": "string"
              },
              "name": {
                "type": "string"
              },
              "prefix": {
                "type": "string",
                "description": "The key's public prefix, as Settings → API shows it."
              },
              "scopes": {
                "type": "array",
                "items": {
                  "type": "string"
                }
              },
              "createdAt": {
                "type": "string",
                "format": "date-time"
              },
              "expiresAt": {
                "anyOf": [
                  {
                    "type": "string",
                    "format": "date-time"
                  },
                  {
                    "type": "null"
                  }
                ]
              }
            },
            "required": [
              "id",
              "name",
              "prefix",
              "scopes",
              "createdAt",
              "expiresAt"
            ]
          }
        },
        "required": [
          "tenant",
          "apiKey"
        ],
        "description": "The workspace a key belongs to, and the key itself."
      },
      "Meeting": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "customer": {
            "$ref": "#/components/schemas/CustomerRef"
          },
          "title": {
            "type": [
              "string",
              "null"
            ]
          },
          "startTime": {
            "anyOf": [
              {
                "type": "string",
                "format": "date-time"
              },
              {
                "type": "null"
              }
            ]
          },
          "endTime": {
            "anyOf": [
              {
                "type": "string",
                "format": "date-time"
              },
              {
                "type": "null"
              }
            ]
          },
          "durationSeconds": {
            "anyOf": [
              {
                "type": "integer"
              },
              {
                "type": "null"
              }
            ],
            "description": "As the recording or calendar reports it, else the gap from start to end."
          },
          "summary": {
            "description": "What was discussed: your call recorder's summary, or Sonora's own from the transcript.",
            "type": [
              "string",
              "null"
            ]
          },
          "nextSteps": {
            "description": "The next steps the summary drew from the meeting.",
            "type": [
              "string",
              "null"
            ]
          },
          "owner": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/Person"
              },
              {
                "type": "null"
              }
            ],
            "description": "Who on your team ran the meeting, where known."
          },
          "attendees": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/MeetingAttendee"
            },
            "description": "Organizer first."
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "updatedAt": {
            "type": "string",
            "format": "date-time"
          },
          "links": {
            "type": "object",
            "properties": {
              "self": {
                "type": "string",
                "description": "This resource's canonical URL on the API host."
              }
            },
            "required": [
              "self"
            ]
          }
        },
        "required": [
          "id",
          "customer",
          "title",
          "startTime",
          "endTime",
          "durationSeconds",
          "summary",
          "nextSteps",
          "owner",
          "attendees",
          "createdAt",
          "updatedAt",
          "links"
        ],
        "description": "A meeting with a customer."
      },
      "MeetingAttendee": {
        "type": "object",
        "properties": {
          "name": {
            "type": [
              "string",
              "null"
            ]
          },
          "email": {
            "type": "string"
          },
          "isOrganizer": {
            "type": "boolean"
          },
          "contactId": {
            "description": "The Sonora contact this attendee is; null for your own team and anyone unmatched.",
            "type": [
              "string",
              "null"
            ]
          }
        },
        "required": [
          "name",
          "email",
          "isOrganizer",
          "contactId"
        ],
        "description": "Someone invited to a meeting."
      },
      "MeetingList": {
        "type": "object",
        "properties": {
          "data": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Meeting"
            }
          },
          "totalCount": {
            "description": "Only with `?include=totalCount`: every match under the same filters.",
            "type": "integer"
          },
          "nextCursor": {
            "description": "Pass as `cursor` for the next page; null on the last page.",
            "type": [
              "string",
              "null"
            ]
          }
        },
        "required": [
          "data",
          "nextCursor"
        ]
      },
      "Note": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "customerId": {
            "type": "string"
          },
          "title": {
            "type": "string"
          },
          "contentMarkdown": {
            "type": "string",
            "description": "The note as Markdown: headings, lists, checklists, links and emphasis kept."
          },
          "contentText": {
            "type": "string",
            "description": "The note as plain text, for search or a preview."
          },
          "author": {
            "$ref": "#/components/schemas/NoteAuthor"
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "updatedAt": {
            "type": "string",
            "format": "date-time"
          },
          "links": {
            "type": "object",
            "properties": {
              "self": {
                "type": "string",
                "description": "This resource's canonical URL on the API host."
              }
            },
            "required": [
              "self"
            ]
          }
        },
        "required": [
          "id",
          "customerId",
          "title",
          "contentMarkdown",
          "contentText",
          "author",
          "createdAt",
          "updatedAt",
          "links"
        ],
        "description": "A note your team shares on a customer."
      },
      "NoteAuthor": {
        "type": "object",
        "properties": {
          "type": {
            "type": "string",
            "enum": [
              "user",
              "api_key"
            ],
            "description": "`user` for a note someone wrote in Sonora; `api_key` for one written through the API."
          },
          "name": {
            "description": "The person's name, or the API key's. Null for a key since deleted.",
            "type": [
              "string",
              "null"
            ]
          },
          "email": {
            "description": "The person's email. Always null for an API key.",
            "type": [
              "string",
              "null"
            ]
          }
        },
        "required": [
          "type",
          "name",
          "email"
        ],
        "description": "Who wrote a note: a person on your team, or an API key — never the person who created the key."
      },
      "NoteList": {
        "type": "object",
        "properties": {
          "data": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Note"
            }
          },
          "totalCount": {
            "description": "Only with `?include=totalCount`: every match under the same filters.",
            "type": "integer"
          },
          "nextCursor": {
            "description": "Pass as `cursor` for the next page; null on the last page.",
            "type": [
              "string",
              "null"
            ]
          }
        },
        "required": [
          "data",
          "nextCursor"
        ]
      },
      "Person": {
        "type": "object",
        "properties": {
          "name": {
            "type": [
              "string",
              "null"
            ]
          },
          "email": {
            "description": "Null for a name typed in by hand or synced without a user.",
            "type": [
              "string",
              "null"
            ]
          }
        },
        "required": [
          "name",
          "email"
        ],
        "description": "A person on your team."
      },
      "RecipientMatch": {
        "oneOf": [
          {
            "type": "object",
            "properties": {
              "type": {
                "type": "string",
                "const": "role"
              },
              "role": {
                "type": "string"
              }
            },
            "required": [
              "type",
              "role"
            ]
          },
          {
            "type": "object",
            "properties": {
              "type": {
                "type": "string",
                "const": "email"
              }
            },
            "required": [
              "type"
            ]
          },
          {
            "type": "object",
            "properties": {
              "type": {
                "type": "string",
                "const": "contactId"
              }
            },
            "required": [
              "type"
            ]
          }
        ],
        "description": "Why a call named a person: by role slug, by email, or by contact id."
      },
      "RunRecipient": {
        "type": "object",
        "properties": {
          "contactId": {
            "type": [
              "string",
              "null"
            ]
          },
          "email": {
            "type": "string"
          },
          "name": {
            "description": "Read fresh from the contact.",
            "type": [
              "string",
              "null"
            ]
          },
          "matchedBy": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/RecipientMatch"
            }
          }
        },
        "required": [
          "contactId",
          "email",
          "name",
          "matchedBy"
        ],
        "description": "One person an API call named that the run reaches."
      },
      "SkippedRecipient": {
        "type": "object",
        "properties": {
          "contactId": {
            "type": "string"
          },
          "email": {
            "type": "string"
          },
          "reason": {
            "type": "string",
            "enum": [
              "unsubscribed",
              "bounced",
              "do_not_contact",
              "no_email",
              "recently_triggered"
            ]
          },
          "matchedBy": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/RecipientMatch"
            }
          },
          "lastRunId": {
            "description": "`recently_triggered` only: the run that last reached them through this workflow.",
            "type": "string"
          },
          "retryAfter": {
            "description": "`recently_triggered` only: when the resend window lets a call reach them again.",
            "type": "string"
          }
        },
        "required": [
          "reason",
          "matchedBy"
        ],
        "description": "A person the call named who won't be emailed."
      },
      "StartRunBatchResponse": {
        "type": "object",
        "properties": {
          "data": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/StartRunBatchResult"
            },
            "description": "One result per element, in order."
          }
        },
        "required": [
          "data"
        ]
      },
      "StartRunBatchResult": {
        "type": "object",
        "properties": {
          "status": {
            "type": "integer",
            "description": "What the single call for this element would have answered."
          },
          "body": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/WorkflowRunAcceptance"
              },
              {
                "$ref": "#/components/schemas/Error"
              }
            ]
          }
        },
        "required": [
          "status",
          "body"
        ],
        "description": "One element's outcome."
      },
      "Workflow": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "description": "The chain root id: what `POST /workflows/{id}/runs` takes, stable across edits."
          },
          "name": {
            "type": "string"
          },
          "active": {
            "type": "boolean",
            "description": "Whether a call can start a run now. False for a draft (`409 workflow_inactive`) and for a workflow whose author was removed (`409 workflow_has_no_owner`)."
          },
          "acceptsRecipients": {
            "type": "boolean",
            "description": "Whether a call names its recipients (`recipients.roles` / `emails` / `contactIds`)."
          },
          "variables": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "key": {
                  "type": "string"
                },
                "label": {
                  "type": [
                    "string",
                    "null"
                  ]
                },
                "description": {
                  "type": [
                    "string",
                    "null"
                  ]
                },
                "required": {
                  "type": "boolean"
                }
              },
              "required": [
                "key",
                "label",
                "description",
                "required"
              ]
            }
          },
          "resendWindowHours": {
            "type": "number"
          },
          "runsUrl": {
            "type": "string",
            "description": "Where `POST` starts a run."
          }
        },
        "required": [
          "id",
          "name",
          "active",
          "acceptsRecipients",
          "variables",
          "resendWindowHours",
          "runsUrl"
        ],
        "description": "An API-triggered workflow a caller can start."
      },
      "WorkflowList": {
        "type": "object",
        "properties": {
          "data": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Workflow"
            }
          }
        },
        "required": [
          "data"
        ]
      },
      "WorkflowRun": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "status": {
            "type": "string",
            "enum": [
              "active",
              "completed",
              "failed",
              "cancelled"
            ]
          },
          "origin": {
            "type": "string",
            "enum": [
              "auto",
              "manual",
              "api"
            ]
          },
          "workflow": {
            "type": "object",
            "properties": {
              "id": {
                "type": "string",
                "description": "The workflow's chain root id."
              },
              "versionId": {
                "type": "string",
                "description": "The version the run executes."
              },
              "name": {
                "type": [
                  "string",
                  "null"
                ]
              }
            },
            "required": [
              "id",
              "versionId",
              "name"
            ]
          },
          "account": {
            "anyOf": [
              {
                "type": "object",
                "properties": {
                  "id": {
                    "type": "string"
                  },
                  "name": {
                    "type": [
                      "string",
                      "null"
                    ]
                  }
                },
                "required": [
                  "id",
                  "name"
                ]
              },
              {
                "type": "null"
              }
            ],
            "description": "The account the run is about; null for a contacts or meetings workflow's run."
          },
          "recipients": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/RunRecipient"
            },
            "description": "Who an API call named and the run reaches; empty otherwise."
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "completedAt": {
            "anyOf": [
              {
                "type": "string",
                "format": "date-time"
              },
              {
                "type": "null"
              }
            ]
          },
          "goal": {
            "anyOf": [
              {
                "type": "object",
                "properties": {
                  "name": {
                    "type": "string"
                  },
                  "status": {
                    "type": "string"
                  },
                  "metAt": {
                    "anyOf": [
                      {
                        "type": "string",
                        "format": "date-time"
                      },
                      {
                        "type": "null"
                      }
                    ]
                  },
                  "windowEndsAt": {
                    "type": "string",
                    "format": "date-time"
                  }
                },
                "required": [
                  "name",
                  "status",
                  "metAt",
                  "windowEndsAt"
                ]
              },
              {
                "type": "null"
              }
            ]
          },
          "links": {
            "type": "object",
            "properties": {
              "self": {
                "type": "string",
                "description": "This resource's canonical URL on the API host."
              }
            },
            "required": [
              "self"
            ]
          },
          "trigger": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/WorkflowRunTrigger"
              },
              {
                "type": "null"
              }
            ]
          },
          "currentStep": {
            "anyOf": [
              {
                "type": "object",
                "properties": {
                  "id": {
                    "type": "string"
                  },
                  "label": {
                    "type": "string"
                  }
                },
                "required": [
                  "id",
                  "label"
                ]
              },
              {
                "type": "null"
              }
            ]
          },
          "waitingOn": {
            "anyOf": [
              {
                "type": "string",
                "enum": [
                  "review",
                  "send",
                  "reply",
                  "delay",
                  "enrichment"
                ]
              },
              {
                "type": "null"
              }
            ],
            "description": "What the run is waiting for right now, if anything."
          },
          "error": {
            "type": [
              "string",
              "null"
            ]
          },
          "steps": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/WorkflowRunStep"
            }
          },
          "messages": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/WorkflowRunMessage"
            }
          }
        },
        "required": [
          "id",
          "status",
          "origin",
          "workflow",
          "account",
          "recipients",
          "createdAt",
          "completedAt",
          "goal",
          "links",
          "trigger",
          "currentStep",
          "waitingOn",
          "error",
          "steps",
          "messages"
        ],
        "description": "One run: its trigger, steps, what it is waiting on, and every email it produced."
      },
      "WorkflowRunAcceptance": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "origin": {
            "type": "string",
            "enum": [
              "auto",
              "manual",
              "api"
            ]
          },
          "workflow": {
            "type": "object",
            "properties": {
              "id": {
                "type": "string",
                "description": "The workflow's chain root id."
              },
              "versionId": {
                "type": "string",
                "description": "The version the run executes."
              },
              "name": {
                "type": [
                  "string",
                  "null"
                ]
              }
            },
            "required": [
              "id",
              "versionId",
              "name"
            ]
          },
          "account": {
            "anyOf": [
              {
                "type": "object",
                "properties": {
                  "id": {
                    "type": "string"
                  },
                  "name": {
                    "type": [
                      "string",
                      "null"
                    ]
                  }
                },
                "required": [
                  "id",
                  "name"
                ]
              },
              {
                "type": "null"
              }
            ],
            "description": "The account the run is about; null for a contacts or meetings workflow's run."
          },
          "recipients": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/RunRecipient"
            },
            "description": "Who an API call named and the run reaches; empty otherwise."
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "links": {
            "type": "object",
            "properties": {
              "self": {
                "type": "string",
                "description": "This resource's canonical URL on the API host."
              }
            },
            "required": [
              "self"
            ]
          },
          "trigger": {
            "$ref": "#/components/schemas/WorkflowRunTrigger"
          }
        },
        "required": [
          "id",
          "origin",
          "workflow",
          "account",
          "recipients",
          "createdAt",
          "links",
          "trigger"
        ],
        "description": "What a start call started: the run's fields fixed at acceptance. Its progress is `GET links.self`."
      },
      "WorkflowRunList": {
        "type": "object",
        "properties": {
          "data": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/WorkflowRunSummary"
            }
          },
          "totalCount": {
            "description": "Only with `?include=totalCount`: every match under the same filters.",
            "type": "integer"
          },
          "nextCursor": {
            "description": "Pass as `cursor` for the next page; null on the last page.",
            "type": [
              "string",
              "null"
            ]
          }
        },
        "required": [
          "data",
          "nextCursor"
        ]
      },
      "WorkflowRunMessage": {
        "type": "object",
        "properties": {
          "stepId": {
            "type": "string"
          },
          "stepLabel": {
            "type": "string"
          },
          "cueId": {
            "type": "string"
          },
          "contactId": {
            "type": [
              "string",
              "null"
            ]
          },
          "email": {
            "type": [
              "string",
              "null"
            ]
          },
          "status": {
            "type": "string",
            "enum": [
              "draft",
              "held",
              "sent",
              "bounced",
              "not_sent"
            ]
          },
          "heldUntil": {
            "anyOf": [
              {
                "type": "string",
                "format": "date-time"
              },
              {
                "type": "null"
              }
            ],
            "description": "`held` only: when the hold lifts."
          },
          "note": {
            "description": "Why a send reached fewer people than it addressed, or nobody.",
            "type": [
              "string",
              "null"
            ]
          },
          "sentAt": {
            "anyOf": [
              {
                "type": "string",
                "format": "date-time"
              },
              {
                "type": "null"
              }
            ]
          },
          "trackingScope": {
            "anyOf": [
              {
                "type": "string",
                "enum": [
                  "recipient",
                  "email"
                ]
              },
              {
                "type": "null"
              }
            ],
            "description": "Whose opens and clicks the counts are. `recipient`: this person was the email's only To recipient. `email`: one email went To several people with one tracking pixel, so its opens and clicks are recorded on the first person on the To line and the others read zero. Null until sent."
          },
          "firstOpenedAt": {
            "anyOf": [
              {
                "type": "string",
                "format": "date-time"
              },
              {
                "type": "null"
              }
            ]
          },
          "openCount": {
            "type": "integer"
          },
          "firstClickedAt": {
            "anyOf": [
              {
                "type": "string",
                "format": "date-time"
              },
              {
                "type": "null"
              }
            ]
          },
          "clickCount": {
            "type": "integer"
          },
          "firstRepliedAt": {
            "anyOf": [
              {
                "type": "string",
                "format": "date-time"
              },
              {
                "type": "null"
              }
            ]
          },
          "replyCount": {
            "type": "integer"
          },
          "firstBouncedAt": {
            "anyOf": [
              {
                "type": "string",
                "format": "date-time"
              },
              {
                "type": "null"
              }
            ]
          }
        },
        "required": [
          "stepId",
          "stepLabel",
          "cueId",
          "contactId",
          "email",
          "status",
          "heldUntil",
          "note",
          "sentAt",
          "trackingScope",
          "firstOpenedAt",
          "openCount",
          "firstClickedAt",
          "clickCount",
          "firstRepliedAt",
          "replyCount",
          "firstBouncedAt"
        ],
        "description": "One email a run produced, per recipient once it has gone out."
      },
      "WorkflowRunStep": {
        "type": "object",
        "properties": {
          "runId": {
            "type": "string",
            "description": "The run that took the step: the root, or one fan-out branch."
          },
          "id": {
            "type": "string"
          },
          "label": {
            "type": "string"
          },
          "type": {
            "type": [
              "string",
              "null"
            ]
          },
          "status": {
            "type": "string"
          },
          "outcome": {
            "type": [
              "string",
              "null"
            ]
          },
          "error": {
            "type": [
              "string",
              "null"
            ]
          },
          "enteredAt": {
            "type": "string",
            "format": "date-time"
          },
          "endedAt": {
            "anyOf": [
              {
                "type": "string",
                "format": "date-time"
              },
              {
                "type": "null"
              }
            ]
          }
        },
        "required": [
          "runId",
          "id",
          "label",
          "type",
          "status",
          "outcome",
          "error",
          "enteredAt",
          "endedAt"
        ]
      },
      "WorkflowRunSummary": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "status": {
            "type": "string",
            "enum": [
              "active",
              "completed",
              "failed",
              "cancelled"
            ]
          },
          "origin": {
            "type": "string",
            "enum": [
              "auto",
              "manual",
              "api"
            ]
          },
          "workflow": {
            "type": "object",
            "properties": {
              "id": {
                "type": "string",
                "description": "The workflow's chain root id."
              },
              "versionId": {
                "type": "string",
                "description": "The version the run executes."
              },
              "name": {
                "type": [
                  "string",
                  "null"
                ]
              }
            },
            "required": [
              "id",
              "versionId",
              "name"
            ]
          },
          "account": {
            "anyOf": [
              {
                "type": "object",
                "properties": {
                  "id": {
                    "type": "string"
                  },
                  "name": {
                    "type": [
                      "string",
                      "null"
                    ]
                  }
                },
                "required": [
                  "id",
                  "name"
                ]
              },
              {
                "type": "null"
              }
            ],
            "description": "The account the run is about; null for a contacts or meetings workflow's run."
          },
          "recipients": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/RunRecipient"
            },
            "description": "Who an API call named and the run reaches; empty otherwise."
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "completedAt": {
            "anyOf": [
              {
                "type": "string",
                "format": "date-time"
              },
              {
                "type": "null"
              }
            ]
          },
          "goal": {
            "anyOf": [
              {
                "type": "object",
                "properties": {
                  "name": {
                    "type": "string"
                  },
                  "status": {
                    "type": "string"
                  },
                  "metAt": {
                    "anyOf": [
                      {
                        "type": "string",
                        "format": "date-time"
                      },
                      {
                        "type": "null"
                      }
                    ]
                  },
                  "windowEndsAt": {
                    "type": "string",
                    "format": "date-time"
                  }
                },
                "required": [
                  "name",
                  "status",
                  "metAt",
                  "windowEndsAt"
                ]
              },
              {
                "type": "null"
              }
            ]
          },
          "links": {
            "type": "object",
            "properties": {
              "self": {
                "type": "string",
                "description": "This resource's canonical URL on the API host."
              }
            },
            "required": [
              "self"
            ]
          }
        },
        "required": [
          "id",
          "status",
          "origin",
          "workflow",
          "account",
          "recipients",
          "createdAt",
          "completedAt",
          "goal",
          "links"
        ],
        "description": "One run as the list returns it."
      },
      "WorkflowRunTrigger": {
        "type": "object",
        "properties": {
          "requestId": {
            "type": "string"
          },
          "receivedAt": {
            "type": "string",
            "format": "date-time"
          },
          "account": {
            "type": "object",
            "properties": {
              "id": {
                "type": "string"
              },
              "idType": {
                "type": "string"
              }
            },
            "required": [
              "id",
              "idType"
            ]
          },
          "roles": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "The roles the call matched, as slugs."
          },
          "variables": {
            "type": "object",
            "additionalProperties": {
              "type": [
                "string",
                "number",
                "boolean"
              ]
            }
          },
          "idempotencyKey": {
            "type": [
              "string",
              "null"
            ]
          },
          "skippedRecipients": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/SkippedRecipient"
            }
          }
        },
        "required": [
          "requestId",
          "receivedAt",
          "account",
          "roles",
          "variables",
          "idempotencyKey",
          "skippedRecipients"
        ],
        "description": "What the API call that started a run asked for, and who it skipped."
      },
      "AccountRef": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "minLength": 1,
            "maxLength": 255,
            "description": "The account's id, in the system `idType` names."
          },
          "idType": {
            "default": "sonora",
            "description": "How `id` names the account: `sonora` (the default; Sonora's customer id), `salesforce`, `hubspot`, `domain`, or `custom_field:<key>`.",
            "type": "string",
            "pattern": "^(sonora|salesforce|hubspot|domain|custom_field:[^\\s].{0,99})$"
          }
        },
        "required": [
          "id"
        ],
        "description": "An account as the caller's own systems name it."
      },
      "AddAccountTeamMemberRequest": {
        "type": "object",
        "properties": {
          "role": {
            "description": "Their role on the account: `Solutions engineer`, `Executive sponsor`. Left out, a member keeps the role they have (one taken off the team and put back gets theirs back), and a new member has none; `null` clears it. On a member your CRM syncs, the CRM's role returns with its next sync.",
            "anyOf": [
              {
                "type": "string",
                "minLength": 1,
                "maxLength": 255
              },
              {
                "type": "null"
              }
            ]
          }
        },
        "additionalProperties": false,
        "description": "The member's role, if any. The body may be left out."
      },
      "CreateCustomerFieldRequest": {
        "type": "object",
        "properties": {
          "label": {
            "type": "string",
            "minLength": 1,
            "maxLength": 100,
            "description": "How Sonora shows the field. No other field may show the same label, ignoring case; runs of spaces are kept as one."
          },
          "type": {
            "type": "string",
            "enum": [
              "string",
              "number",
              "boolean",
              "date",
              "select",
              "user"
            ],
            "description": "`date` holds `YYYY-MM-DD`; `select` one of `options`; `user` people in your workspace."
          },
          "format": {
            "description": "`currency` on a `number`; `url` or `email` on a `string`. Left out: plain.",
            "anyOf": [
              {
                "type": "string",
                "enum": [
                  "currency",
                  "url",
                  "email"
                ]
              },
              {
                "type": "null"
              }
            ]
          },
          "options": {
            "description": "`select` only, and required there: the values the field allows, in order — 1 to 50, unique, each up to 100 characters.",
            "anyOf": [
              {
                "minItems": 1,
                "maxItems": 50,
                "type": "array",
                "items": {
                  "type": "string",
                  "minLength": 1,
                  "maxLength": 100
                }
              },
              {
                "type": "null"
              }
            ]
          },
          "multiple": {
            "description": "`user` only: `true` lets the field hold several people. Left out, it holds one.",
            "type": "boolean"
          }
        },
        "required": [
          "label",
          "type"
        ],
        "additionalProperties": false,
        "description": "A custom field to add to customers. Its key is made from the label."
      },
      "CreateCustomerRequest": {
        "type": "object",
        "properties": {
          "domain": {
            "type": "string",
            "minLength": 1,
            "maxLength": 255,
            "description": "The company's website or email domain — `acme.com`, `https://www.acme.com/pricing` — kept as the bare domain. It becomes the customer's website and its email domain, so the people Sonora sees writing from it belong to the customer."
          },
          "name": {
            "description": "Defaults to a name made from the domain: `acme-cloud.com` becomes `Acme Cloud`.",
            "type": "string",
            "minLength": 1,
            "maxLength": 255
          }
        },
        "required": [
          "domain"
        ],
        "additionalProperties": false,
        "description": "A customer to create, named by its domain."
      },
      "CreateEnrichmentConfigRequest": {
        "type": "object",
        "properties": {
          "title": {
            "type": "string",
            "minLength": 1,
            "maxLength": 120
          },
          "description": {
            "anyOf": [
              {
                "type": "string",
                "maxLength": 1000
              },
              {
                "type": "null"
              }
            ],
            "description": "What the enrichment means, for the people reading it. A blank one is `null`."
          },
          "prompt": {
            "type": "string",
            "minLength": 1,
            "maxLength": 12000,
            "description": "The question the AI answers for each customer, in plain language (Markdown is fine): `What is the main reason this account might not renew?`"
          },
          "outputType": {
            "type": "string",
            "enum": [
              "short_text",
              "long_text",
              "number",
              "score",
              "boolean",
              "date",
              "email",
              "url",
              "select"
            ],
            "description": "What the value is: `short_text`/`long_text` a string, `number` a number, `score` 1–10 (10 strongest), `boolean`, `date` `YYYY-MM-DD`, `email`, `url`, or `select` one of `options` (a list of them with `multiple`)."
          },
          "options": {
            "minItems": 2,
            "maxItems": 10,
            "type": "array",
            "items": {
              "type": "string",
              "minLength": 1,
              "maxLength": 200
            },
            "description": "`select` only, and required for it: the 2–10 values it picks from."
          },
          "multiple": {
            "type": "boolean",
            "description": "`select` only: a value is a list of options rather than one. Defaults to `false`."
          },
          "dataSources": {
            "minItems": 1,
            "maxItems": 9,
            "type": "array",
            "items": {
              "type": "string",
              "enum": [
                "meetings",
                "emails",
                "notes",
                "issues",
                "feature_requests",
                "product_usage",
                "ecsat",
                "engagement",
                "web_search"
              ]
            },
            "description": "What the AI may read to answer: the customer's `meetings`, `emails`, `notes`, `issues`, `feature_requests`, `product_usage`, `ecsat` and `engagement`, or `web_search`. A run usually costs 1 credit per customer, or 4 with `web_search`, which also needs web search on your plan. `emails` is read with the access of the person who made the enrichment, so only one someone made in Sonora takes it: one created through the API has no owner."
          }
        },
        "required": [
          "title",
          "prompt",
          "outputType",
          "dataSources"
        ],
        "additionalProperties": false,
        "description": "An enrichment to add to every customer: a question for the AI, the shape of its answer, and what it may read."
      },
      "CreateNoteRequest": {
        "type": "object",
        "properties": {
          "title": {
            "description": "Defaults to `Untitled`, as a new note in Sonora does.",
            "type": "string",
            "minLength": 1,
            "maxLength": 255
          },
          "contentMarkdown": {
            "type": "string",
            "maxLength": 50000,
            "description": "The note as Markdown: headings, lists, `- [ ]` checklists, links, emphasis and code. Raw HTML is shown as text, never rendered."
          }
        },
        "required": [
          "contentMarkdown"
        ],
        "additionalProperties": false,
        "description": "A shared note to add to a customer."
      },
      "PersonRef": {
        "type": "object",
        "properties": {
          "email": {
            "anyOf": [
              {
                "type": "string",
                "format": "email"
              },
              {
                "type": "null"
              }
            ],
            "description": "A person in your workspace. `null` only restates an owner your CRM names who isn't a Sonora user."
          },
          "name": {
            "description": "Ignored — a person is named by email — so a `Person` you read can be sent back.",
            "type": [
              "string",
              "null"
            ]
          }
        },
        "required": [
          "email"
        ],
        "additionalProperties": false,
        "description": "A person on your team, named by email."
      },
      "RunEnrichmentConfigRequest": {
        "type": "object",
        "properties": {
          "customerIds": {
            "description": "Up to 100 customers to run it on, by Sonora id.",
            "minItems": 1,
            "maxItems": 100,
            "type": "array",
            "items": {
              "type": "string",
              "format": "uuid"
            }
          },
          "all": {
            "description": "Run it on every customer that isn't archived.",
            "type": "boolean",
            "const": true
          },
          "onlyMissing": {
            "description": "Skip customers that already have a value, so only the rest are run and charged. Defaults to `false`: every customer named is worked out again.",
            "type": "boolean"
          }
        },
        "additionalProperties": false,
        "description": "The customers to run the enrichment on: `customerIds`, or `all`."
      },
      "StartRunBatchRequest": {
        "minItems": 1,
        "maxItems": 100,
        "type": "array",
        "items": {
          "$ref": "#/components/schemas/StartRunRequest"
        },
        "description": "Start the workflow for up to 100 accounts, each named once."
      },
      "StartRunRequest": {
        "type": "object",
        "properties": {
          "account": {
            "description": "The account the run is about.",
            "$ref": "#/components/schemas/AccountRef"
          },
          "recipients": {
            "description": "Who the run emails, for a workflow whose email steps send to people the call names. Required there, refused elsewhere.",
            "type": "object",
            "properties": {
              "roles": {
                "description": "Contact role slugs (`GET /contact-roles`); everyone at the account in one of them.",
                "maxItems": 50,
                "type": "array",
                "items": {
                  "type": "string",
                  "minLength": 1,
                  "maxLength": 200
                }
              },
              "emails": {
                "maxItems": 50,
                "type": "array",
                "items": {
                  "type": "string",
                  "format": "email"
                }
              },
              "contactIds": {
                "maxItems": 50,
                "type": "array",
                "items": {
                  "type": "string",
                  "format": "uuid"
                }
              }
            },
            "additionalProperties": false
          },
          "variables": {
            "description": "Values for the workflow's declared variables (`GET /workflows`): strings, numbers or booleans.",
            "type": "object",
            "additionalProperties": {}
          }
        },
        "required": [
          "account"
        ],
        "additionalProperties": false,
        "description": "Start one run of an API-triggered workflow."
      },
      "UpdateCustomerFieldRequest": {
        "type": "object",
        "properties": {
          "label": {
            "type": "string",
            "minLength": 1,
            "maxLength": 100,
            "description": "How Sonora shows the field. No other field may show the same label, ignoring case; runs of spaces are kept as one."
          },
          "options": {
            "description": "A select field's options, in the order to show them: every option it has, plus any to add. Leaving one out is `422`, since customers may hold it. Adding is `422` past 50 options, or past the field's own count if it already has more, and an option added must be 1 to 100 characters.",
            "minItems": 1,
            "type": "array",
            "items": {
              "type": "string"
            }
          }
        },
        "additionalProperties": false,
        "description": "What to change. The key, the type and every customer's value stay as they are."
      },
      "UpdateCustomerRequest": {
        "type": "object",
        "properties": {
          "name": {
            "type": "string"
          },
          "arr": {
            "description": "Annual recurring revenue, 0–9,999,999,999.99; kept to the cent.",
            "type": [
              "number",
              "null"
            ]
          },
          "stage": {
            "type": [
              "string",
              "null"
            ]
          },
          "tier": {
            "type": [
              "string",
              "null"
            ]
          },
          "renewalDate": {
            "anyOf": [
              {
                "type": "string",
                "format": "date"
              },
              {
                "type": "null"
              }
            ]
          },
          "contractStartDate": {
            "anyOf": [
              {
                "type": "string",
                "format": "date"
              },
              {
                "type": "null"
              }
            ]
          },
          "owner": {
            "description": "`null` leaves the account unowned.",
            "anyOf": [
              {
                "$ref": "#/components/schemas/PersonRef"
              },
              {
                "type": "null"
              }
            ]
          },
          "customFields": {
            "description": "Custom fields by key, each as its `type` says (`GET /customer-fields`); `null` clears one. A `user` field takes a list of people, `[{ \"email\" }]`. Keys you leave out are kept.",
            "type": "object",
            "propertyNames": {
              "type": "string",
              "minLength": 1,
              "maxLength": 255
            },
            "additionalProperties": {}
          }
        },
        "additionalProperties": false,
        "description": "The fields to change. A field you leave out is kept, `null` clears one, and a value equal to what the customer already holds is not a change."
      },
      "UpdateEnrichmentConfigRequest": {
        "type": "object",
        "properties": {
          "title": {
            "type": "string",
            "minLength": 1,
            "maxLength": 120
          },
          "description": {
            "anyOf": [
              {
                "type": "string",
                "maxLength": 1000
              },
              {
                "type": "null"
              }
            ],
            "description": "What the enrichment means, for the people reading it. A blank one is `null`."
          },
          "prompt": {
            "type": "string",
            "minLength": 1,
            "maxLength": 12000,
            "description": "The question the AI answers for each customer, in plain language (Markdown is fine): `What is the main reason this account might not renew?`"
          },
          "outputType": {
            "type": "string",
            "enum": [
              "short_text",
              "long_text",
              "number",
              "score",
              "boolean",
              "date",
              "email",
              "url",
              "select"
            ],
            "description": "What the value is: `short_text`/`long_text` a string, `number` a number, `score` 1–10 (10 strongest), `boolean`, `date` `YYYY-MM-DD`, `email`, `url`, or `select` one of `options` (a list of them with `multiple`)."
          },
          "options": {
            "minItems": 2,
            "maxItems": 10,
            "type": "array",
            "items": {
              "type": "string",
              "minLength": 1,
              "maxLength": 200
            },
            "description": "`select` only, and required for it: the 2–10 values it picks from."
          },
          "multiple": {
            "type": "boolean",
            "description": "`select` only: a value is a list of options rather than one. Defaults to `false`."
          },
          "dataSources": {
            "minItems": 1,
            "maxItems": 9,
            "type": "array",
            "items": {
              "type": "string",
              "enum": [
                "meetings",
                "emails",
                "notes",
                "issues",
                "feature_requests",
                "product_usage",
                "ecsat",
                "engagement",
                "web_search"
              ]
            },
            "description": "What the AI may read to answer: the customer's `meetings`, `emails`, `notes`, `issues`, `feature_requests`, `product_usage`, `ecsat` and `engagement`, or `web_search`. A run usually costs 1 credit per customer, or 4 with `web_search`, which also needs web search on your plan. `emails` is read with the access of the person who made the enrichment, so only one someone made in Sonora takes it: one created through the API has no owner."
          }
        },
        "additionalProperties": false,
        "description": "The settings to change. A field you leave out is kept, and a value equal to the current one is not a change."
      },
      "UpdateIssueRequest": {
        "type": "object",
        "properties": {
          "status": {
            "type": "string",
            "enum": [
              "open",
              "resolved"
            ],
            "description": "`resolved` to mark the issue resolved; `open` to reopen it."
          }
        },
        "required": [
          "status"
        ],
        "additionalProperties": false,
        "description": "The status to set. Only `open` and `resolved`: the statuses a person can set in Sonora."
      },
      "UpdateNoteRequest": {
        "type": "object",
        "properties": {
          "title": {
            "type": "string",
            "minLength": 1,
            "maxLength": 255,
            "description": "Shown as the note's heading in Sonora."
          },
          "contentMarkdown": {
            "description": "Replaces the whole body. To change part of it, read the note, edit its `contentMarkdown` and send it back.",
            "type": "string",
            "maxLength": 50000
          }
        },
        "additionalProperties": false,
        "description": "What to change. A field you leave out is kept."
      }
    }
  }
}
