{
  "openapi": "3.1.0",
  "info": {
    "title": "SMB Sales Boost API",
    "version": "1.0.0",
    "description": "REST API for SMB Sales Boost - a B2B lead generation platform that helps you find growing local businesses to reach out to: small and medium businesses across the United States showing growth signals such as new websites, new social profiles and fresh online activity (newly registered businesses are one subset). Search, filter, and export leads with comprehensive company data including contact information, location, and AI-enriched categories. API access requires a Starter, Growth, Scale, Platinum, or Enterprise subscription plan, including during the 14-day free trial. New users can purchase a subscription (or start a free trial) entirely via API with POST /purchase (no web signup required); the API key is emailed after checkout. A Model Context Protocol (MCP) server is also available at https://smbsalesboost.com/mcp for AI assistant integration - see https://smbsalesboost.com/docs/api/mcp-server for details. Every operation that uses an API key can also return 401 unauthorized (missing, invalid or revoked key), 403 forbidden (no active or trialing paid subscription), 429 (more than 600 requests per minute per key) and 503 service_unavailable (temporary; retry after the Retry-After delay). Integration endpoints (/integrations*) also share one budget per account of 60 requests per minute (connect: 30 per minute), and can return 503 provider_unavailable, which is not temporary (no Retry-After; do not retry).",
    "contact": {
      "name": "SMB Sales Boost Support",
      "email": "support@smbsalesboost.com",
      "url": "https://smbsalesboost.com/support"
    },
    "termsOfService": "https://smbsalesboost.com/terms-of-service",
    "license": {
      "name": "Proprietary"
    }
  },
  "servers": [
    {
      "url": "https://smbsalesboost.com/api/v1",
      "description": "Production"
    }
  ],
  "security": [
    {
      "BearerAuth": []
    }
  ],
  "tags": [
    {
      "name": "Programmatic Purchase",
      "description": "Start a subscription or free trial entirely via API (no web signup required). The API key is emailed after checkout."
    },
    {
      "name": "User Profile",
      "description": "View and update your account profile"
    },
    {
      "name": "Leads",
      "description": "Search, filter, and export leads from the database"
    },
    {
      "name": "Filter Presets",
      "description": "Save and manage reusable filter configurations"
    },
    {
      "name": "Keyword Lists",
      "description": "Manage keyword lists for targeted lead searching"
    },
    {
      "name": "Email Schedules",
      "description": "Automated email delivery of leads matching your filters"
    },
    {
      "name": "Export Formats",
      "description": "Customize export column layouts and file settings"
    },
    {
      "name": "Export History",
      "description": "View and re-download past exports"
    },
    {
      "name": "Lead Export History",
      "description": "Lead-level export tracking: view, re-export, refresh snapshots, and bulk-select all exports"
    },
    {
      "name": "Account Settings",
      "description": "Lead database settings and account configuration"
    },
    {
      "name": "AI Features",
      "description": "AI-powered category suggestions and keyword generation"
    },
    {
      "name": "Credits & Subscription",
      "description": "Purchase additional credits and manage your subscription"
    },
    {
      "name": "Export Blacklist",
      "description": "Manage leads excluded from future exports"
    },
    {
      "name": "Integrations",
      "description": "Send lead data to HubSpot, Salesforce, Pipedrive, Zapier, n8n, Make, Pipedream, Clay, or any custom webhook. Integrations send business contact details to outside systems automatically on every subscribed event. When an API key creates or re-points an integration, the account owner is emailed for each change. During bursts of more than 10 changes in an hour, further changes are combined into summary emails. Every integration endpoint shares one rate-limit budget per account (60 requests per minute; connect: 30 per minute)."
    },
    {
      "name": "Enrichments",
      "description": "Enrich a list of website URLs. Each URL is checked against the SMB Sales Boost database first; anything not already there is fetched live. We never add the URLs you submit, or the results we find for them, to our lead database, which we keep curated so it stays clean and focused on high-quality leads.\n\n**Pricing.** 1 credit for a URL in the database you have not bought before (this includes its buying signals and category enrichments). 0.1 credit for a URL fetched live. 0 credits for a URL you have already exported, one we got no data for, and one without the contact details you asked for.\n\n**A URL found in the database is never fetched live**, so lowering `databaseMatchPercent` does not let you buy database records at the live price.\n\n**Runs are asynchronous.** Submitting returns a run id immediately; a large list can take hours. Poll `GET /enrichments/{id}` until `status` is `completed` or `partial`, then call `/download`.\n\n**Credits are charged in two stages.** Database matches are charged as soon as they are matched; live results are charged when the file is published. `credits.final` tells you whether the total is settled."
    },
    {
      "name": "Background Jobs",
      "description": "Long-running requests. Every request reaches the API through an edge network that gives the server about 100 seconds to start answering, so work that can take longer runs as a background job: POST /leads/export, POST /email-schedules/{id}/trigger, POST /ai/suggest-categories and POST /lead-export-history/refresh, /refresh-and-export and /re-export. Such a request first checks its input and answers a 4xx at once, exactly as before. Otherwise it answers exactly like before when the work finishes within about 75 seconds (or within the wait you ask for with the Prefer header, counted from when the request arrived), plus Cache-Control: no-store and X-SMBSB-Job-Id; if not, it answers 202 Accepted with a jobId, a Location header, a statusUrl and X-SMBSB-Job-Status: running. Either way the answer starts within about 90 seconds of the request's arrival. Poll GET /jobs/{jobId} with any API key of the same account until it no longer answers 202: it then returns exactly what the original request would have returned (same status, headers and body, an error included).\n\n**Idempotency-Key.** Every one of these requests accepts the header (8-64 letters, numbers, underscores or dashes); send one with each export and schedule trigger. Repeating a request with the same key never runs it again: it returns the running job, its stored answer, or, once that answer is no longer kept, 410 job_result_expired. The key stays with the job as long as the server keeps any record of it: up to about 25 hours after the job finished (about 48 hours for a schedule trigger); a server restart forgets every key. An answer of 409, 429 or 503, and a 402 insufficient_credits from POST /leads/export, does not use up the key: nothing happened, so a retry with the same key runs the request. Every other answer, an error included, comes back again for the same key, so after an error answer (for example 400 export_too_large) or when you change any argument, use a new key. The same key with a different request answers 409 idempotency_key_reused; keys are separate per endpoint. Without a key, an identical request (same account, same input) returns the job that is still running; once it has finished, a new identical request runs again.\n\n**Limits.** Each account can run up to 3 exports, 5 schedule triggers, 3 category suggestions and 2 of each Lead Export History request at once; more answer 429 job_capacity_busy with Retry-After: 30. The request still counts toward its endpoint's rate limit (a retry that returns a running job too); polls count only toward the general limit of 600 requests per minute per key.\n\n**Retention.** A finished job's answer is normally kept for 60 minutes (24 hours for a schedule trigger). It can be dropped sooner under heavy load, and each account keeps at most 500 finished jobs (the oldest go first). After that, GET /jobs/{jobId} answers 410 job_result_expired for about a day, with a message that says where to look; then the job is forgotten and the answer is 404 job_not_found. Never repeat an export or a schedule trigger just because its answer is gone: check what it already did first. Exports: the files are in GET /export-history (kept 90 days) and the leads in GET /lead-export-history; a re-run with maxCredits 0 (without excludePurchased) returns leads you already paid for at no charge, while a normal re-run picks new leads first and charges for them. Schedule sends: lastSent and totalSentCount in GET /email-schedules. A server restart interrupts running jobs and forgets kept answers and keys (GET /jobs/{jobId} then answers 404 job_not_found); what a job already saved stays saved, and leads you already received are never charged twice."
    }
  ],
  "paths": {
    "/integrations/providers": {
      "get": {
        "tags": [
          "Integrations"
        ],
        "operationId": "listIntegrationProviders",
        "summary": "List integration providers",
        "description": "Returns the provider catalog with runtime availability. The webhook providers (Zapier, n8n, Make, Pipedream, Clay, Generic Webhook) are always available. HubSpot, Salesforce and Pipedrive are available when they are configured for this deployment; otherwise available is false and creating or connecting them returns 503 provider_unavailable. HubSpot, Salesforce and Pipedrive integrations take only lead.created and lead.updated; a subscription to any other event returns 400 validation_error.",
        "responses": {
          "200": {
            "description": "Provider catalog",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/IntegrationProvider"
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "description": "Rate limit exceeded. Every integration endpoint shares one budget per account of 60 requests per minute (the dashboard's Integrations tab and every API key of the account count against the same budget), on top of the usual per-key API limit. Wait for the Retry-After delay (also in the retryAfter body field), then retry.",
            "headers": {
              "Retry-After": {
                "schema": {
                  "type": "integer"
                },
                "description": "Seconds to wait before retrying"
              },
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests allowed in the window"
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left in the window (0 on a 429)"
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "string",
                  "format": "date-time"
                },
                "description": "When the window resets"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RateLimitError"
                }
              }
            }
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        }
      }
    },
    "/integrations": {
      "get": {
        "tags": [
          "Integrations"
        ],
        "operationId": "listIntegrations",
        "summary": "List your integrations",
        "description": "Returns every integration on your account, newest first, with its event subscriptions and who created or last changed it.",
        "responses": {
          "200": {
            "description": "Your integrations",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Integration"
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "description": "Rate limit exceeded. Every integration endpoint shares one budget per account of 60 requests per minute (the dashboard's Integrations tab and every API key of the account count against the same budget), on top of the usual per-key API limit. Wait for the Retry-After delay (also in the retryAfter body field), then retry.",
            "headers": {
              "Retry-After": {
                "schema": {
                  "type": "integer"
                },
                "description": "Seconds to wait before retrying"
              },
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests allowed in the window"
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left in the window (0 on a 429)"
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "string",
                  "format": "date-time"
                },
                "description": "When the window resets"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RateLimitError"
                }
              }
            }
          },
          "500": {
            "description": "internal_error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        }
      }
    },
    "/integrations/webhook": {
      "post": {
        "tags": [
          "Integrations"
        ],
        "operationId": "createWebhookIntegration",
        "summary": "Create a webhook integration",
        "description": "Creates a webhook destination for one of the webhook providers (zapier, n8n, make, pipedream, clay, generic_webhook). From then on SMB Sales Boost sends lead data (business contact details) to targetUrl on every subscribed event, automatically, until the integration is disabled or deleted. Confirm the destination with the account owner first. The response includes signingSecret, used to verify the X-SMB-Signature header on every delivery; it is shown only once, so hand it to the user and do not log it. If the request times out, the integration may have been created: check GET /integrations first. Repeating the same provider and targetUrl returns 409 integration_exists; a different provider or a changed URL (even a trailing slash) creates a second integration with its own secret. The signing secret cannot be shown again, so if the integration exists but you did not get its signingSecret, delete it (DELETE /integrations/{id}) and create it again. The integration and its subscriptions are written together (all or nothing), and once it is written the response always carries the signingSecret; a 503 service_unavailable means nothing was created. Limits: targetUrl must be a public https URL; at most 25 integrations per account. Safeguard: when an API key (REST or MCP) creates a webhook, connects a CRM, changes where an integration sends data, adds or re-activates an event, or re-enables an integration, SMB Sales Boost records the key on the integration (createdVia / lastChangedVia, shown in the dashboard as 'Created via API key smbk_...') and emails the account owner. The account owner is emailed for each change. During bursts of more than 10 changes in an hour, further changes are combined into summary emails.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "provider",
                  "targetUrl",
                  "events"
                ],
                "properties": {
                  "provider": {
                    "type": "string",
                    "enum": [
                      "zapier",
                      "n8n",
                      "make",
                      "pipedream",
                      "clay",
                      "generic_webhook"
                    ]
                  },
                  "targetUrl": {
                    "type": "string",
                    "format": "uri",
                    "maxLength": 2048,
                    "description": "Public https URL that receives the signed JSON POSTs"
                  },
                  "events": {
                    "type": "array",
                    "minItems": 1,
                    "items": {
                      "type": "string",
                      "enum": [
                        "lead.created",
                        "lead.updated",
                        "export.completed",
                        "email_schedule.sent",
                        "credit.low_balance"
                      ]
                    },
                    "description": "Events to send (duplicates are ignored)"
                  },
                  "displayName": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 100
                  },
                  "apiKey": {
                    "type": "string",
                    "maxLength": 500,
                    "description": "Optional shared secret sent as the X-SMB-API-Key header on every delivery (for example a Clay table key). Stored encrypted, never returned, and only sent to the active destination URLs it is bound to, never to a different host. It is bound to targetUrl when the integration is created; an edit in the dashboard binds it again to the integration's active destinations on the same origin. A URL on a switched-off (inactive) subscription is never bound, so switching that subscription on later does not send the key there until the owner edits the integration in the dashboard or apiKey is sent again. When an API key (REST or MCP) later points the integration at any new destination URL (even on the same host, since services like Zapier, Make and Clay put every customer's hooks on one host), that request must send apiKey again (a new value, or null to stop sending it)."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Created",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "integration": {
                          "$ref": "#/components/schemas/Integration"
                        },
                        "signingSecret": {
                          "type": "string",
                          "description": "Webhook signing secret. Shown only once: give it to the user and do not log it."
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "validation_error (details lists the problems) or bad_request (the targetUrl is not a safe public https URL).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "409": {
            "description": "integration_exists: you already have this provider sending to this URL (check GET /integrations), or integration_limit: the account already has 25 integrations.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded. Every integration endpoint shares one budget per account of 60 requests per minute (the dashboard's Integrations tab and every API key of the account count against the same budget), on top of the usual per-key API limit. Wait for the Retry-After delay (also in the retryAfter body field), then retry.",
            "headers": {
              "Retry-After": {
                "schema": {
                  "type": "integer"
                },
                "description": "Seconds to wait before retrying"
              },
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests allowed in the window"
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left in the window (0 on a 429)"
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "string",
                  "format": "date-time"
                },
                "description": "When the window resets"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RateLimitError"
                }
              }
            }
          },
          "500": {
            "description": "internal_error. Check GET /integrations before repeating the request.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "provider_unavailable: this provider is not available on this deployment yet (message '<Provider> is not available yet.'; not temporary, no Retry-After header, do not retry; GET /integrations/providers shows availability), or service_unavailable: a temporary failure, retry after the Retry-After delay.",
            "headers": {
              "Retry-After": {
                "schema": {
                  "type": "integer"
                },
                "description": "Seconds to wait before retrying (service_unavailable only)"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/integrations/{id}": {
      "get": {
        "tags": [
          "Integrations"
        ],
        "operationId": "getIntegration",
        "summary": "Get an integration",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Integration ID (from GET /integrations)",
            "schema": {
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Integration",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/Integration"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "bad_request: invalid id",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "description": "not_found: no integration with this id belongs to your account. Use GET /integrations to list your integrations.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded. Every integration endpoint shares one budget per account of 60 requests per minute (the dashboard's Integrations tab and every API key of the account count against the same budget), on top of the usual per-key API limit. Wait for the Retry-After delay (also in the retryAfter body field), then retry.",
            "headers": {
              "Retry-After": {
                "schema": {
                  "type": "integer"
                },
                "description": "Seconds to wait before retrying"
              },
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests allowed in the window"
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left in the window (0 on a 429)"
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "string",
                  "format": "date-time"
                },
                "description": "When the window resets"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RateLimitError"
                }
              }
            }
          },
          "500": {
            "description": "internal_error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        }
      },
      "patch": {
        "tags": [
          "Integrations"
        ],
        "operationId": "updateIntegration",
        "summary": "Update an integration",
        "description": "Updates the display name, status, event subscriptions, destination URL, header API key or Pipedrive deal settings. Sending subscriptions replaces the whole subscription set. HubSpot, Salesforce and Pipedrive integrations take only lead.created and lead.updated; a subscription to any other event (active or not) returns 400 validation_error ('<Provider> integrations accept only lead.created and lead.updated.', with one details entry per refused subscription) and nothing is saved. A webhook subscription sent without targetUrl uses defaultTargetUrl when it is sent; otherwise it keeps that event's current URL, or takes the integration's first URL for a new event. defaultTargetUrl sent without subscriptions re-points every existing subscription (inactive ones too). Set status to disabled to pause all sending, or to connected to resume it (from disabled or needs_attention; this counts as re-enabling). needs_attention is set only by SMB Sales Boost: sending it returns 400 validation_error (unless the integration is already needs_attention, which is a no-op). Every URL must be a public https URL. If the integration sends a header API key (X-SMB-API-Key) and the request points any subscription at a destination URL the integration did not already use (any new URL, even on the same host, since services like Zapier, Make and Clay put every customer's hooks on one host), send apiKey in the same request (a new value, or null to stop sending it); otherwise the request is rejected with 400 and nothing changes. apiKey itself applies only to Clay integrations and to webhook integrations that already send a header API key (400 bad_request otherwise, and on a CRM); null or an empty string stops sending it. Changing a destination (any new targetUrl, on an active or inactive subscription), adding or re-activating an event, or re-enabling the integration changes where lead data goes, so confirm it with the account owner first. Safeguard: when an API key (REST or MCP) creates a webhook, connects a CRM, changes where an integration sends data, adds or re-activates an event, or re-enables an integration, SMB Sales Boost records the key on the integration (createdVia / lastChangedVia, shown in the dashboard as 'Created via API key smbk_...') and emails the account owner. A dashboard change replaces that record only when it adds a destination URL the integration never had; pausing an integration (also the pause offered when an API key is revoked), and resuming it, switching on more events or switching on a URL it already had from the dashboard, keep it, so an integration an API key re-pointed still shows the key. The account owner is emailed for each change. During bursts of more than 10 changes in an hour, further changes are combined into summary emails. If the integration changes while the update is being applied (for example the owner pauses it at the same moment), the update reads it again and is applied once more on top of that change (every check runs again on the new state); only if it changes again does the request return 409 conflict ('The integration changed while this update was being applied. Read it again and retry.'), and then nothing changes: read the integration again (GET /integrations/{id}) and retry. If the API key making the request is revoked in that moment, the request returns 401 unauthorized ('This API key has been revoked') and nothing is applied.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Integration ID (from GET /integrations)",
            "schema": {
              "type": "integer"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "displayName": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 100
                  },
                  "status": {
                    "type": "string",
                    "enum": [
                      "connected",
                      "disabled"
                    ],
                    "description": "disabled pauses all sending; connected resumes it (re-enables a disabled or needs_attention integration). needs_attention is system-only: sending it returns 400 validation_error, unless the integration already is needs_attention (then it is a harmless no-op)."
                  },
                  "subscriptions": {
                    "type": "array",
                    "maxItems": 20,
                    "items": {
                      "type": "object",
                      "required": [
                        "eventType"
                      ],
                      "properties": {
                        "eventType": {
                          "type": "string",
                          "enum": [
                            "lead.created",
                            "lead.updated",
                            "export.completed",
                            "email_schedule.sent",
                            "credit.low_balance"
                          ]
                        },
                        "targetUrl": {
                          "type": "string",
                          "format": "uri",
                          "maxLength": 2048,
                          "nullable": true,
                          "description": "Webhook integrations. Omit to use defaultTargetUrl, or else this event's current URL (the integration's first URL for a new event)."
                        },
                        "isActive": {
                          "type": "boolean",
                          "description": "false keeps the subscription, but live events never use it, and webhook tests and pushes do not either (a CRM test or push still goes to the connected CRM). POST /integrations/{id}/deliveries/{deliveryId}/retry still re-sends a past delivery to the URL it was first sent to. A new targetUrl still counts as a destination change (owner email) even on an inactive subscription."
                        },
                        "config": {
                          "type": "object",
                          "additionalProperties": true,
                          "nullable": true
                        }
                      }
                    },
                    "description": "Replaces the whole subscription set (max 20): an event left out stops being sent. HubSpot, Salesforce and Pipedrive integrations take only lead.created and lead.updated; a subscription to any other event returns 400 validation_error."
                  },
                  "defaultTargetUrl": {
                    "type": "string",
                    "format": "uri",
                    "maxLength": 2048,
                    "description": "Webhook integrations only (400 bad_request on a CRM). Sent without subscriptions, it re-points every existing subscription (inactive ones and per-event URLs too) to this URL. Sent with subscriptions, it is the URL for each subscription that has no targetUrl. A webhook subscription with neither keeps that event's current URL, or the integration's first URL for a new event. To change only some events, send subscriptions instead."
                  },
                  "apiKey": {
                    "type": "string",
                    "maxLength": 500,
                    "nullable": true,
                    "description": "Header API key sent as X-SMB-API-Key. Applies only to Clay integrations and to webhook integrations that already send a header API key; on a CRM it returns 400 bad_request ('apiKey only applies to webhook integrations'), on any other webhook 400 bad_request ('apiKey only applies to Clay integrations and to webhook integrations that already send a header API key'). A new value replaces the key; null or an empty string stops sending it. The key is only sent to the active destination URLs it is bound to, never to a different host: a new value is bound to the integration's active destinations on the origin of its destination, and an edit in the dashboard binds it again the same way. A URL on a switched-off (inactive) subscription is never bound, so switching that subscription on later (which needs no apiKey, because the URL is not new) does not send the key there until the owner edits the integration in the dashboard or apiKey is sent again. When an API key (REST or MCP) points an integration that has a header API key at a new destination URL (any URL on none of its subscriptions yet, even on the same host), apiKey is required in the same request (a new value, or null to stop sending it); otherwise 400 bad_request ('This integration sends a header API key. Pointing it at a new destination URL requires apiKey in the same request (a new value, or null to stop sending it).'). Stored encrypted and never returned."
                  },
                  "pipedriveDeals": {
                    "type": "object",
                    "description": "Pipedrive integrations only: create a Deal for each pushed lead",
                    "properties": {
                      "enabled": {
                        "type": "boolean"
                      },
                      "pipelineId": {
                        "type": "integer",
                        "minimum": 1
                      },
                      "stageId": {
                        "type": "integer",
                        "minimum": 1
                      },
                      "defaultValue": {
                        "type": "number",
                        "minimum": 0
                      },
                      "defaultCurrency": {
                        "type": "string",
                        "minLength": 3,
                        "maxLength": 8
                      },
                      "expectedCloseDays": {
                        "type": "integer",
                        "minimum": 0,
                        "maximum": 3650
                      },
                      "fieldMap": {
                        "type": "object",
                        "additionalProperties": {
                          "type": "string",
                          "minLength": 1,
                          "maxLength": 100
                        }
                      }
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Updated",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/Integration"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "validation_error (details lists the problems, including status needs_attention on an integration that is not already needs_attention, and, on a HubSpot, Salesforce or Pipedrive integration, a subscription to an event other than lead.created or lead.updated: '<Provider> integrations accept only lead.created and lead.updated.') or bad_request (invalid id, unsafe URL, defaultTargetUrl on a CRM integration, 'apiKey only applies to webhook integrations' (apiKey on a CRM), 'apiKey only applies to Clay integrations and to webhook integrations that already send a header API key', pipedriveDeals on a non-Pipedrive integration, deals enabled without a pipeline and stage, or 'This integration sends a header API key. Pointing it at a new destination URL requires apiKey in the same request (a new value, or null to stop sending it).').",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Missing, invalid or revoked API key. Also returned ('This API key has been revoked') when the API key making the request is revoked while the update is being applied; nothing is applied.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "unauthorized",
                  "message": "This API key has been revoked"
                }
              }
            }
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "description": "not_found: no integration with this id belongs to your account. Use GET /integrations to list your integrations.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "integration_exists: the new destination is already used by another integration of the same provider on your account; nothing changed. conflict ('The integration changed while this update was being applied. Read it again and retry.'): the integration or its subscriptions changed twice while this update was being applied (a single change, for example the owner pausing it at the same moment, is absorbed: the update is applied again on top of it); nothing changed. Read it again (GET /integrations/{id}), check the change still makes sense, and retry.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded. Every integration endpoint shares one budget per account of 60 requests per minute (the dashboard's Integrations tab and every API key of the account count against the same budget), on top of the usual per-key API limit. Wait for the Retry-After delay (also in the retryAfter body field), then retry.",
            "headers": {
              "Retry-After": {
                "schema": {
                  "type": "integer"
                },
                "description": "Seconds to wait before retrying"
              },
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests allowed in the window"
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left in the window (0 on a 429)"
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "string",
                  "format": "date-time"
                },
                "description": "When the window resets"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RateLimitError"
                }
              }
            }
          },
          "500": {
            "description": "internal_error. The change may already have been saved: check GET /integrations/{id} before repeating it. (A 503 service_unavailable is only returned before anything was written.)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        }
      },
      "delete": {
        "tags": [
          "Integrations"
        ],
        "operationId": "deleteIntegration",
        "summary": "Delete an integration",
        "description": "Deletes the integration with its subscriptions and delivery history. Sending stops immediately.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Integration ID (from GET /integrations)",
            "schema": {
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "$ref": "#/components/responses/DeleteSuccess"
          },
          "400": {
            "description": "bad_request: invalid id",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "description": "not_found: no integration with this id belongs to your account. Use GET /integrations to list your integrations.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded. Every integration endpoint shares one budget per account of 60 requests per minute (the dashboard's Integrations tab and every API key of the account count against the same budget), on top of the usual per-key API limit. Wait for the Retry-After delay (also in the retryAfter body field), then retry.",
            "headers": {
              "Retry-After": {
                "schema": {
                  "type": "integer"
                },
                "description": "Seconds to wait before retrying"
              },
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests allowed in the window"
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left in the window (0 on a 429)"
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "string",
                  "format": "date-time"
                },
                "description": "When the window resets"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RateLimitError"
                }
              }
            }
          },
          "500": {
            "description": "internal_error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        }
      }
    },
    "/integrations/{id}/deliveries": {
      "get": {
        "tags": [
          "Integrations"
        ],
        "operationId": "listIntegrationDeliveries",
        "summary": "List recent deliveries",
        "description": "Returns the newest 50 delivery attempts for the integration. Live events, pushes, tests and retries are all recorded. 5 failed deliveries in a row set the integration to needs_attention (live events to it stop and pushes are refused until it is re-enabled). One failed delivery is, for example, a live webhook event whose 5 attempts all failed, a failed CRM write, a failed push, a retry that could not reach the destination, or a test the destination rejected.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Integration ID (from GET /integrations)",
            "schema": {
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Deliveries",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/IntegrationDelivery"
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "bad_request: invalid id",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "description": "not_found: no integration with this id belongs to your account. Use GET /integrations to list your integrations.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded. Every integration endpoint shares one budget per account of 60 requests per minute (the dashboard's Integrations tab and every API key of the account count against the same budget), on top of the usual per-key API limit. Wait for the Retry-After delay (also in the retryAfter body field), then retry.",
            "headers": {
              "Retry-After": {
                "schema": {
                  "type": "integer"
                },
                "description": "Seconds to wait before retrying"
              },
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests allowed in the window"
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left in the window (0 on a 429)"
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "string",
                  "format": "date-time"
                },
                "description": "When the window resets"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RateLimitError"
                }
              }
            }
          },
          "500": {
            "description": "internal_error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        }
      }
    },
    "/integrations/{id}/deliveries/{deliveryId}/retry": {
      "post": {
        "tags": [
          "Integrations"
        ],
        "operationId": "retryIntegrationDelivery",
        "summary": "Retry a delivery",
        "description": "Sends the stored payload of a past delivery again, to the URL it was first sent to (webhooks; even if that subscription is now inactive or points elsewhere) or to the connected CRM. A webhook gets the same event data again in a new signed envelope (a new event id and occurredAt). Lead data leaves SMB Sales Boost again, so confirm with the account owner first. No credits are charged. A paused integration (status disabled) must be re-enabled first (PATCH status connected). If the retry succeeds on a needs_attention integration, the integration returns to connected; that counts as re-enabling it (recorded in lastChangedVia, owner emailed). A CRM (HubSpot, Salesforce, Pipedrive) re-sends only lead.created and lead.updated deliveries: an older delivery of any other event returns 200 with status failed ('CRM integrations receive only lead.created and lead.updated, so this <eventType> delivery cannot be retried.') and nothing is sent or recorded. Otherwise the retry is recorded in GET /integrations/{id}/deliveries, and a failed retry (also one that could not reach the destination, such as a timeout or an unreachable host) counts toward needs_attention like any other delivery. A failed attempt returns 200 with status failed.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Integration ID (from GET /integrations)",
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "deliveryId",
            "in": "path",
            "required": true,
            "description": "Delivery ID (from GET /integrations/{id}/deliveries)",
            "schema": {
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Retry result",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/IntegrationDeliveryResult"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "bad_request: invalid id or deliveryId, or bad_request 'Integration is paused. Re-enable it first (PATCH status connected).' when the integration's status is disabled.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "description": "not_found: the integration or the delivery does not exist on your account",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded. Every integration endpoint shares one budget per account of 60 requests per minute (the dashboard's Integrations tab and every API key of the account count against the same budget), on top of the usual per-key API limit. Wait for the Retry-After delay (also in the retryAfter body field), then retry.",
            "headers": {
              "Retry-After": {
                "schema": {
                  "type": "integer"
                },
                "description": "Seconds to wait before retrying"
              },
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests allowed in the window"
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left in the window (0 on a 429)"
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "string",
                  "format": "date-time"
                },
                "description": "When the window resets"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RateLimitError"
                }
              }
            }
          },
          "500": {
            "description": "internal_error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "provider_unavailable: this provider is not available on this deployment yet (message '<Provider> is not available yet.'; not temporary, no Retry-After header, do not retry; GET /integrations/providers shows availability), or service_unavailable: a temporary failure, retry after the Retry-After delay.",
            "headers": {
              "Retry-After": {
                "schema": {
                  "type": "integer"
                },
                "description": "Seconds to wait before retrying (service_unavailable only)"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/integrations/{id}/test": {
      "post": {
        "tags": [
          "Integrations"
        ],
        "operationId": "testIntegration",
        "summary": "Send a test event",
        "description": "Sends a synthetic signed lead.created event (a sample business named Acme Test Co) to the real destination. For a CRM this creates a real test record in the CRM that is not removed afterwards (HubSpot: the sample has no email, so it creates no Contact, only an Acme Test Co Company matched by the website domain example.com). Confirm with the account owner first. Webhook test events and pushes only ever use an active subscription: the active one for the event, else any active subscription with a targetUrl; with none, the attempt fails with 'No active subscription for this integration'. A paused integration (status disabled) must be re-enabled first (PATCH status connected). If the test succeeds on a needs_attention integration, the integration returns to connected; that counts as re-enabling it (recorded in lastChangedVia, owner emailed). A failed attempt returns 200 with status failed.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Integration ID (from GET /integrations)",
            "schema": {
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Test result",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/IntegrationDeliveryResult"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "bad_request: invalid id, or bad_request 'Integration is paused. Re-enable it first (PATCH status connected).' when the integration's status is disabled.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "description": "not_found: no integration with this id belongs to your account. Use GET /integrations to list your integrations.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded. Every integration endpoint shares one budget per account of 60 requests per minute (the dashboard's Integrations tab and every API key of the account count against the same budget), on top of the usual per-key API limit. Wait for the Retry-After delay (also in the retryAfter body field), then retry.",
            "headers": {
              "Retry-After": {
                "schema": {
                  "type": "integer"
                },
                "description": "Seconds to wait before retrying"
              },
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests allowed in the window"
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left in the window (0 on a 429)"
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "string",
                  "format": "date-time"
                },
                "description": "When the window resets"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RateLimitError"
                }
              }
            }
          },
          "500": {
            "description": "internal_error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "provider_unavailable: this provider is not available on this deployment yet (message '<Provider> is not available yet.'; not temporary, no Retry-After header, do not retry; GET /integrations/providers shows availability), or service_unavailable: a temporary failure, retry after the Retry-After delay.",
            "headers": {
              "Retry-After": {
                "schema": {
                  "type": "integer"
                },
                "description": "Seconds to wait before retrying (service_unavailable only)"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/integrations/{id}/push-lead": {
      "post": {
        "tags": [
          "Integrations"
        ],
        "operationId": "pushLeadToIntegration",
        "summary": "Push one lead to an integration",
        "description": "Sends one lead to the integration now. Send leadId to push a lead from the SMB Sales Boost database: it is tracked like an export (it appears in lead export history with method crm_push), costs 1 credit when the lead is new to you and nothing when you already received it (a lead you already own is pushed free even at a 0 balance; a new lead without credits returns 402), respects your export blacklist, and only the fields an export includes are sent. Send lead to push your own data instead: untracked and free. The integration must be connected (status connected). Confirm with the account owner first: the lead's business contact details leave SMB Sales Boost. The target integration receives the lead once, from this push only: it is left out of the background lead.created / lead.updated events the push triggers. Your OTHER integrations still get those events: a tracked lead you had not received before counts as received and sends lead.created to your other integrations subscribed to lead.created, and re-pushing a lead you already received sends lead.updated to your other integrations subscribed to lead.updated only when the lead changed since you last received it (a newer Last Buying Signal). A leadId push to a webhook sends the same flat lead summary as a live lead.created event, with source crm_push. A push of your own data sends {\"lead\": <your object as sent>, \"source\": \"manual_push\"}. Webhook test events and pushes only ever use an active subscription: the active one for the event, else any active subscription with a targetUrl; with none, the attempt fails with 'No active subscription for this integration'. Every push is recorded in the integration's deliveries (GET /integrations/{id}/deliveries) and counts toward needs_attention like any other delivery: after 5 failed deliveries in a row the integration is needs_attention, and push-lead returns 400 until it is re-enabled. A push refused before anything is sent (for example 'No active subscription for this integration', answered as 200 with status failed, tracked false and creditsUsed 0) is not charged, not added to your export history, triggers no events and is not recorded. After a timeout, check the deliveries (and, for a CRM, the CRM itself) before pushing again.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Integration ID (from GET /integrations)",
            "schema": {
              "type": "integer"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "description": "Exactly one of leadId or lead",
                "properties": {
                  "leadId": {
                    "type": "integer",
                    "description": "Lead ID from GET /leads or GET /leads/preview (tracked; 1 credit if new to you)"
                  },
                  "lead": {
                    "type": "object",
                    "additionalProperties": true,
                    "description": "Your own lead data (untracked, free)"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Push result",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "status": {
                          "type": "string",
                          "enum": [
                            "success",
                            "failed"
                          ]
                        },
                        "tracked": {
                          "type": "boolean",
                          "description": "true when leadId was pushed and recorded in your export history; false for your own lead data, and for a push refused before anything was sent (for example 'No active subscription for this integration')"
                        },
                        "creditsUsed": {
                          "type": "integer",
                          "description": "Credits charged (0 or 1). Always 0 when the push was refused before anything was sent (for example 'No active subscription for this integration')."
                        },
                        "result": {
                          "type": "object",
                          "additionalProperties": true,
                          "description": "Provider result, for example the CRM record ids"
                        },
                        "error": {
                          "type": "string",
                          "description": "Why the push failed (when status is failed)"
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "validation_error or bad_request (invalid id, neither leadId nor lead, or the integration is not connected, for example 'Integration is not connected (status: needs_attention). Re-enable it with PATCH status \"connected\" first.').",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "description": "insufficient_credits: the lead is new to you and your balance cannot cover 1 credit. Nothing was pushed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/InsufficientCreditsError"
                }
              }
            }
          },
          "403": {
            "description": "forbidden: no active or trialing paid subscription, or (plans without credits only) the push would exceed your plan's export limits.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found: the integration or the lead does not exist",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded. Every integration endpoint shares one budget per account of 60 requests per minute (the dashboard's Integrations tab and every API key of the account count against the same budget), on top of the usual per-key API limit. Wait for the Retry-After delay (also in the retryAfter body field), then retry.",
            "headers": {
              "Retry-After": {
                "schema": {
                  "type": "integer"
                },
                "description": "Seconds to wait before retrying"
              },
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests allowed in the window"
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left in the window (0 on a 429)"
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "string",
                  "format": "date-time"
                },
                "description": "When the window resets"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RateLimitError"
                }
              }
            }
          },
          "500": {
            "description": "internal_error. Not retryable. If it happens while charging ('Push failed while charging. A credit may have been taken; check GET /me and your export history before pushing this lead again.'), 1 credit may have been taken without a push: check GET /me, your export history and GET /integrations/{id}/deliveries before repeating.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "provider_unavailable: this provider is not available on this deployment yet (not temporary, no Retry-After, do not retry), or service_unavailable: a temporary failure raised only before any charge or push; safe to retry after the Retry-After delay.",
            "headers": {
              "Retry-After": {
                "schema": {
                  "type": "integer"
                },
                "description": "Seconds to wait before retrying (service_unavailable only)"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/integrations/{provider}/connect": {
      "post": {
        "tags": [
          "Integrations"
        ],
        "operationId": "connectCrmIntegration",
        "summary": "Start connecting a CRM",
        "description": "Starts the OAuth connection for HubSpot, Salesforce or Pipedrive and returns authorizationUrl. Nothing is connected yet: the user must open authorizationUrl in a browser and approve access within 10 minutes. The CRM integration then appears in GET /integrations and starts receiving lead data on its initial events (HubSpot: lead.created on, lead.updated present but off - turn it on with PATCH /integrations/{id}; Salesforce: lead.created; Pipedrive: lead.created and lead.updated). GET /integrations shows the real isActive of each event. Deliveries to HubSpot, Salesforce and Pipedrive are sent at most 3 at a time per integration, and HubSpot calls are paced to HubSpot's rate limits, so a large export reaches the CRM gradually. Connecting a CRM account that is already connected re-authorizes that integration in place: it gets new access, a needs_attention integration becomes connected again, a paused one stays paused, and its events, field mapping, Pipedrive deal settings and delivery history are kept. The 25-integration limit applies only to new connections. Use this endpoint to reconnect a CRM integration that lost access (to reconnect a Salesforce sandbox integration, send environment sandbox); signing in to a different account of the same CRM creates a new integration. A reconnect started with an API key that turns a needs_attention integration back to connected counts as re-enabling it (recorded in lastChangedVia, owner emailed); a reconnect that only refreshes access sends no notice. If HubSpot does not confirm which HubSpot account the user signed in to, nothing is connected or changed and the browser lands on the dashboard with hubspot_error carrying the message 'We could not confirm which HubSpot account you signed in to, so nothing was connected or changed. Please try connecting again in a minute.' Only share the URL with the account owner: whoever approves it attaches their CRM to this account. If the API key that started the connection is revoked before the user approves, nothing is connected and the browser lands on the dashboard with <provider>_error=key_revoked. The CRMs are available when they are configured for this deployment; otherwise this returns 503 provider_unavailable. Any other provider in the path returns 404 not_found. Safeguard: when an API key (REST or MCP) creates a webhook, connects a CRM, changes where an integration sends data, adds or re-activates an event, or re-enables an integration, SMB Sales Boost records the key on the integration (createdVia / lastChangedVia, shown in the dashboard as 'Created via API key smbk_...') and emails the account owner. The account owner is emailed for each change. During bursts of more than 10 changes in an hour, further changes are combined into summary emails.",
        "parameters": [
          {
            "name": "provider",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "enum": [
                "hubspot",
                "salesforce",
                "pipedrive"
              ]
            }
          }
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "environment": {
                    "type": "string",
                    "enum": [
                      "production",
                      "sandbox"
                    ],
                    "default": "production",
                    "description": "Salesforce only: which org to connect (production by default). To reconnect a sandbox integration, send sandbox."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Authorization URL",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "provider": {
                          "type": "string",
                          "enum": [
                            "hubspot",
                            "salesforce",
                            "pipedrive"
                          ]
                        },
                        "authorizationUrl": {
                          "type": "string",
                          "format": "uri",
                          "description": "Open in a browser to approve the connection"
                        },
                        "expiresAt": {
                          "type": "string",
                          "format": "date-time",
                          "description": "The URL stops working after this time (10 minutes)"
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "bad_request: environment was sent for a provider other than Salesforce, or it is not production or sandbox.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "description": "not_found: the provider in the path is not hubspot, salesforce or pipedrive.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "integration_limit: the account already has 25 integrations and none of them is an integration of this CRM that a sign-in could re-authorize; delete one before connecting another. (With such an integration the connection starts: signing in to that same CRM account re-authorizes it, while a CRM account that is not connected yet is refused in the browser with <provider>_error=integration_limit and nothing is created.)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded. Connect requests are limited to 30 per minute per account (shared with the dashboard's connect buttons), and also count against the shared integrations budget of 60 requests per minute per account. Wait for the Retry-After delay (also in the retryAfter body field), then retry.",
            "headers": {
              "Retry-After": {
                "schema": {
                  "type": "integer"
                },
                "description": "Seconds to wait before retrying"
              },
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests allowed in the window"
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left in the window (0 on a 429)"
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "string",
                  "format": "date-time"
                },
                "description": "When the window resets"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RateLimitError"
                }
              }
            }
          },
          "500": {
            "description": "internal_error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "provider_unavailable: this provider is not available on this deployment yet (message '<Provider> is not available yet.'; not temporary, no Retry-After header, do not retry; GET /integrations/providers shows availability), or service_unavailable: a temporary failure, retry after the Retry-After delay.",
            "headers": {
              "Retry-After": {
                "schema": {
                  "type": "integer"
                },
                "description": "Seconds to wait before retrying (service_unavailable only)"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/integrations/{id}/field-mapping": {
      "get": {
        "tags": [
          "Integrations"
        ],
        "operationId": "getIntegrationFieldMapping",
        "summary": "Get the CRM field mapping",
        "description": "HubSpot, Salesforce and Pipedrive integrations only. Returns the SMB Sales Boost lead fields, the CRM's fields and the current mapping. Reads the CRM's field list (cached for 10 minutes).",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Integration ID (from GET /integrations)",
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "refresh",
            "in": "query",
            "required": false,
            "description": "true reloads the CRM's field list instead of using the cache",
            "schema": {
              "type": "boolean"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Field catalog and mapping",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/IntegrationFieldCatalog"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "validation_error (details lists the problems) or bad_request (invalid id, or the integration is not the provider this endpoint is for).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "description": "not_found: no integration with this id belongs to your account. Use GET /integrations to list your integrations.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded. Every integration endpoint shares one budget per account of 60 requests per minute (the dashboard's Integrations tab and every API key of the account count against the same budget), on top of the usual per-key API limit. Wait for the Retry-After delay (also in the retryAfter body field), then retry.",
            "headers": {
              "Retry-After": {
                "schema": {
                  "type": "integer"
                },
                "description": "Seconds to wait before retrying"
              },
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests allowed in the window"
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left in the window (0 on a 429)"
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "string",
                  "format": "date-time"
                },
                "description": "When the window resets"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RateLimitError"
                }
              }
            }
          },
          "500": {
            "description": "internal_error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "502": {
            "description": "provider_error: the CRM's API failed or refused the request (for example the connection was revoked in the CRM). Nothing was changed on SMB Sales Boost; retry later or reconnect the CRM.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "provider_unavailable: this provider is not available on this deployment yet (message '<Provider> is not available yet.'; not temporary, no Retry-After header, do not retry; GET /integrations/providers shows availability), or service_unavailable: a temporary failure, retry after the Retry-After delay.",
            "headers": {
              "Retry-After": {
                "schema": {
                  "type": "integer"
                },
                "description": "Seconds to wait before retrying (service_unavailable only)"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "patch": {
        "tags": [
          "Integrations"
        ],
        "operationId": "updateIntegrationFieldMapping",
        "summary": "Update the CRM field mapping",
        "description": "Sets which CRM field receives each SMB Sales Boost lead field. HubSpot takes contact and company, Salesforce takes lead, Pipedrive takes person and organization. The mapping is replaced as a whole: a scope of this CRM that you leave out is cleared. A scope that belongs to another CRM, or a body with no scope at all, returns 400 validation_error and changes nothing (use DELETE to reset).",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Integration ID (from GET /integrations)",
            "schema": {
              "type": "integer"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/IntegrationFieldMapping"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Saved mapping",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "mapping": {
                          "$ref": "#/components/schemas/IntegrationFieldMapping"
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "validation_error (details lists the problems): a scope that belongs to another CRM (for example 'Scope(s) lead do not apply to HubSpot integrations. HubSpot takes: contact, company.'), or no scope at all (send at least one scope; use DELETE /integrations/{id}/field-mapping to reset). Or bad_request (invalid id, or the integration is not a HubSpot, Salesforce or Pipedrive integration).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "description": "not_found: no integration with this id belongs to your account. Use GET /integrations to list your integrations.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded. Every integration endpoint shares one budget per account of 60 requests per minute (the dashboard's Integrations tab and every API key of the account count against the same budget), on top of the usual per-key API limit. Wait for the Retry-After delay (also in the retryAfter body field), then retry.",
            "headers": {
              "Retry-After": {
                "schema": {
                  "type": "integer"
                },
                "description": "Seconds to wait before retrying"
              },
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests allowed in the window"
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left in the window (0 on a 429)"
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "string",
                  "format": "date-time"
                },
                "description": "When the window resets"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RateLimitError"
                }
              }
            }
          },
          "500": {
            "description": "internal_error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "provider_unavailable: this provider is not available on this deployment yet (message '<Provider> is not available yet.'; not temporary, no Retry-After header, do not retry; GET /integrations/providers shows availability), or service_unavailable: a temporary failure, retry after the Retry-After delay.",
            "headers": {
              "Retry-After": {
                "schema": {
                  "type": "integer"
                },
                "description": "Seconds to wait before retrying (service_unavailable only)"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "delete": {
        "tags": [
          "Integrations"
        ],
        "operationId": "resetIntegrationFieldMapping",
        "summary": "Reset the CRM field mapping",
        "description": "Clears the custom mapping so the built-in defaults apply again.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Integration ID (from GET /integrations)",
            "schema": {
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Reset mapping",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "mapping": {
                          "$ref": "#/components/schemas/IntegrationFieldMapping"
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "validation_error (details lists the problems) or bad_request (invalid id, or the integration is not the provider this endpoint is for).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "description": "not_found: no integration with this id belongs to your account. Use GET /integrations to list your integrations.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded. Every integration endpoint shares one budget per account of 60 requests per minute (the dashboard's Integrations tab and every API key of the account count against the same budget), on top of the usual per-key API limit. Wait for the Retry-After delay (also in the retryAfter body field), then retry.",
            "headers": {
              "Retry-After": {
                "schema": {
                  "type": "integer"
                },
                "description": "Seconds to wait before retrying"
              },
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests allowed in the window"
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left in the window (0 on a 429)"
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "string",
                  "format": "date-time"
                },
                "description": "When the window resets"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RateLimitError"
                }
              }
            }
          },
          "500": {
            "description": "internal_error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "provider_unavailable: this provider is not available on this deployment yet (message '<Provider> is not available yet.'; not temporary, no Retry-After header, do not retry; GET /integrations/providers shows availability), or service_unavailable: a temporary failure, retry after the Retry-After delay.",
            "headers": {
              "Retry-After": {
                "schema": {
                  "type": "integer"
                },
                "description": "Seconds to wait before retrying (service_unavailable only)"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/integrations/{id}/field-mapping/test": {
      "post": {
        "tags": [
          "Integrations"
        ],
        "operationId": "testIntegrationFieldMapping",
        "summary": "Dry-run the CRM field mapping",
        "description": "Checks a mapping against the real CRM: creates synthetic test records with it, then deletes them. Send mapping to test an unsaved mapping; omit it to test the saved one. Confirm with the account owner first, because it writes to their CRM briefly.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Integration ID (from GET /integrations)",
            "schema": {
              "type": "integer"
            }
          }
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "mapping": {
                    "$ref": "#/components/schemas/IntegrationFieldMapping"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Validation result",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/MappingValidationResult"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "validation_error (details lists the problems; a scope that belongs to another CRM is rejected, for example 'Scope(s) lead do not apply to HubSpot integrations. HubSpot takes: contact, company.') or bad_request (invalid id, or the integration is not a HubSpot, Salesforce or Pipedrive integration).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "description": "not_found: no integration with this id belongs to your account. Use GET /integrations to list your integrations.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded. Every integration endpoint shares one budget per account of 60 requests per minute (the dashboard's Integrations tab and every API key of the account count against the same budget), on top of the usual per-key API limit. Wait for the Retry-After delay (also in the retryAfter body field), then retry.",
            "headers": {
              "Retry-After": {
                "schema": {
                  "type": "integer"
                },
                "description": "Seconds to wait before retrying"
              },
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests allowed in the window"
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left in the window (0 on a 429)"
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "string",
                  "format": "date-time"
                },
                "description": "When the window resets"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RateLimitError"
                }
              }
            }
          },
          "500": {
            "description": "internal_error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "502": {
            "description": "provider_error: the CRM's API failed or refused the request (for example the connection was revoked in the CRM). Nothing was changed on SMB Sales Boost; retry later or reconnect the CRM.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "provider_unavailable: this provider is not available on this deployment yet (message '<Provider> is not available yet.'; not temporary, no Retry-After header, do not retry; GET /integrations/providers shows availability), or service_unavailable: a temporary failure, retry after the Retry-After delay.",
            "headers": {
              "Retry-After": {
                "schema": {
                  "type": "integer"
                },
                "description": "Seconds to wait before retrying (service_unavailable only)"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/integrations/{id}/pipedrive/pipelines": {
      "get": {
        "tags": [
          "Integrations"
        ],
        "operationId": "listPipedrivePipelines",
        "summary": "List Pipedrive pipelines",
        "description": "Pipedrive integrations only. Returns the pipelines and stages to choose from for pipedriveDeals (PATCH /integrations/{id}).",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Integration ID (from GET /integrations)",
            "schema": {
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Pipelines",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/PipedrivePipeline"
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "validation_error (details lists the problems) or bad_request (invalid id, or the integration is not the provider this endpoint is for).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "description": "not_found: no integration with this id belongs to your account. Use GET /integrations to list your integrations.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded. Every integration endpoint shares one budget per account of 60 requests per minute (the dashboard's Integrations tab and every API key of the account count against the same budget), on top of the usual per-key API limit. Wait for the Retry-After delay (also in the retryAfter body field), then retry.",
            "headers": {
              "Retry-After": {
                "schema": {
                  "type": "integer"
                },
                "description": "Seconds to wait before retrying"
              },
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests allowed in the window"
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left in the window (0 on a 429)"
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "string",
                  "format": "date-time"
                },
                "description": "When the window resets"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RateLimitError"
                }
              }
            }
          },
          "500": {
            "description": "internal_error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "502": {
            "description": "provider_error: the CRM's API failed or refused the request (for example the connection was revoked in the CRM). Nothing was changed on SMB Sales Boost; retry later or reconnect the CRM.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "provider_unavailable: this provider is not available on this deployment yet (message '<Provider> is not available yet.'; not temporary, no Retry-After header, do not retry; GET /integrations/providers shows availability), or service_unavailable: a temporary failure, retry after the Retry-After delay.",
            "headers": {
              "Retry-After": {
                "schema": {
                  "type": "integer"
                },
                "description": "Seconds to wait before retrying (service_unavailable only)"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/integrations/{id}/pipedrive/deal-fields": {
      "get": {
        "tags": [
          "Integrations"
        ],
        "operationId": "listPipedriveDealFields",
        "summary": "List Pipedrive deal fields",
        "description": "Pipedrive integrations only. Returns the SMB Sales Boost lead fields, the Pipedrive deal fields and the current deal field map (pipedriveDeals.fieldMap).",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Integration ID (from GET /integrations)",
            "schema": {
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Deal fields",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "smbFields": {
                          "type": "array",
                          "items": {
                            "type": "object",
                            "additionalProperties": true
                          }
                        },
                        "dealFields": {
                          "type": "array",
                          "items": {
                            "type": "object",
                            "additionalProperties": true
                          }
                        },
                        "cachedAt": {
                          "type": "string",
                          "format": "date-time",
                          "nullable": true
                        },
                        "fieldMap": {
                          "type": "object",
                          "additionalProperties": {
                            "type": "string"
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "validation_error (details lists the problems) or bad_request (invalid id, or the integration is not the provider this endpoint is for).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "description": "not_found: no integration with this id belongs to your account. Use GET /integrations to list your integrations.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded. Every integration endpoint shares one budget per account of 60 requests per minute (the dashboard's Integrations tab and every API key of the account count against the same budget), on top of the usual per-key API limit. Wait for the Retry-After delay (also in the retryAfter body field), then retry.",
            "headers": {
              "Retry-After": {
                "schema": {
                  "type": "integer"
                },
                "description": "Seconds to wait before retrying"
              },
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests allowed in the window"
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left in the window (0 on a 429)"
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "string",
                  "format": "date-time"
                },
                "description": "When the window resets"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RateLimitError"
                }
              }
            }
          },
          "500": {
            "description": "internal_error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "502": {
            "description": "provider_error: the CRM's API failed or refused the request (for example the connection was revoked in the CRM). Nothing was changed on SMB Sales Boost; retry later or reconnect the CRM.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "provider_unavailable: this provider is not available on this deployment yet (message '<Provider> is not available yet.'; not temporary, no Retry-After header, do not retry; GET /integrations/providers shows availability), or service_unavailable: a temporary failure, retry after the Retry-After delay.",
            "headers": {
              "Retry-After": {
                "schema": {
                  "type": "integer"
                },
                "description": "Seconds to wait before retrying (service_unavailable only)"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/integrations/hubspot/callback": {
      "servers": [
        {
          "url": "https://smbsalesboost.com/api",
          "description": "OAuth redirect targets are mounted under /api (not /api/v1)"
        }
      ],
      "get": {
        "tags": [
          "Integrations"
        ],
        "security": [],
        "operationId": "hubspotOAuthCallback",
        "summary": "HubSpot OAuth redirect (browser only)",
        "description": "The redirect URI registered with HubSpot. HubSpot sends the user's browser here after they approve the authorizationUrl from POST /integrations/hubspot/connect; API clients never call it. It creates the integration, or re-authorizes the integration already connected to that HubSpot account (new access; its events, field mapping and delivery history are kept), and redirects to the dashboard Integrations tab with hubspot_connected=1 (plus hubspot_reconnected=1 for a re-authorize, and hubspot_paused=1 when that integration is still paused). On failure the redirect carries hubspot_error=<reason>, for example key_revoked (the API key that started the connection was revoked; nothing was created or changed), integration_limit (the account has 25 integrations and this HubSpot account is not connected yet; nothing was created) or, rarely, already_connected (two connections of the same account finished at the same moment), or the message 'We could not confirm which HubSpot account you signed in to, so nothing was connected or changed. Please try connecting again in a minute.' when HubSpot did not confirm the account (nothing was created or changed).",
        "parameters": [
          {
            "in": "query",
            "name": "code",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "in": "query",
            "name": "state",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "302": {
            "description": "Redirects back to the dashboard Integrations tab"
          }
        }
      }
    },
    "/integrations/salesforce/callback": {
      "servers": [
        {
          "url": "https://smbsalesboost.com/api",
          "description": "OAuth redirect targets are mounted under /api (not /api/v1)"
        }
      ],
      "get": {
        "tags": [
          "Integrations"
        ],
        "security": [],
        "operationId": "salesforceOAuthCallback",
        "summary": "Salesforce OAuth redirect (browser only)",
        "description": "The redirect URI registered with Salesforce. Salesforce sends the user's browser here after they approve the authorizationUrl from POST /integrations/salesforce/connect; API clients never call it. It creates the integration, or re-authorizes the integration already connected to that Salesforce account (new access; its events, field mapping and delivery history are kept), and redirects to the dashboard Integrations tab with salesforce_connected=1 (plus salesforce_reconnected=1 for a re-authorize, and salesforce_paused=1 when that integration is still paused). On failure the redirect carries salesforce_error=<reason>, for example key_revoked (the API key that started the connection was revoked; nothing was created or changed), integration_limit (the account has 25 integrations and this Salesforce account is not connected yet; nothing was created) or, rarely, already_connected (two connections of the same account finished at the same moment).",
        "parameters": [
          {
            "in": "query",
            "name": "code",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "in": "query",
            "name": "state",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "302": {
            "description": "Redirects back to the dashboard Integrations tab"
          }
        }
      }
    },
    "/integrations/pipedrive/callback": {
      "servers": [
        {
          "url": "https://smbsalesboost.com/api",
          "description": "OAuth redirect targets are mounted under /api (not /api/v1)"
        }
      ],
      "get": {
        "tags": [
          "Integrations"
        ],
        "security": [],
        "operationId": "pipedriveOAuthCallback",
        "summary": "Pipedrive OAuth redirect (browser only)",
        "description": "The redirect URI registered with Pipedrive. Pipedrive sends the user's browser here after they approve the authorizationUrl from POST /integrations/pipedrive/connect; API clients never call it. It creates the integration, or re-authorizes the integration already connected to that Pipedrive account (new access; its events, field mapping, Pipedrive deal settings and delivery history are kept), and redirects to the dashboard Integrations tab with pipedrive_connected=1 (plus pipedrive_reconnected=1 for a re-authorize, and pipedrive_paused=1 when that integration is still paused). On failure the redirect carries pipedrive_error=<reason>, for example key_revoked (the API key that started the connection was revoked; nothing was created or changed), integration_limit (the account has 25 integrations and this Pipedrive account is not connected yet; nothing was created) or, rarely, already_connected (two connections of the same account finished at the same moment).",
        "parameters": [
          {
            "in": "query",
            "name": "code",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "in": "query",
            "name": "state",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "302": {
            "description": "Redirects back to the dashboard Integrations tab"
          }
        }
      }
    },
    "/purchase": {
      "post": {
        "tags": [
          "Programmatic Purchase"
        ],
        "operationId": "createPurchase",
        "summary": "Start a subscription purchase or free trial",
        "description": "Create a Stripe Checkout session for a subscription entirely via API. No web signup, onboarding or API key is needed. The 201 response carries checkoutUrl (open it in a browser to pay or to start the trial), expiresAt, plan, a trial object and a message. It does not return a claim token or the API key: the API key is emailed to the address you supplied once checkout is complete. If that address already has an SMB Sales Boost account, the purchase is first confirmed from an email sent to that address, and the key follows once it is confirmed.\n\nFree trial: Starter, Growth and Scale start with a 14-day free trial by default. trial defaults to true; only the JSON boolean false skips the trial and charges the plan price at checkout. Platinum and Enterprise never trial. During a trial a payment method is required, nothing is charged at checkout, the trial includes 50% of the plan's monthly credits, and the plan price is charged automatically when the trial ends unless the subscription is cancelled first. One trial per person: this endpoint checks the email address (see trial.eligible and trial.reason in the response), and the payment method is checked after checkout, where a checkout whose card already had a trial is cancelled without a charge. All charges are final; there are no refunds.\n\nRate limited to 5 requests per hour per IP.",
        "security": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "email",
                  "plan"
                ],
                "properties": {
                  "email": {
                    "type": "string",
                    "format": "email",
                    "description": "Customer email address",
                    "example": "user@example.com"
                  },
                  "plan": {
                    "type": "string",
                    "enum": [
                      "starter",
                      "growth",
                      "scale",
                      "platinum",
                      "enterprise"
                    ],
                    "description": "Subscription plan to purchase",
                    "example": "growth"
                  },
                  "trial": {
                    "type": "boolean",
                    "default": true,
                    "description": "Start with the 14-day free trial when eligible (Starter, Growth and Scale only). Defaults to true; only the JSON boolean false opts out and buys the plan outright. Ignored for Platinum and Enterprise, which never trial.",
                    "example": true
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Checkout session created",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "checkoutUrl": {
                          "type": "string",
                          "format": "uri",
                          "description": "Stripe Checkout URL - redirect or open this to complete payment"
                        },
                        "expiresAt": {
                          "type": "string",
                          "format": "date-time",
                          "description": "When this checkout session expires (24 hours from creation)"
                        },
                        "plan": {
                          "type": "string",
                          "description": "The plan being purchased"
                        },
                        "trial": {
                          "type": "object",
                          "description": "Whether this checkout starts a free trial, and what it costs.",
                          "properties": {
                            "eligible": {
                              "type": "boolean",
                              "description": "True when this checkout starts a 14-day free trial"
                            },
                            "days": {
                              "type": "integer",
                              "description": "Trial length in days (14), or 0 when there is no trial",
                              "example": 14
                            },
                            "credits": {
                              "type": "integer",
                              "description": "Credits included in the trial (50% of the plan's monthly credits), or 0 when there is no trial"
                            },
                            "endsAt": {
                              "type": "string",
                              "format": "date-time",
                              "nullable": true,
                              "description": "Estimated trial end, when the plan price is charged unless the subscription is cancelled first; null when there is no trial"
                            },
                            "reason": {
                              "type": "string",
                              "enum": [
                                "not_requested",
                                "plan_not_eligible",
                                "trial_already_used",
                                "currently_subscribed"
                              ],
                              "description": "Present only when eligible is false. not_requested: you sent trial: false. plan_not_eligible: Platinum and Enterprise never trial. trial_already_used: this email address already had a free trial. currently_subscribed: this email address has an active or trialing subscription."
                            },
                            "note": {
                              "type": "string",
                              "description": "What is charged and when, for example: 'A payment method is required but nothing is charged today. The plan price is charged automatically when the 14-day trial ends unless the subscription is cancelled first. All charges are final.'"
                            }
                          }
                        },
                        "message": {
                          "type": "string",
                          "description": "Next steps. Says that the API key will be emailed to the address you provided, and that an address that already has an account is first sent an email to confirm the purchase. The same wording is returned for every address."
                        }
                      }
                    }
                  }
                },
                "example": {
                  "data": {
                    "checkoutUrl": "https://checkout.stripe.com/c/pay/cs_live_...",
                    "expiresAt": "2026-10-08T12:00:00.000Z",
                    "plan": "growth",
                    "trial": {
                      "eligible": true,
                      "days": 14,
                      "credits": 1000,
                      "endsAt": "2026-10-21T12:00:00.000Z",
                      "note": "A payment method is required but nothing is charged today. The plan price is charged automatically when the 14-day trial ends unless the subscription is cancelled first. All charges are final."
                    },
                    "message": "Complete checkout at the URL to start the 14-day free trial. Your API key will be emailed to the address you provided once the trial starts. If that address already has an SMB Sales Boost account, we first email it a link to confirm the purchase, and the key is sent once it is confirmed."
                  }
                }
              }
            }
          },
          "400": {
            "description": "invalid_email (a valid email address is required) or invalid_plan (plan must be one of starter, growth, scale, platinum, enterprise)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "invalid_plan",
                  "message": "Plan must be one of: platinum, enterprise, starter, growth, scale"
                }
              }
            }
          },
          "403": {
            "description": "forbidden (this email address is not eligible for purchase) or disposable_email (use a permanent email address)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "disposable_email",
                  "message": "Disposable email addresses are not allowed. Please use a permanent email address."
                }
              }
            }
          },
          "409": {
            "description": "already_subscribed: this email address already has an active subscription or is on a free trial. To start the paid plan during a trial, or to switch trial plans, use POST /subscription/change-plan with that account's API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "already_subscribed",
                  "message": "An active subscription already exists for this email. Use the dashboard to manage your API keys."
                }
              }
            }
          },
          "410": {
            "description": "plan_retired: the legacy 'pro' plan is no longer sold; choose starter, growth, scale, platinum or enterprise",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "plan_retired",
                  "message": "The \"pro\" plan is no longer available for new purchases. Please choose one of: platinum, enterprise, starter, growth, scale."
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "description": "internal_error: the checkout session could not be created",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "service_unavailable: payment processing is not configured",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/claim-key": {
      "post": {
        "tags": [
          "Programmatic Purchase"
        ],
        "operationId": "claimApiKey",
        "summary": "Claim an API key with a claim token (legacy)",
        "description": "Kept for backward compatibility only. This endpoint exchanges a claim token for the API key, but current purchases no longer issue a claim token (POST /purchase does not return one), so new buyers should wait for the API key that is emailed after checkout instead. With a token from an older purchase: 200 returns the key once, 202 means payment is still pending. Rate limited to 30 requests per hour per IP.",
        "deprecated": true,
        "security": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "email",
                  "claimToken"
                ],
                "properties": {
                  "email": {
                    "type": "string",
                    "format": "email",
                    "description": "Email used during purchase",
                    "example": "user@example.com"
                  },
                  "claimToken": {
                    "type": "string",
                    "description": "A claim token from an older purchase flow. Current purchases do not issue one: the API key is emailed to the purchase address instead.",
                    "example": "claim_abc123..."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "API key claimed successfully",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "apiKey": {
                          "type": "string",
                          "description": "Your API key (smbk_ prefix). Store securely - shown only once."
                        },
                        "plan": {
                          "type": "string",
                          "description": "Subscription plan"
                        },
                        "email": {
                          "type": "string",
                          "description": "Account email"
                        },
                        "message": {
                          "type": "string",
                          "description": "Account setup recommendations"
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "202": {
            "description": "Payment not yet completed - poll again in 5-10 seconds",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "status": {
                          "type": "string",
                          "enum": [
                            "pending"
                          ]
                        },
                        "message": {
                          "type": "string"
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "invalid_email or invalid_token (the claim token is missing)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "email_mismatch: the email does not match the purchase record",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found: invalid claim token",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "already_claimed: the key was already claimed; generate a new key from the dashboard if it was lost",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "410": {
            "description": "expired: the purchase session has expired",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/me": {
      "get": {
        "tags": [
          "User Profile"
        ],
        "operationId": "getUserProfile",
        "summary": "Get your profile",
        "description": "Retrieve your user profile including subscription details, account settings, and onboarding status.",
        "responses": {
          "200": {
            "description": "User profile retrieved successfully",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/UserProfile"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        }
      },
      "patch": {
        "tags": [
          "User Profile"
        ],
        "operationId": "updateUserProfile",
        "summary": "Update your profile",
        "description": "Update your profile fields. Only provided fields are updated. Allowed fields: firstName (max 50 characters), lastName (max 50), companyName (max 100), companyWebsite (max 500; must be an http:// or https:// URL, a bare domain is accepted and stored with https://). Send null or an empty string to clear a field.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "firstName": {
                    "type": "string",
                    "nullable": true,
                    "maxLength": 50,
                    "example": "John"
                  },
                  "lastName": {
                    "type": "string",
                    "nullable": true,
                    "maxLength": 50,
                    "example": "Doe"
                  },
                  "companyName": {
                    "type": "string",
                    "nullable": true,
                    "maxLength": 100,
                    "example": "Acme Corp"
                  },
                  "companyWebsite": {
                    "type": "string",
                    "nullable": true,
                    "maxLength": 500,
                    "description": "http:// or https:// URL; a bare domain such as acme.com is stored as https://acme.com/",
                    "example": "https://acme.com"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Profile updated successfully",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/UserProfileBasic"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        }
      }
    },
    "/leads": {
      "get": {
        "tags": [
          "Leads"
        ],
        "operationId": "searchLeads",
        "summary": "Search and filter leads",
        "description": "Search the lead database with keyword filters, location filters, and other criteria.\n\nPositive filter required: send at least one of positiveKeywords, nameIncludeTerms, urlIncludeTerms, crawledUrlIncludeTerms or descriptionIncludeTerms. Without one the call returns 200 with an empty leads array, requiresKeywords: true and a message, and charges nothing. Uses a two-phase filtering model: Phase 1 (Include) combines all positive keywords with OR logic, Phase 2 (Exclude) removes matches via AND logic.\n\nCredit System (credit plans: Starter, Growth, Scale, Platinum, Enterprise): each new lead returned costs 1 credit. Charging is sticky: a lead you have already received through any channel (dashboard, API, MCP or email schedule) is never charged again, even if its data changed later, and a new lead that shares a primary phone or primary email with a lead you already received is also free. Within the page selected by page and limit, new leads are always ordered first, and they are cut to the smaller of maxCredits and your spendable balance (credit balance plus any overage-budget headroom); when the balance was the limit the response carries X-Credits-Capped: true. Omitting maxCredits therefore caps the request at your spendable balance instead of failing. 402 insufficient_credits only happens if a concurrent request spends the balance between the check and the charge. creditsUsed and creditsRemaining in the response report what was charged. Your export blacklist is not applied to this endpoint (GET /leads/preview and POST /leads/export apply it).\n\nLead Database: queries run against the Main database ('other'), which contains businesses of all types (e.g., accounting firms, marketing agencies, medical practices) with both phone numbers and email addresses as available contact methods. The database parameter is kept for backward compatibility; any value resolves to the Main database.\n\nWildcard Pattern Matching: All keyword and term parameters support `*` as a wildcard character that matches any sequence of characters. For example, `*account*` matches 'accounting', 'accountant', 'accounting firm', etc. Multiple wildcards in a single term let you match compound phrases: `*book*keep*` matches 'bookkeeping', 'bookkeeper services', etc. Other examples: `*market*agency*`, `*tax*prep*`. Keywords without wildcards perform substring matching by default (equivalent to wrapping in `*...*`).",
        "parameters": [
          {
            "$ref": "#/components/parameters/page"
          },
          {
            "$ref": "#/components/parameters/limit"
          },
          {
            "$ref": "#/components/parameters/database"
          },
          {
            "$ref": "#/components/parameters/positiveKeywords"
          },
          {
            "$ref": "#/components/parameters/negativeKeywords"
          },
          {
            "$ref": "#/components/parameters/orColumns"
          },
          {
            "$ref": "#/components/parameters/stateInclude"
          },
          {
            "$ref": "#/components/parameters/stateExclude"
          },
          {
            "$ref": "#/components/parameters/cityInclude"
          },
          {
            "$ref": "#/components/parameters/cityExclude"
          },
          {
            "$ref": "#/components/parameters/zipInclude"
          },
          {
            "$ref": "#/components/parameters/zipExclude"
          },
          {
            "$ref": "#/components/parameters/nameIncludeTerms"
          },
          {
            "$ref": "#/components/parameters/nameExcludeTerms"
          },
          {
            "$ref": "#/components/parameters/sortBy"
          },
          {
            "$ref": "#/components/parameters/sortOrder"
          },
          {
            "$ref": "#/components/parameters/minReviewCount"
          },
          {
            "$ref": "#/components/parameters/maxReviewCount"
          },
          {
            "$ref": "#/components/parameters/descriptionEmptyFilter"
          },
          {
            "$ref": "#/components/parameters/urlIncludeTerms"
          },
          {
            "$ref": "#/components/parameters/urlExcludeTerms"
          },
          {
            "$ref": "#/components/parameters/crawledUrlIncludeTerms"
          },
          {
            "$ref": "#/components/parameters/crawledUrlExcludeTerms"
          },
          {
            "$ref": "#/components/parameters/descriptionIncludeTerms"
          },
          {
            "$ref": "#/components/parameters/descriptionExcludeTerms"
          },
          {
            "$ref": "#/components/parameters/emailPrimaryInclude"
          },
          {
            "$ref": "#/components/parameters/emailPrimaryExclude"
          },
          {
            "$ref": "#/components/parameters/phonePrimaryInclude"
          },
          {
            "$ref": "#/components/parameters/phonePrimaryExclude"
          },
          {
            "$ref": "#/components/parameters/emailTier1Include"
          },
          {
            "$ref": "#/components/parameters/emailTier1Exclude"
          },
          {
            "$ref": "#/components/parameters/emailTier2Include"
          },
          {
            "$ref": "#/components/parameters/emailTier2Exclude"
          },
          {
            "$ref": "#/components/parameters/emailTier3Include"
          },
          {
            "$ref": "#/components/parameters/emailTier3Exclude"
          },
          {
            "$ref": "#/components/parameters/emailTier4Include"
          },
          {
            "$ref": "#/components/parameters/emailTier4Exclude"
          },
          {
            "$ref": "#/components/parameters/emailTier5Include"
          },
          {
            "$ref": "#/components/parameters/emailTier5Exclude"
          },
          {
            "$ref": "#/components/parameters/emailTier6Include"
          },
          {
            "$ref": "#/components/parameters/emailTier6Exclude"
          },
          {
            "$ref": "#/components/parameters/phoneTotalInclude"
          },
          {
            "$ref": "#/components/parameters/phoneTotalExclude"
          },
          {
            "$ref": "#/components/parameters/employeeCountEmptyFilter"
          },
          {
            "$ref": "#/components/parameters/employeeCountInclude"
          },
          {
            "$ref": "#/components/parameters/employeeCountExclude"
          },
          {
            "$ref": "#/components/parameters/foundingDateEmptyFilter"
          },
          {
            "$ref": "#/components/parameters/foundingDateInclude"
          },
          {
            "$ref": "#/components/parameters/foundingDateExclude"
          },
          {
            "$ref": "#/components/parameters/isChainEmptyFilter"
          },
          {
            "$ref": "#/components/parameters/isChainInclude"
          },
          {
            "$ref": "#/components/parameters/isChainExclude"
          },
          {
            "$ref": "#/components/parameters/priceRangeEmptyFilter"
          },
          {
            "$ref": "#/components/parameters/priceRangeInclude"
          },
          {
            "$ref": "#/components/parameters/priceRangeExclude"
          },
          {
            "$ref": "#/components/parameters/legalNameEmptyFilter"
          },
          {
            "$ref": "#/components/parameters/legalNameInclude"
          },
          {
            "$ref": "#/components/parameters/legalNameExclude"
          },
          {
            "$ref": "#/components/parameters/industryNaicsEmptyFilter"
          },
          {
            "$ref": "#/components/parameters/industryNaicsInclude"
          },
          {
            "$ref": "#/components/parameters/industryNaicsExclude"
          },
          {
            "$ref": "#/components/parameters/dunsEmptyFilter"
          },
          {
            "$ref": "#/components/parameters/dunsInclude"
          },
          {
            "$ref": "#/components/parameters/dunsExclude"
          },
          {
            "$ref": "#/components/parameters/founderEmptyFilter"
          },
          {
            "$ref": "#/components/parameters/founderInclude"
          },
          {
            "$ref": "#/components/parameters/founderExclude"
          },
          {
            "$ref": "#/components/parameters/founderEmailEmptyFilter"
          },
          {
            "$ref": "#/components/parameters/founderEmailInclude"
          },
          {
            "$ref": "#/components/parameters/founderEmailExclude"
          },
          {
            "$ref": "#/components/parameters/founderPhoneEmptyFilter"
          },
          {
            "$ref": "#/components/parameters/founderPhoneInclude"
          },
          {
            "$ref": "#/components/parameters/founderPhoneExclude"
          },
          {
            "$ref": "#/components/parameters/openingHoursEmptyFilter"
          },
          {
            "$ref": "#/components/parameters/openingHoursInclude"
          },
          {
            "$ref": "#/components/parameters/openingHoursExclude"
          },
          {
            "$ref": "#/components/parameters/areaServedEmptyFilter"
          },
          {
            "$ref": "#/components/parameters/areaServedInclude"
          },
          {
            "$ref": "#/components/parameters/areaServedExclude"
          },
          {
            "$ref": "#/components/parameters/offersEmptyFilter"
          },
          {
            "$ref": "#/components/parameters/offersInclude"
          },
          {
            "$ref": "#/components/parameters/offersExclude"
          },
          {
            "$ref": "#/components/parameters/brandEmptyFilter"
          },
          {
            "$ref": "#/components/parameters/brandInclude"
          },
          {
            "$ref": "#/components/parameters/brandExclude"
          },
          {
            "$ref": "#/components/parameters/sloganEmptyFilter"
          },
          {
            "$ref": "#/components/parameters/sloganInclude"
          },
          {
            "$ref": "#/components/parameters/sloganExclude"
          },
          {
            "$ref": "#/components/parameters/logoUrlEmptyFilter"
          },
          {
            "$ref": "#/components/parameters/logoUrlInclude"
          },
          {
            "$ref": "#/components/parameters/logoUrlExclude"
          },
          {
            "$ref": "#/components/parameters/socialProfilesEmptyFilter"
          },
          {
            "$ref": "#/components/parameters/socialProfilesInclude"
          },
          {
            "$ref": "#/components/parameters/socialProfilesExclude"
          },
          {
            "$ref": "#/components/parameters/platformEmptyFilter"
          },
          {
            "$ref": "#/components/parameters/platformInclude"
          },
          {
            "$ref": "#/components/parameters/platformExclude"
          },
          {
            "$ref": "#/components/parameters/bookingOpsCrmSoftwareEmptyFilter"
          },
          {
            "$ref": "#/components/parameters/bookingOpsCrmSoftwareInclude"
          },
          {
            "$ref": "#/components/parameters/bookingOpsCrmSoftwareExclude"
          },
          {
            "$ref": "#/components/parameters/reputationChatSoftwareEmptyFilter"
          },
          {
            "$ref": "#/components/parameters/reputationChatSoftwareInclude"
          },
          {
            "$ref": "#/components/parameters/reputationChatSoftwareExclude"
          },
          {
            "$ref": "#/components/parameters/marketingEmailSoftwareEmptyFilter"
          },
          {
            "$ref": "#/components/parameters/marketingEmailSoftwareInclude"
          },
          {
            "$ref": "#/components/parameters/marketingEmailSoftwareExclude"
          },
          {
            "$ref": "#/components/parameters/adPixelsEmptyFilter"
          },
          {
            "$ref": "#/components/parameters/adPixelsInclude"
          },
          {
            "$ref": "#/components/parameters/adPixelsExclude"
          },
          {
            "$ref": "#/components/parameters/paymentFinancingSoftwareEmptyFilter"
          },
          {
            "$ref": "#/components/parameters/paymentFinancingSoftwareInclude"
          },
          {
            "$ref": "#/components/parameters/paymentFinancingSoftwareExclude"
          },
          {
            "$ref": "#/components/parameters/emailPrimaryMxValues"
          },
          {
            "$ref": "#/components/parameters/emailPrimaryMxEmptyFilter"
          },
          {
            "$ref": "#/components/parameters/emailDomainMxValues"
          },
          {
            "$ref": "#/components/parameters/emailDomainMxEmptyFilter"
          },
          {
            "$ref": "#/components/parameters/emailSecurityValues"
          },
          {
            "$ref": "#/components/parameters/emailSecurityEmptyFilter"
          },
          {
            "$ref": "#/components/parameters/addressSourceValues"
          },
          {
            "$ref": "#/components/parameters/addressSourceEmptyFilter"
          },
          {
            "$ref": "#/components/parameters/minNumLocations"
          },
          {
            "$ref": "#/components/parameters/maxNumLocations"
          },
          {
            "$ref": "#/components/parameters/minOpenPositions"
          },
          {
            "$ref": "#/components/parameters/maxOpenPositions"
          },
          {
            "$ref": "#/components/parameters/openPositionsEmptyFilter"
          },
          {
            "$ref": "#/components/parameters/minRatingValue"
          },
          {
            "$ref": "#/components/parameters/maxRatingValue"
          },
          {
            "$ref": "#/components/parameters/redirectFilter"
          },
          {
            "$ref": "#/components/parameters/buyingSignalTypeFilter"
          },
          {
            "$ref": "#/components/parameters/updateReasonFilter"
          },
          {
            "$ref": "#/components/parameters/websiteSchemaFilter"
          },
          {
            "$ref": "#/components/parameters/lastBuyingSignalFrom"
          },
          {
            "$ref": "#/components/parameters/lastUpdatedFrom"
          },
          {
            "$ref": "#/components/parameters/lastBuyingSignalTo"
          },
          {
            "$ref": "#/components/parameters/lastUpdatedTo"
          },
          {
            "$ref": "#/components/parameters/timeScrapedFrom"
          },
          {
            "$ref": "#/components/parameters/timeScrapedTo"
          },
          {
            "$ref": "#/components/parameters/registrationDateFrom"
          },
          {
            "$ref": "#/components/parameters/registrationDateTo"
          },
          {
            "$ref": "#/components/parameters/search"
          },
          {
            "$ref": "#/components/parameters/streetInclude"
          },
          {
            "$ref": "#/components/parameters/streetExclude"
          },
          {
            "$ref": "#/components/parameters/streetEmptyFilter"
          },
          {
            "$ref": "#/components/parameters/cityEmptyFilter"
          },
          {
            "$ref": "#/components/parameters/stateEmptyFilter"
          },
          {
            "$ref": "#/components/parameters/zipEmptyFilter"
          },
          {
            "$ref": "#/components/parameters/phonePrimaryEmptyFilter"
          },
          {
            "$ref": "#/components/parameters/phoneTotalEmptyFilter"
          },
          {
            "$ref": "#/components/parameters/emailPrimaryEmptyFilter"
          },
          {
            "$ref": "#/components/parameters/emailTier1EmptyFilter"
          },
          {
            "$ref": "#/components/parameters/emailTier2EmptyFilter"
          },
          {
            "$ref": "#/components/parameters/emailTier3EmptyFilter"
          },
          {
            "$ref": "#/components/parameters/emailTier4EmptyFilter"
          },
          {
            "$ref": "#/components/parameters/emailTier5EmptyFilter"
          },
          {
            "$ref": "#/components/parameters/emailTier6EmptyFilter"
          },
          {
            "name": "maxResults",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1
            },
            "description": "Most leads to return in this call, new leads plus leads you already received. New leads come first. It works inside the page selected by page and limit, so a value above limit has no effect."
          },
          {
            "name": "maxCredits",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 0
            },
            "description": "Most credits to spend on this call. Only new leads count. The call is also capped at your spendable balance (credit balance plus any overage-budget headroom), so the effective cap is the smaller of the two; when the balance was the limit the response carries X-Credits-Capped: true. Leads you already received are included free up to maxResults or the page limit. Set 0 to receive only leads you already received, at no cost. If omitted, the call is capped at your spendable balance."
          }
        ],
        "responses": {
          "200": {
            "description": "Leads matching the search criteria",
            "headers": {
              "X-Credits-Capped": {
                "$ref": "#/components/headers/XCreditsCapped"
              },
              "X-Lead-Query-Path": {
                "$ref": "#/components/headers/XLeadQueryPath"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "leads": {
                          "type": "array",
                          "items": {
                            "$ref": "#/components/schemas/Lead"
                          }
                        },
                        "pagination": {
                          "type": "object",
                          "properties": {
                            "page": {
                              "type": "integer",
                              "description": "Current page number"
                            },
                            "limit": {
                              "type": "integer",
                              "description": "Results per page"
                            },
                            "total": {
                              "type": "integer",
                              "description": "Total matching leads before pagination"
                            },
                            "pages": {
                              "type": "integer",
                              "description": "Total number of pages"
                            }
                          }
                        },
                        "databaseType": {
                          "type": "string",
                          "enum": [
                            "other"
                          ],
                          "description": "The lead database that served this request: always 'other' (the Main database)."
                        },
                        "creditsUsed": {
                          "type": "integer",
                          "description": "Credits charged for this call: the number of new leads delivered (only present for credit-plan users)"
                        },
                        "creditsRemaining": {
                          "type": "integer",
                          "description": "Total credits remaining after this query (only present for credit-plan users)"
                        },
                        "maxResults": {
                          "type": "integer",
                          "description": "Echo of maxResults parameter (only present when maxResults was specified)"
                        },
                        "maxCredits": {
                          "type": "integer",
                          "description": "Echo of maxCredits parameter (only present when maxCredits was specified)"
                        },
                        "requiresKeywords": {
                          "type": "boolean",
                          "description": "Present and true when no positive filter was sent; leads is then empty and nothing was charged"
                        },
                        "message": {
                          "type": "string",
                          "description": "Present with requiresKeywords: explains which filters count as positive"
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "description": "insufficient_credits (credit plans only). Requests are capped at your spendable balance, so this only happens when a concurrent request from your account spent the balance between the check and the charge. Nothing was charged; retry, or buy credits with POST /purchase-credits.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/InsufficientCreditsError"
                },
                "example": {
                  "error": "insufficient_credits",
                  "message": "This query requires 120 credits but you only have 80 available. Use maxCredits or maxResults to limit credit usage, reduce your page size with the 'limit' parameter, or purchase more credits via POST /api/v1/purchase-credits.",
                  "creditsRemaining": 80
                }
              }
            }
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "description": "Too many requests, for one of three reasons. (1) A rate limit was exceeded: error 'Rate limit exceeded' with a retryAfter field (seconds, the same as the Retry-After header). (2) Lead search capacity is busy: error 'lead_query_capacity_busy' with Retry-After: 10. (3) Another export, lead search or integration push of your account is still recording its charge and did not finish within 30 seconds (less when the search itself already used most of the request's time): error 'charge_in_progress' with Retry-After: 30. All carry the X-RateLimit-* headers (the per-key rate limiter sets them on every request), so tell them apart by the body's error field, not by the headers. None of them charged anything; retry after the Retry-After delay, or narrow your filters so each query returns faster.",
            "headers": {
              "Retry-After": {
                "schema": {
                  "type": "integer"
                },
                "description": "Seconds to wait before retrying"
              },
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests allowed in the window"
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left in the window (0 on a 'Rate limit exceeded' 429; the other 429s show the normal count)"
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "string",
                  "format": "date-time"
                },
                "description": "When the window resets, as an ISO 8601 date-time (for example 2026-10-07T14:05:00.000Z)"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "oneOf": [
                    {
                      "$ref": "#/components/schemas/RateLimitError"
                    },
                    {
                      "$ref": "#/components/schemas/Error"
                    }
                  ]
                },
                "examples": {
                  "rateLimited": {
                    "summary": "Rate limit exceeded",
                    "value": {
                      "error": "Rate limit exceeded",
                      "message": "API rate limit exceeded. Please slow down your requests.",
                      "retryAfter": 42
                    }
                  },
                  "leadQueryCapacityBusy": {
                    "summary": "Lead search capacity busy",
                    "value": {
                      "error": "lead_query_capacity_busy",
                      "message": "Lead search capacity is currently busy. Retry after the indicated delay, or narrow your filters so each query returns faster."
                    }
                  },
                  "chargeInProgress": {
                    "summary": "Another charge of your account is still being recorded",
                    "value": {
                      "error": "charge_in_progress",
                      "message": "Another export or push for your account is still being recorded. Please try again in a minute."
                    }
                  }
                }
              }
            }
          },
          "500": {
            "description": "internal_error. The request may have been partly processed (credits charged and leads recorded as received), so check creditsRemaining with GET /me before retrying. A retry is not charged again for leads already recorded as received, but it can charge for other new leads, so check first.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        }
      }
    },
    "/leads/preview": {
      "get": {
        "tags": [
          "Leads"
        ],
        "operationId": "previewLeads",
        "summary": "Preview leads with masked contact info (no credits)",
        "description": "Preview leads matching your filters with masked contact details, at no credit cost. Accepts the same filters as GET /leads (including the contact term filters such as emailPrimaryInclude and phonePrimaryInclude, and the company-column include, exclude and empty filters) plus excludePurchased; maxCredits and maxResults do not apply. Use it to test filters and size a query before spending credits on GET /leads or POST /leads/export.\n\nMasking: contact values are masked, not hashed. A phone keeps its area code and last two digits, for example '(512) ***-**42', and an email keeps its first three characters and the full domain, for example 'hel*@domain.com'. Total Phones, the email tier lists and founder emails and phones are masked value by value.\n\nLeads you already received come back unmasked with contactExported: true, the same as on the dashboard. A lead counts as received when its id was delivered to you, or when its primary phone, Total Phones, primary email or Email Tier 1-6 values include the primary phone or primary email of a lead you already received. Charging is narrower (it compares primary phone and primary email only), so a lead revealed only through a Total Phones or Email Tier value can still cost a credit on GET /leads or POST /leads/export.\n\nSet excludePurchased=true to drop leads you already received. They are removed after the page is selected, so a page can hold fewer than limit leads while pagination.total still counts them. Your export blacklist is applied. Response includes preview: true.",
        "parameters": [
          {
            "$ref": "#/components/parameters/page"
          },
          {
            "$ref": "#/components/parameters/limit"
          },
          {
            "$ref": "#/components/parameters/database"
          },
          {
            "$ref": "#/components/parameters/positiveKeywords"
          },
          {
            "$ref": "#/components/parameters/negativeKeywords"
          },
          {
            "$ref": "#/components/parameters/orColumns"
          },
          {
            "$ref": "#/components/parameters/stateInclude"
          },
          {
            "$ref": "#/components/parameters/stateExclude"
          },
          {
            "$ref": "#/components/parameters/cityInclude"
          },
          {
            "$ref": "#/components/parameters/cityExclude"
          },
          {
            "$ref": "#/components/parameters/zipInclude"
          },
          {
            "$ref": "#/components/parameters/zipExclude"
          },
          {
            "$ref": "#/components/parameters/nameIncludeTerms"
          },
          {
            "$ref": "#/components/parameters/nameExcludeTerms"
          },
          {
            "$ref": "#/components/parameters/sortBy"
          },
          {
            "$ref": "#/components/parameters/sortOrder"
          },
          {
            "$ref": "#/components/parameters/minReviewCount"
          },
          {
            "$ref": "#/components/parameters/maxReviewCount"
          },
          {
            "$ref": "#/components/parameters/descriptionEmptyFilter"
          },
          {
            "$ref": "#/components/parameters/urlIncludeTerms"
          },
          {
            "$ref": "#/components/parameters/urlExcludeTerms"
          },
          {
            "$ref": "#/components/parameters/crawledUrlIncludeTerms"
          },
          {
            "$ref": "#/components/parameters/crawledUrlExcludeTerms"
          },
          {
            "$ref": "#/components/parameters/descriptionIncludeTerms"
          },
          {
            "$ref": "#/components/parameters/descriptionExcludeTerms"
          },
          {
            "$ref": "#/components/parameters/emailPrimaryInclude"
          },
          {
            "$ref": "#/components/parameters/emailPrimaryExclude"
          },
          {
            "$ref": "#/components/parameters/phonePrimaryInclude"
          },
          {
            "$ref": "#/components/parameters/phonePrimaryExclude"
          },
          {
            "$ref": "#/components/parameters/emailTier1Include"
          },
          {
            "$ref": "#/components/parameters/emailTier1Exclude"
          },
          {
            "$ref": "#/components/parameters/emailTier2Include"
          },
          {
            "$ref": "#/components/parameters/emailTier2Exclude"
          },
          {
            "$ref": "#/components/parameters/emailTier3Include"
          },
          {
            "$ref": "#/components/parameters/emailTier3Exclude"
          },
          {
            "$ref": "#/components/parameters/emailTier4Include"
          },
          {
            "$ref": "#/components/parameters/emailTier4Exclude"
          },
          {
            "$ref": "#/components/parameters/emailTier5Include"
          },
          {
            "$ref": "#/components/parameters/emailTier5Exclude"
          },
          {
            "$ref": "#/components/parameters/emailTier6Include"
          },
          {
            "$ref": "#/components/parameters/emailTier6Exclude"
          },
          {
            "$ref": "#/components/parameters/phoneTotalInclude"
          },
          {
            "$ref": "#/components/parameters/phoneTotalExclude"
          },
          {
            "$ref": "#/components/parameters/employeeCountEmptyFilter"
          },
          {
            "$ref": "#/components/parameters/employeeCountInclude"
          },
          {
            "$ref": "#/components/parameters/employeeCountExclude"
          },
          {
            "$ref": "#/components/parameters/foundingDateEmptyFilter"
          },
          {
            "$ref": "#/components/parameters/foundingDateInclude"
          },
          {
            "$ref": "#/components/parameters/foundingDateExclude"
          },
          {
            "$ref": "#/components/parameters/isChainEmptyFilter"
          },
          {
            "$ref": "#/components/parameters/isChainInclude"
          },
          {
            "$ref": "#/components/parameters/isChainExclude"
          },
          {
            "$ref": "#/components/parameters/priceRangeEmptyFilter"
          },
          {
            "$ref": "#/components/parameters/priceRangeInclude"
          },
          {
            "$ref": "#/components/parameters/priceRangeExclude"
          },
          {
            "$ref": "#/components/parameters/legalNameEmptyFilter"
          },
          {
            "$ref": "#/components/parameters/legalNameInclude"
          },
          {
            "$ref": "#/components/parameters/legalNameExclude"
          },
          {
            "$ref": "#/components/parameters/industryNaicsEmptyFilter"
          },
          {
            "$ref": "#/components/parameters/industryNaicsInclude"
          },
          {
            "$ref": "#/components/parameters/industryNaicsExclude"
          },
          {
            "$ref": "#/components/parameters/dunsEmptyFilter"
          },
          {
            "$ref": "#/components/parameters/dunsInclude"
          },
          {
            "$ref": "#/components/parameters/dunsExclude"
          },
          {
            "$ref": "#/components/parameters/founderEmptyFilter"
          },
          {
            "$ref": "#/components/parameters/founderInclude"
          },
          {
            "$ref": "#/components/parameters/founderExclude"
          },
          {
            "$ref": "#/components/parameters/founderEmailEmptyFilter"
          },
          {
            "$ref": "#/components/parameters/founderEmailInclude"
          },
          {
            "$ref": "#/components/parameters/founderEmailExclude"
          },
          {
            "$ref": "#/components/parameters/founderPhoneEmptyFilter"
          },
          {
            "$ref": "#/components/parameters/founderPhoneInclude"
          },
          {
            "$ref": "#/components/parameters/founderPhoneExclude"
          },
          {
            "$ref": "#/components/parameters/openingHoursEmptyFilter"
          },
          {
            "$ref": "#/components/parameters/openingHoursInclude"
          },
          {
            "$ref": "#/components/parameters/openingHoursExclude"
          },
          {
            "$ref": "#/components/parameters/areaServedEmptyFilter"
          },
          {
            "$ref": "#/components/parameters/areaServedInclude"
          },
          {
            "$ref": "#/components/parameters/areaServedExclude"
          },
          {
            "$ref": "#/components/parameters/offersEmptyFilter"
          },
          {
            "$ref": "#/components/parameters/offersInclude"
          },
          {
            "$ref": "#/components/parameters/offersExclude"
          },
          {
            "$ref": "#/components/parameters/brandEmptyFilter"
          },
          {
            "$ref": "#/components/parameters/brandInclude"
          },
          {
            "$ref": "#/components/parameters/brandExclude"
          },
          {
            "$ref": "#/components/parameters/sloganEmptyFilter"
          },
          {
            "$ref": "#/components/parameters/sloganInclude"
          },
          {
            "$ref": "#/components/parameters/sloganExclude"
          },
          {
            "$ref": "#/components/parameters/logoUrlEmptyFilter"
          },
          {
            "$ref": "#/components/parameters/logoUrlInclude"
          },
          {
            "$ref": "#/components/parameters/logoUrlExclude"
          },
          {
            "$ref": "#/components/parameters/socialProfilesEmptyFilter"
          },
          {
            "$ref": "#/components/parameters/socialProfilesInclude"
          },
          {
            "$ref": "#/components/parameters/socialProfilesExclude"
          },
          {
            "$ref": "#/components/parameters/platformEmptyFilter"
          },
          {
            "$ref": "#/components/parameters/platformInclude"
          },
          {
            "$ref": "#/components/parameters/platformExclude"
          },
          {
            "$ref": "#/components/parameters/bookingOpsCrmSoftwareEmptyFilter"
          },
          {
            "$ref": "#/components/parameters/bookingOpsCrmSoftwareInclude"
          },
          {
            "$ref": "#/components/parameters/bookingOpsCrmSoftwareExclude"
          },
          {
            "$ref": "#/components/parameters/reputationChatSoftwareEmptyFilter"
          },
          {
            "$ref": "#/components/parameters/reputationChatSoftwareInclude"
          },
          {
            "$ref": "#/components/parameters/reputationChatSoftwareExclude"
          },
          {
            "$ref": "#/components/parameters/marketingEmailSoftwareEmptyFilter"
          },
          {
            "$ref": "#/components/parameters/marketingEmailSoftwareInclude"
          },
          {
            "$ref": "#/components/parameters/marketingEmailSoftwareExclude"
          },
          {
            "$ref": "#/components/parameters/adPixelsEmptyFilter"
          },
          {
            "$ref": "#/components/parameters/adPixelsInclude"
          },
          {
            "$ref": "#/components/parameters/adPixelsExclude"
          },
          {
            "$ref": "#/components/parameters/paymentFinancingSoftwareEmptyFilter"
          },
          {
            "$ref": "#/components/parameters/paymentFinancingSoftwareInclude"
          },
          {
            "$ref": "#/components/parameters/paymentFinancingSoftwareExclude"
          },
          {
            "$ref": "#/components/parameters/emailPrimaryMxValues"
          },
          {
            "$ref": "#/components/parameters/emailPrimaryMxEmptyFilter"
          },
          {
            "$ref": "#/components/parameters/emailDomainMxValues"
          },
          {
            "$ref": "#/components/parameters/emailDomainMxEmptyFilter"
          },
          {
            "$ref": "#/components/parameters/emailSecurityValues"
          },
          {
            "$ref": "#/components/parameters/emailSecurityEmptyFilter"
          },
          {
            "$ref": "#/components/parameters/addressSourceValues"
          },
          {
            "$ref": "#/components/parameters/addressSourceEmptyFilter"
          },
          {
            "$ref": "#/components/parameters/minNumLocations"
          },
          {
            "$ref": "#/components/parameters/maxNumLocations"
          },
          {
            "$ref": "#/components/parameters/minOpenPositions"
          },
          {
            "$ref": "#/components/parameters/maxOpenPositions"
          },
          {
            "$ref": "#/components/parameters/openPositionsEmptyFilter"
          },
          {
            "$ref": "#/components/parameters/minRatingValue"
          },
          {
            "$ref": "#/components/parameters/maxRatingValue"
          },
          {
            "$ref": "#/components/parameters/redirectFilter"
          },
          {
            "$ref": "#/components/parameters/buyingSignalTypeFilter"
          },
          {
            "$ref": "#/components/parameters/updateReasonFilter"
          },
          {
            "$ref": "#/components/parameters/websiteSchemaFilter"
          },
          {
            "$ref": "#/components/parameters/lastBuyingSignalFrom"
          },
          {
            "$ref": "#/components/parameters/lastUpdatedFrom"
          },
          {
            "$ref": "#/components/parameters/lastBuyingSignalTo"
          },
          {
            "$ref": "#/components/parameters/lastUpdatedTo"
          },
          {
            "$ref": "#/components/parameters/timeScrapedFrom"
          },
          {
            "$ref": "#/components/parameters/timeScrapedTo"
          },
          {
            "$ref": "#/components/parameters/registrationDateFrom"
          },
          {
            "$ref": "#/components/parameters/registrationDateTo"
          },
          {
            "$ref": "#/components/parameters/search"
          },
          {
            "$ref": "#/components/parameters/streetInclude"
          },
          {
            "$ref": "#/components/parameters/streetExclude"
          },
          {
            "$ref": "#/components/parameters/streetEmptyFilter"
          },
          {
            "$ref": "#/components/parameters/cityEmptyFilter"
          },
          {
            "$ref": "#/components/parameters/stateEmptyFilter"
          },
          {
            "$ref": "#/components/parameters/zipEmptyFilter"
          },
          {
            "$ref": "#/components/parameters/phonePrimaryEmptyFilter"
          },
          {
            "$ref": "#/components/parameters/phoneTotalEmptyFilter"
          },
          {
            "$ref": "#/components/parameters/emailPrimaryEmptyFilter"
          },
          {
            "$ref": "#/components/parameters/emailTier1EmptyFilter"
          },
          {
            "$ref": "#/components/parameters/emailTier2EmptyFilter"
          },
          {
            "$ref": "#/components/parameters/emailTier3EmptyFilter"
          },
          {
            "$ref": "#/components/parameters/emailTier4EmptyFilter"
          },
          {
            "$ref": "#/components/parameters/emailTier5EmptyFilter"
          },
          {
            "$ref": "#/components/parameters/emailTier6EmptyFilter"
          },
          {
            "name": "excludePurchased",
            "in": "query",
            "schema": {
              "type": "boolean",
              "default": false
            },
            "description": "When true, omit leads you already received (by lead id or by a shared contact point, as described above) from the page. Default false."
          }
        ],
        "responses": {
          "200": {
            "description": "Preview leads with masked contact information",
            "headers": {
              "X-Lead-Query-Path": {
                "$ref": "#/components/headers/XLeadQueryPath"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "leads": {
                          "type": "array",
                          "items": {
                            "$ref": "#/components/schemas/Lead"
                          },
                          "description": "Leads with masked contact fields (for example '(512) ***-**42' and 'hel*@domain.com'); leads you already received are unmasked and carry contactExported: true"
                        },
                        "pagination": {
                          "type": "object",
                          "properties": {
                            "page": {
                              "type": "integer"
                            },
                            "limit": {
                              "type": "integer"
                            },
                            "total": {
                              "type": "integer"
                            },
                            "pages": {
                              "type": "integer"
                            }
                          }
                        },
                        "databaseType": {
                          "type": "string",
                          "enum": [
                            "other"
                          ],
                          "description": "The lead database that served this request: always 'other' (the Main database)."
                        },
                        "preview": {
                          "type": "boolean",
                          "description": "Always true for preview responses",
                          "example": true
                        },
                        "requiresKeywords": {
                          "type": "boolean",
                          "description": "Present and true when no positive filter was sent; leads is then empty"
                        },
                        "message": {
                          "type": "string",
                          "description": "Present with requiresKeywords: explains which filters count as positive"
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/RateLimitedOrLeadQueryBusy"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        }
      }
    },
    "/leads/other/schema-types": {
      "get": {
        "tags": [
          "Leads"
        ],
        "operationId": "getSchemaTypes",
        "summary": "List available website schema types",
        "description": "Returns a sorted list of all distinct website schema types found in the Main lead database. Use these values with the websiteSchemaFilter parameter on GET /leads. The list is kept up to date in the background, so this normally answers at once. Right after a server restart it can wait up to 55 seconds (or the wait you set with Prefer: wait=N, counted from when the request arrived) for the list to be rebuilt and then answer 503 schema_types_warming (Retry-After: 60).",
        "parameters": [
          {
            "name": "Prefer",
            "in": "header",
            "required": false,
            "schema": {
              "type": "string",
              "pattern": "^wait=[0-9]+$"
            },
            "description": "wait=N (0-55 seconds): how long to wait, counted from when the request arrived, for the list right after a server restart before answering 503 schema_types_warming. Default: 55 seconds. Use a shorter wait when your client times out sooner."
          }
        ],
        "responses": {
          "200": {
            "description": "Array of schema type strings",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "type": "string"
                  },
                  "example": [
                    "AutoDealer",
                    "Dentist",
                    "HomeAndConstructionBusiness",
                    "LegalService",
                    "LocalBusiness",
                    "MedicalBusiness",
                    "Organization",
                    "Restaurant"
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "503": {
            "description": "service_unavailable (a temporary failure, for example the database could not be reached during authentication), or schema_types_warming: the list is still being rebuilt after a server restart. Retry after the Retry-After delay (60 seconds for schema_types_warming).",
            "headers": {
              "Retry-After": {
                "schema": {
                  "type": "integer"
                },
                "description": "Seconds to wait before retrying"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "schemaTypesWarming": {
                    "summary": "The list is being rebuilt",
                    "value": {
                      "error": "schema_types_warming",
                      "message": "The list of website schema types is being rebuilt after a server restart. Retry after the indicated delay."
                    }
                  },
                  "serviceUnavailable": {
                    "summary": "Temporary failure",
                    "value": {
                      "error": "service_unavailable",
                      "message": "Authentication is temporarily unavailable. Retry after the indicated delay."
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/leads/export": {
      "post": {
        "tags": [
          "Leads"
        ],
        "operationId": "exportLeads",
        "summary": "Export leads to CSV/JSON/XLSX",
        "description": "Export leads matching your filters (or specific selectedIds) as CSV, JSON or XLSX files. The file type comes from the export format (formatId, or your default format). Up to 100,000 leads per filter export. Rate limited to 5 exports per minute, shared with the other export endpoints and the email schedule trigger.\n\nCredits (credit plans: Starter, Growth, Scale, Platinum, Enterprise): each new lead exported costs 1 credit. Charging is sticky: a lead you have already received through any channel (dashboard, API, MCP or email schedule) is never charged again, even if its data changed later, and a new lead that shares a primary phone or primary email with a lead you already received is also free. New leads are always ordered first, and they are cut to the smaller of maxCredits and your spendable balance (credit balance plus any overage-budget headroom); when the balance was the limit the response carries X-Credits-Capped: true. Omitting maxCredits therefore caps the request at your spendable balance instead of failing. 402 insufficient_credits only happens if a concurrent request spends the balance between the check and the charge.\n\nFile bodies: with inline true (the default) each file's data field holds the whole file, as plain UTF-8 text for csv and json files and as base64 for xlsx files. Set inline=false to receive download links instead, which is recommended for large exports. Every file is also kept in export history for 90 days.\n\nXLSX is limited to 20,000 rows per file. Any xlsx export that would put more than 20,000 rows in one file returns 400 export_too_large: a single-file export, the largest part of a split format (maxRowsPerFile above 20,000 or not set, or equal_parts / parts_max_rows parts that come out larger), or the includeTotal file, which holds every row. That check runs after the leads are charged and recorded as received. Do not simply repeat the request: an export picks new leads first, so a repeat can charge for different leads. Re-run it with maxCredits 0 (and without excludePurchased) using a csv export format or an xlsx format with splitFiles true, splitMode max_rows, maxRowsPerFile of 20,000 or less and includeTotal false, which returns the leads you already have at no cost, or re-export them with POST /lead-export-history/re-export.\n\nDefault-format files name the buying-signal columns Last Buying Signal and Buying Signal Type (CSV/XLSX headers and JSON keys); they no longer have Last Updated / Update Reason columns. To keep the old header names, pass a formatId whose fieldMappings map lastUpdated and lastUpdatedReason to the names you need.\n\nLong-running request: it answers exactly as described here when the work finishes within about 75 seconds (or within the wait you set with the Prefer header); otherwise it answers 202 Accepted with a jobId. Poll GET /jobs/{jobId}, which then returns exactly this response. Send an Idempotency-Key header so that a retry after a timeout returns the same job instead of running the request again (see the Background Jobs tag).\n\n**After a 202.** The export keeps running: the charge, your lead export history and the Export History files are written whether or not you collect the response. GET /jobs/{jobId} returns the full response (with X-Credits-Capped) for up to 60 minutes after the export finishes (sooner under heavy load or after a server restart), then 410 job_result_expired for about a day, then 404 job_not_found; the files stay in GET /export-history for 90 days either way. Use inline=false for large exports, so the response stays small. To get the result of a running export poll GET /jobs/{jobId}, never send the export again: a new export after this one finished (or with other arguments) picks new leads first and charges for them. The same Idempotency-Key returns this same job, running or finished, and an identical request while it runs does too. If the answer is lost, check GET /export-history and GET /lead-export-history first; a re-run with maxCredits 0 (without excludePurchased) returns leads you already paid for at no charge. A charge_in_progress 429 means another export, lead search or integration push of your account was still recording its charge; nothing was charged, so retry after Retry-After.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "database": {
                    "type": "string",
                    "enum": [
                      "other"
                    ],
                    "default": "other",
                    "description": "Lead database. Only 'other' (the Main database) is available. Kept for backward compatibility: legacy values are still accepted and resolve to the Main database."
                  },
                  "filters": {
                    "type": "object",
                    "description": "Same filter parameters as GET /leads (positiveKeywords, negativeKeywords, stateInclude, buyingSignalTypeFilter, lastBuyingSignalFrom, lastBuyingSignalTo, etc.). buyingSignalTypeFilter accepts a JSON array or a comma-separated string. The older names updateReasonFilter, lastUpdatedFrom and lastUpdatedTo are still accepted (ignored when the new name is also sent)."
                  },
                  "selectedIds": {
                    "type": "array",
                    "items": {
                      "type": "integer"
                    },
                    "description": "Specific lead IDs to export (alternative to filters)"
                  },
                  "formatId": {
                    "type": "integer",
                    "description": "Export format template ID. Uses default format if not specified."
                  },
                  "maxLeads": {
                    "type": "integer",
                    "minimum": 1,
                    "description": "Most leads to include in this export. Leads over the cap, and new leads cut by the credit cap, are counted in overflowCount and kept in the lead reservoir while it has room, then added to your next export that also sets maxLeads. The reservoir is one pool per account, shared with your email schedules and capped at your plan's monthly credit allowance; leads that do not fit are not kept. If omitted, all matching leads are exported, subject to the credit cap."
                  },
                  "maxResults": {
                    "type": "integer",
                    "minimum": 1,
                    "description": "Most leads in this export, new leads plus leads you already received. New leads come first. If omitted, all matching leads are included, subject to the credit cap."
                  },
                  "maxCredits": {
                    "type": "integer",
                    "minimum": 0,
                    "description": "Most credits to spend on this export. Only new leads count. The export is also capped at your spendable balance (credit balance plus any overage-budget headroom), so the effective cap is the smaller of the two; when the balance was the limit the response carries X-Credits-Capped: true. Leads you already received are included free up to maxResults. Set 0 to export only leads you already received, at no cost. If omitted, the export is capped at your spendable balance."
                  },
                  "excludePurchased": {
                    "type": "boolean",
                    "default": false,
                    "description": "When true, export only leads you have not received yet. Leads you already received (matched by lead id, or by a shared contact point as described on GET /leads/preview) are skipped before export. Charging rules are unchanged. Default false."
                  },
                  "inline": {
                    "type": "boolean",
                    "default": true,
                    "description": "When false, each entry in the response `files` array omits its `data` (the whole file body) and carries `exportId` plus `downloadUrl` instead, so you fetch the bytes from GET /export-history/{id}/download when you want them. Recommended for large exports: a 100,000-row export inlined as JSON can be tens of megabytes that most clients write straight to disk anyway. `fileName`, `fileType` and `size` are always present either way. May also be supplied as the query parameter ?inline=false. Default true, which is the original response shape. A file that could not be saved to export history keeps its `data` and has no `downloadUrl`."
                  }
                }
              }
            }
          }
        },
        "parameters": [
          {
            "name": "inline",
            "in": "query",
            "required": false,
            "schema": {
              "type": "boolean",
              "default": true
            },
            "description": "Same as the `inline` body property, for clients that prefer it on the query string. When false the response omits each file's `data` and returns `exportId` + `downloadUrl` instead. A file that could not be saved to export history keeps its `data` and has no `downloadUrl`."
          },
          {
            "$ref": "#/components/parameters/JobIdempotencyKey"
          },
          {
            "$ref": "#/components/parameters/PreferWait"
          }
        ],
        "responses": {
          "200": {
            "description": "Export generated successfully",
            "headers": {
              "X-Credits-Capped": {
                "$ref": "#/components/headers/XCreditsCapped"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "files": {
                          "type": "array",
                          "items": {
                            "type": "object",
                            "properties": {
                              "fileName": {
                                "type": "string"
                              },
                              "fileType": {
                                "type": "string",
                                "description": "csv, json or xlsx"
                              },
                              "data": {
                                "type": "string",
                                "description": "The whole file body, present when inline is true (the default): plain UTF-8 text for csv and json files, base64-encoded bytes for xlsx files. With inline=false it is omitted, except for a file that could not be saved to export history: that file comes back here instead, with no downloadUrl, so a paid export is never lost."
                              },
                              "size": {
                                "type": "integer",
                                "description": "File size in bytes (for xlsx, the size of the decoded file)"
                              },
                              "exportId": {
                                "type": "integer",
                                "description": "This file's own export-history id. Present only when inline=false. A split export writes one history row PER FILE, so this - not the top-level exportId, which is only the first - is what addresses parts 2..N."
                              },
                              "downloadUrl": {
                                "type": "string",
                                "description": "Relative path to fetch this file's bytes: /api/v1/export-history/{exportId}/download. Present only when inline=false and the file was saved to export history, where it can be downloaded for 90 days."
                              }
                            }
                          }
                        },
                        "leadCount": {
                          "type": "integer"
                        },
                        "exportId": {
                          "type": "integer"
                        },
                        "databaseType": {
                          "type": "string",
                          "enum": [
                            "other"
                          ],
                          "description": "The lead database that served this request: always 'other' (the Main database)."
                        },
                        "creditsUsed": {
                          "type": "integer",
                          "description": "Number of credits deducted for net-new leads (credit-plan users only)"
                        },
                        "creditsRemaining": {
                          "type": "integer",
                          "description": "Remaining credit balance after this export (credit-plan users only)"
                        },
                        "maxLeads": {
                          "type": "integer",
                          "description": "Echo of maxLeads parameter (only present when maxLeads was specified)"
                        },
                        "overflowCount": {
                          "type": "integer",
                          "description": "Leads left out of this export by maxLeads or by the credit cap (only present when maxLeads is specified). They are kept in your account's lead reservoir while it has room (one pool shared with your email schedules, up to your plan's monthly credit allowance); leads that do not fit are not kept."
                        },
                        "maxResults": {
                          "type": "integer",
                          "description": "Echo of maxResults parameter (only present when maxResults was specified)"
                        },
                        "maxCredits": {
                          "type": "integer",
                          "description": "Echo of maxCredits parameter (only present when maxCredits was specified)"
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "202": {
            "$ref": "#/components/responses/JobAccepted"
          },
          "400": {
            "description": "bad_request (for example maxLeads, maxResults or maxCredits is not a whole number in range) or export_too_large (an xlsx export that would put more than 20,000 rows in one file: a single file, a split part or the includeTotal file; the leads were already charged and recorded as received, so do not simply repeat the export - see the operation description for the no-cost re-run)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "description": "insufficient_credits (credit plans only). Requests are capped at your spendable balance, so this only happens when a concurrent request from your account spent the balance between the check and the charge. Nothing was charged, and this answer does not use up the Idempotency-Key: retry, with the same key or a new one, after buying credits with POST /purchase-credits if needed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/InsufficientCreditsError"
                },
                "example": {
                  "error": "insufficient_credits",
                  "message": "This export requires 120 credits for new leads but you only have 80 available. Reduce the number of leads or purchase more credits via POST /api/v1/purchase-credits.",
                  "creditsRemaining": 80
                }
              }
            }
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "409": {
            "description": "idempotency_key_reused: this Idempotency-Key was already used with a different request on this endpoint. Use a new key for a new request.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "idempotencyKeyReused": {
                    "summary": "Idempotency-Key reused",
                    "value": {
                      "error": "idempotency_key_reused",
                      "message": "This Idempotency-Key was already used for a different request. Use a new key for a new request."
                    }
                  }
                }
              }
            }
          },
          "410": {
            "$ref": "#/components/responses/JobResultExpired"
          },
          "429": {
            "description": "Too many requests, for one of 4 reasons. (1) a rate limit was exceeded (error 'Rate limit exceeded', with a retryAfter field equal to the Retry-After header). (2) lead search capacity is busy (error 'lead_query_capacity_busy', Retry-After: 10). (3) too many long-running requests are running at once, for your account or on the server (error 'job_capacity_busy', Retry-After: 30). (4) another export, lead search or integration push of your account is still recording its charge and did not finish within 10 minutes (error 'charge_in_progress', Retry-After: 30). None of them charged anything. Tell them apart by the body's error field. Retry after the Retry-After delay; a 429 does not use up an Idempotency-Key, so the retry may reuse it.",
            "headers": {
              "Retry-After": {
                "schema": {
                  "type": "integer"
                },
                "description": "Seconds to wait before retrying"
              },
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests allowed in the window"
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left in the window (0 on a 'Rate limit exceeded' 429; the other 429s show the normal count)"
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "string",
                  "format": "date-time"
                },
                "description": "When the window resets, as an ISO 8601 date-time (for example 2026-10-07T14:05:00.000Z)"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "oneOf": [
                    {
                      "$ref": "#/components/schemas/RateLimitError"
                    },
                    {
                      "$ref": "#/components/schemas/Error"
                    }
                  ]
                },
                "examples": {
                  "rateLimited": {
                    "summary": "Rate limit exceeded",
                    "value": {
                      "error": "Rate limit exceeded",
                      "message": "API rate limit exceeded. Please slow down your requests.",
                      "retryAfter": 42
                    }
                  },
                  "leadQueryCapacityBusy": {
                    "summary": "Lead search capacity busy",
                    "value": {
                      "error": "lead_query_capacity_busy",
                      "message": "Lead search capacity is currently busy. Retry after the indicated delay, or narrow your filters so each query returns faster."
                    }
                  },
                  "jobCapacityBusy": {
                    "summary": "Too many long-running requests",
                    "value": {
                      "error": "job_capacity_busy",
                      "message": "The server is busy with other long-running requests. Retry after the indicated delay."
                    }
                  },
                  "chargeInProgress": {
                    "summary": "Another charge is being recorded",
                    "value": {
                      "error": "charge_in_progress",
                      "message": "Another export or push for your account is still being recorded. Please try again in a minute."
                    }
                  }
                }
              }
            }
          },
          "500": {
            "description": "internal_error. The export may have been partly processed (credits charged and leads recorded as received), so check GET /export-history and creditsRemaining with GET /me before retrying. A retry is not charged again for leads already recorded as received, but it can charge for other new leads, so check first. Also error job_timeout when an export is still running after 6 hours (its files appear in GET /export-history if it completes), and result_too_large when a response collected with GET /jobs/{jobId} was too large to keep (download the files from GET /export-history). With the same Idempotency-Key, a retry returns this same answer instead of exporting again; a new key runs a new export.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        }
      }
    },
    "/filter-presets": {
      "get": {
        "tags": [
          "Filter Presets"
        ],
        "operationId": "listFilterPresets",
        "summary": "List your filter presets",
        "description": "Retrieve all saved filter presets for your account.",
        "responses": {
          "200": {
            "description": "Filter presets retrieved",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/FilterPreset"
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        }
      },
      "post": {
        "tags": [
          "Filter Presets"
        ],
        "operationId": "createFilterPreset",
        "summary": "Create a filter preset",
        "description": "Save a new reusable filter configuration.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/FilterPresetInput"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Filter preset created",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/FilterPreset"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        }
      }
    },
    "/filter-presets/{id}": {
      "delete": {
        "tags": [
          "Filter Presets"
        ],
        "operationId": "deleteFilterPreset",
        "summary": "Delete a filter preset",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "$ref": "#/components/responses/DeleteSuccess"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        }
      }
    },
    "/keyword-lists": {
      "get": {
        "tags": [
          "Keyword Lists"
        ],
        "operationId": "listKeywordLists",
        "summary": "List your keyword lists",
        "description": "Retrieve all saved keyword lists.",
        "responses": {
          "200": {
            "description": "Keyword lists retrieved",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/KeywordList"
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        }
      },
      "post": {
        "tags": [
          "Keyword Lists"
        ],
        "operationId": "createKeywordList",
        "summary": "Create a keyword list",
        "description": "Create a keyword list. Required: name (trimmed, 1-255 characters), type ('positive' for include keywords or 'negative' for exclude keywords) and keywords (a non-empty array of strings). sourceCategories is optional: an array of strings, or null. Any other field is ignored.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/KeywordListInput"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Keyword list created",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/KeywordList"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "bad_request when the body is missing or not a JSON object; validation_error when name is missing, empty or longer than 255 characters, type is not 'positive' or 'negative', keywords is not a non-empty array of strings, or sourceCategories is neither an array of strings nor null. The message names the problem.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        }
      }
    },
    "/keyword-lists/{id}": {
      "put": {
        "tags": [
          "Keyword Lists"
        ],
        "operationId": "updateKeywordList",
        "summary": "Update a keyword list",
        "description": "Update a keyword list. Send any of name, type, keywords (at least one keyword) and sourceCategories; any other field is ignored.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/KeywordListUpdate"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Keyword list updated",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/KeywordList"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        }
      },
      "delete": {
        "tags": [
          "Keyword Lists"
        ],
        "operationId": "deleteKeywordList",
        "summary": "Delete a keyword list",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "$ref": "#/components/responses/DeleteSuccess"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        }
      }
    },
    "/email-schedules": {
      "get": {
        "tags": [
          "Email Schedules"
        ],
        "operationId": "listEmailSchedules",
        "summary": "List your email schedules",
        "description": "Retrieve all configured email delivery schedules.",
        "responses": {
          "200": {
            "description": "Email schedules retrieved",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/EmailSchedule"
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        }
      },
      "post": {
        "tags": [
          "Email Schedules"
        ],
        "operationId": "createEmailSchedule",
        "summary": "Create an email schedule",
        "description": "Set up automated email delivery of leads matching a filter preset. Requires an active subscription.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/EmailScheduleInput"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Email schedule created",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/EmailSchedule"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        }
      }
    },
    "/email-schedules/{id}": {
      "patch": {
        "tags": [
          "Email Schedules"
        ],
        "operationId": "updateEmailSchedule",
        "summary": "Update an email schedule",
        "description": "Update schedule settings. Only provided fields are updated.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/EmailScheduleUpdate"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Email schedule updated",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/EmailSchedule"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "bad_request: for example intervalValue is not a whole number from 1 to 720, intervalUnit is not 'hours' or 'days', the filter preset or export format is not yours, or a recipient is invalid",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        }
      },
      "delete": {
        "tags": [
          "Email Schedules"
        ],
        "operationId": "deleteEmailSchedule",
        "summary": "Delete an email schedule",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "$ref": "#/components/responses/DeleteSuccess"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        }
      }
    },
    "/export-formats": {
      "get": {
        "tags": [
          "Export Formats"
        ],
        "operationId": "listExportFormats",
        "summary": "List your export formats",
        "description": "Retrieve all custom export format templates.",
        "responses": {
          "200": {
            "description": "Export formats retrieved",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/ExportFormat"
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        }
      },
      "post": {
        "tags": [
          "Export Formats"
        ],
        "operationId": "createExportFormat",
        "summary": "Create an export format",
        "description": "Create a custom export format template with column mapping and file settings.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ExportFormatInput"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Export format created",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/ExportFormat"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        }
      }
    },
    "/export-formats/{id}": {
      "get": {
        "tags": [
          "Export Formats"
        ],
        "operationId": "getExportFormat",
        "summary": "Get an export format",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Export format retrieved",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/ExportFormat"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        }
      },
      "patch": {
        "tags": [
          "Export Formats"
        ],
        "operationId": "updateExportFormat",
        "summary": "Update an export format",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ExportFormatUpdate"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Export format updated",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/ExportFormat"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        }
      },
      "delete": {
        "tags": [
          "Export Formats"
        ],
        "operationId": "deleteExportFormat",
        "summary": "Delete an export format",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "$ref": "#/components/responses/DeleteSuccess"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        }
      }
    },
    "/export-formats/{id}/set-default": {
      "post": {
        "tags": [
          "Export Formats"
        ],
        "operationId": "setDefaultExportFormat",
        "summary": "Set default export format",
        "description": "Set an export format as your default for all exports.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Default export format set",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "message": {
                          "type": "string"
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        }
      }
    },
    "/export-history": {
      "get": {
        "tags": [
          "Export History"
        ],
        "operationId": "listExportHistory",
        "summary": "List your export history",
        "description": "View past export files, newest first, with details including date, row count, and filters used. Each file can be downloaded for 90 days after the export: downloadAvailable says whether it can still be downloaded, expiresAt is the end of its download window, and unavailableReason explains why not (expired or not_stored).",
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "default": 50,
              "minimum": 0,
              "maximum": 200
            },
            "description": "Number of entries to return (0-200)"
          }
        ],
        "responses": {
          "200": {
            "description": "Export history retrieved",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/ExportHistoryEntry"
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        }
      }
    },
    "/export-history/{id}/download": {
      "get": {
        "tags": [
          "Export History"
        ],
        "operationId": "downloadExport",
        "summary": "Download a previous export",
        "description": "Re-download a previously exported file. Files are kept for 90 days after the export, then deleted; nothing is regenerated. Returns the raw file with its Content-Type and an attachment Content-Disposition header. To get a short-lived direct link instead of the bytes, use GET /export-history/{id}/download-url.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            },
            "description": "Export history entry ID (from GET /export-history or the exportId of POST /leads/export)"
          }
        ],
        "responses": {
          "200": {
            "description": "File download",
            "content": {
              "text/csv": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              },
              "application/json": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              },
              "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "410": {
            "description": "The export file has expired (files are kept for 90 days) or was not saved for download",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string",
                      "description": "Error code (gone)"
                    },
                    "reason": {
                      "type": "string",
                      "enum": [
                        "expired",
                        "not_stored"
                      ],
                      "description": "expired: past the 90-day download window. not_stored: the file was not saved for download."
                    },
                    "message": {
                      "type": "string",
                      "description": "Human-readable error message, including how to get the leads again"
                    }
                  }
                },
                "example": {
                  "error": "gone",
                  "reason": "expired",
                  "message": "This export file expired on December 30, 2026. Export files can be downloaded for 90 days after the export. To get these leads again, re-export them with POST /api/v1/lead-export-history/re-export (no credit cost) or run a new export with POST /api/v1/leads/export."
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "503": {
            "description": "File storage is temporarily unavailable. Retry after the number of seconds in Retry-After.",
            "headers": {
              "Retry-After": {
                "schema": {
                  "type": "integer"
                },
                "description": "Seconds to wait before retrying"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "service_unavailable",
                  "message": "Export file downloads are temporarily unavailable. Please try again in a minute."
                }
              }
            }
          }
        }
      }
    },
    "/export-history/{id}/download-url": {
      "get": {
        "tags": [
          "Export History"
        ],
        "operationId": "getExportDownloadUrl",
        "summary": "Get a direct download link for a previous export",
        "description": "Returns a short-lived direct download link for a previously exported file, plus the file details, instead of the file itself. The link is valid for expiresInSeconds seconds (15 minutes); request a new one when it runs out. Send no Authorization header to downloadUrl: the link carries its own signature. Files are kept for 90 days after the export (fileExpiresAt).",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            },
            "description": "Export history entry ID (from GET /export-history or the exportId of POST /leads/export)"
          }
        ],
        "responses": {
          "200": {
            "description": "Download link created",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "downloadUrl": {
                          "type": "string",
                          "description": "Direct download link. Fetch it with no Authorization header."
                        },
                        "expiresInSeconds": {
                          "type": "integer",
                          "description": "Seconds until downloadUrl stops working (900)"
                        },
                        "fileName": {
                          "type": "string"
                        },
                        "fileType": {
                          "type": "string",
                          "enum": [
                            "csv",
                            "json",
                            "xlsx"
                          ]
                        },
                        "fileSize": {
                          "type": "integer",
                          "description": "File size in bytes"
                        },
                        "leadCount": {
                          "type": "integer"
                        },
                        "fileExpiresAt": {
                          "type": "string",
                          "format": "date-time",
                          "description": "When the file stops being downloadable (end of its 90-day window)"
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "410": {
            "description": "The export file has expired (files are kept for 90 days) or was not saved for download",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string",
                      "description": "Error code (gone)"
                    },
                    "reason": {
                      "type": "string",
                      "enum": [
                        "expired",
                        "not_stored"
                      ],
                      "description": "expired: past the 90-day download window. not_stored: the file was not saved for download."
                    },
                    "message": {
                      "type": "string",
                      "description": "Human-readable error message, including how to get the leads again"
                    }
                  }
                },
                "example": {
                  "error": "gone",
                  "reason": "expired",
                  "message": "This export file expired on December 30, 2026. Export files can be downloaded for 90 days after the export. To get these leads again, re-export them with POST /api/v1/lead-export-history/re-export (no credit cost) or run a new export with POST /api/v1/leads/export."
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "503": {
            "description": "File storage is temporarily unavailable. Retry after the number of seconds in Retry-After.",
            "headers": {
              "Retry-After": {
                "schema": {
                  "type": "integer"
                },
                "description": "Seconds to wait before retrying"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "service_unavailable",
                  "message": "Export file downloads are temporarily unavailable. Please try again in a minute."
                }
              }
            }
          }
        }
      }
    },
    "/lead-export-history": {
      "get": {
        "tags": [
          "Lead Export History"
        ],
        "operationId": "listLeadExportHistory",
        "summary": "List lead-level export history",
        "description": "View lead-level export tracking records. Each entry contains the lead data as it was when exported (snapshot), plus the export metadata. Re-exports and refreshes are always free.",
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "default": 50,
              "maximum": 200,
              "minimum": 1
            },
            "description": "Number of entries per page (1-200)"
          },
          {
            "name": "offset",
            "in": "query",
            "schema": {
              "type": "integer",
              "default": 0,
              "minimum": 0
            },
            "description": "Pagination offset"
          },
          {
            "name": "databaseType",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "other"
              ]
            },
            "description": "Filter by lead database. Only 'other' (the Main database) is available. Legacy values are still accepted and resolve to the Main database. Omit for no filter."
          },
          {
            "name": "dateFrom",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "date"
            },
            "description": "Filter exports on or after this date (YYYY-MM-DD)"
          },
          {
            "name": "dateTo",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "date"
            },
            "description": "Filter exports on or before this date (YYYY-MM-DD)"
          },
          {
            "name": "exportMethod",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "dashboard",
                "api",
                "email_schedule",
                "crm_push",
                "enrichment"
              ]
            },
            "description": "Filter by export channel"
          }
        ],
        "responses": {
          "200": {
            "description": "Lead export history retrieved",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "description": "One lead you received: the tracking record plus the lead data",
                        "properties": {
                          "id": {
                            "type": "integer",
                            "description": "Tracking id (use with refresh and re-export)"
                          },
                          "leadId": {
                            "type": "integer"
                          },
                          "databaseType": {
                            "type": "string",
                            "description": "Always 'other' (the Main database) for current exports"
                          },
                          "exportedAt": {
                            "type": "string",
                            "format": "date-time"
                          },
                          "leadLastUpdated": {
                            "type": "string",
                            "format": "date-time",
                            "nullable": true
                          },
                          "exportMethod": {
                            "type": "string",
                            "enum": [
                              "dashboard",
                              "api",
                              "email_schedule",
                              "crm_push",
                              "enrichment"
                            ],
                            "nullable": true
                          },
                          "leadSnapshot": {
                            "type": "object",
                            "nullable": true,
                            "description": "The lead data as it was when you received it (or as of the last refresh)"
                          },
                          "lastChangedFields": {
                            "nullable": true,
                            "description": "Fields that changed at the last refresh"
                          },
                          "lead": {
                            "type": "object",
                            "nullable": true,
                            "description": "The snapshot, or the live lead when no snapshot exists"
                          }
                        }
                      }
                    },
                    "totalCount": {
                      "type": "integer",
                      "description": "All matching entries, before limit and offset"
                    },
                    "limit": {
                      "type": "integer"
                    },
                    "offset": {
                      "type": "integer"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "bad_request: limit must be an integer 1-200, offset a non-negative integer, and dateFrom/dateTo valid dates",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        }
      }
    },
    "/lead-export-history/ids": {
      "get": {
        "tags": [
          "Lead Export History"
        ],
        "operationId": "getLeadExportHistoryIds",
        "summary": "Get all matching export tracking IDs",
        "description": "Returns all tracking record IDs matching the given filters, with no pagination. Use this to build a list of IDs for bulk refresh or re-export operations.",
        "parameters": [
          {
            "name": "databaseType",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "other"
              ]
            },
            "description": "Filter by lead database. Only 'other' (the Main database) is available. Legacy values are still accepted and resolve to the Main database. Omit for no filter."
          },
          {
            "name": "dateFrom",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "date"
            },
            "description": "Filter by export date (from)"
          },
          {
            "name": "dateTo",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "date"
            },
            "description": "Filter by export date (to)"
          },
          {
            "name": "exportMethod",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "dashboard",
                "api",
                "email_schedule",
                "crm_push",
                "enrichment"
              ]
            },
            "description": "Filter by export channel"
          }
        ],
        "responses": {
          "200": {
            "description": "IDs retrieved",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "type": "integer"
                      },
                      "description": "Matching tracking ids"
                    },
                    "totalCount": {
                      "type": "integer"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "bad_request: dateFrom/dateTo must be valid dates",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        }
      }
    },
    "/lead-export-history/re-export": {
      "post": {
        "tags": [
          "Lead Export History"
        ],
        "operationId": "reExportLeadHistory",
        "summary": "Re-export leads from stored snapshots",
        "description": "Re-export previously exported leads using the stored snapshot data. No live data refresh occurs. No credits charged. All selected IDs must be from the same database type. Up to 5,000 tracking ids per request. Counts toward the 5 per minute export rate limit. Always returns a single file (export format file splitting is not applied). Repeated tracking ids are exported once.\n\nLong-running request: it answers exactly as described here when the work finishes within about 75 seconds (or within the wait you set with the Prefer header); otherwise it answers 202 Accepted with a jobId. Poll GET /jobs/{jobId}, which then returns exactly this response. Send an Idempotency-Key header so that a retry after a timeout returns the same job instead of running the request again (see the Background Jobs tag). After a 202 the file is kept for pickup with GET /jobs/{jobId} for up to 60 minutes after it is ready; it is never saved to Export History. After that, run the re-export again; it is free.",
        "parameters": [
          {
            "$ref": "#/components/parameters/JobIdempotencyKey"
          },
          {
            "$ref": "#/components/parameters/PreferWait"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "trackingIds"
                ],
                "properties": {
                  "trackingIds": {
                    "type": "array",
                    "items": {
                      "type": "integer"
                    },
                    "description": "Array of tracking IDs to re-export (from listLeadExportHistory or getLeadExportHistoryIds)"
                  },
                  "formatId": {
                    "type": "integer",
                    "description": "Optional export format ID. Defaults to CSV with all fields."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Export file download",
            "content": {
              "text/csv": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              },
              "application/json": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              },
              "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              }
            }
          },
          "202": {
            "$ref": "#/components/responses/JobAccepted"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "description": "idempotency_key_reused: this Idempotency-Key was already used with a different request on this endpoint. Use a new key for a new request.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "idempotencyKeyReused": {
                    "summary": "Idempotency-Key reused",
                    "value": {
                      "error": "idempotency_key_reused",
                      "message": "This Idempotency-Key was already used for a different request. Use a new key for a new request."
                    }
                  }
                }
              }
            }
          },
          "410": {
            "$ref": "#/components/responses/JobResultExpired"
          },
          "429": {
            "description": "Too many requests, for one of 2 reasons. (1) a rate limit was exceeded (error 'Rate limit exceeded', with a retryAfter field equal to the Retry-After header). (2) too many long-running requests are running at once, for your account or on the server (error 'job_capacity_busy', Retry-After: 30). Nothing was run. Tell them apart by the body's error field. Retry after the Retry-After delay; a 429 does not use up an Idempotency-Key, so the retry may reuse it.",
            "headers": {
              "Retry-After": {
                "schema": {
                  "type": "integer"
                },
                "description": "Seconds to wait before retrying"
              },
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests allowed in the window"
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left in the window (0 on a 'Rate limit exceeded' 429; the other 429s show the normal count)"
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "string",
                  "format": "date-time"
                },
                "description": "When the window resets, as an ISO 8601 date-time (for example 2026-10-07T14:05:00.000Z)"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "oneOf": [
                    {
                      "$ref": "#/components/schemas/RateLimitError"
                    },
                    {
                      "$ref": "#/components/schemas/Error"
                    }
                  ]
                },
                "examples": {
                  "rateLimited": {
                    "summary": "Rate limit exceeded",
                    "value": {
                      "error": "Rate limit exceeded",
                      "message": "API rate limit exceeded. Please slow down your requests.",
                      "retryAfter": 42
                    }
                  },
                  "jobCapacityBusy": {
                    "summary": "Too many long-running requests",
                    "value": {
                      "error": "job_capacity_busy",
                      "message": "The server is busy with other long-running requests. Retry after the indicated delay."
                    }
                  }
                }
              }
            }
          },
          "500": {
            "description": "internal_error ('Failed to generate export file' or 'Failed to re-export leads'), or job_timeout when the re-export is still running after 60 minutes. No credits are charged.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "internal_error",
                  "message": "Failed to generate export file"
                }
              }
            }
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        }
      }
    },
    "/lead-export-history/refresh": {
      "post": {
        "tags": [
          "Lead Export History"
        ],
        "operationId": "refreshLeadExportHistory",
        "summary": "Refresh lead snapshots with live data",
        "description": "Updates stored lead snapshots with the latest data from the live database. No credits charged. Accepts either an explicit list of tracking IDs or a filter object to select all matching records. Up to 5,000 tracking ids per request (a filters selection counts too). Counts toward the 5 per minute export rate limit. A refresh that finds newer data for a lead sends lead.updated to your integrations, unless the lead is on your export blacklist. When the selection comes from filters (no trackingIds array), an invalid filters.dateFrom or filters.dateTo (not a valid date with a year from 1 to 9999) returns 400 bad_request ('filters.dateFrom must be a valid date (e.g. 2026-08-18)', or the same for filters.dateTo), and a filters.databaseType or filters.exportMethod that is not a string returns 400 bad_request ('filters.databaseType must be a string' or 'filters.exportMethod must be a string'). With a trackingIds array the filters are ignored. Repeated tracking ids are refreshed once.\n\nLong-running request: it answers exactly as described here when the work finishes within about 75 seconds (or within the wait you set with the Prefer header); otherwise it answers 202 Accepted with a jobId. Poll GET /jobs/{jobId}, which then returns exactly this response. Send an Idempotency-Key header so that a retry after a timeout returns the same job instead of running the request again (see the Background Jobs tag). After a 202 the refresh keeps running; its result is kept for up to 60 minutes after it finishes (then 410 job_result_expired; the refreshed snapshots stay in GET /lead-export-history).",
        "parameters": [
          {
            "$ref": "#/components/parameters/JobIdempotencyKey"
          },
          {
            "$ref": "#/components/parameters/PreferWait"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "trackingIds": {
                    "type": "array",
                    "items": {
                      "type": "integer"
                    },
                    "description": "Explicit list of tracking IDs to refresh"
                  },
                  "filters": {
                    "type": "object",
                    "description": "Filter object to select all matching records (alternative to trackingIds)",
                    "properties": {
                      "databaseType": {
                        "type": "string",
                        "enum": [
                          "other"
                        ],
                        "description": "Filter by lead database. Only 'other' (the Main database) is available. Legacy values are still accepted and resolve to the Main database. Omit for no filter."
                      },
                      "dateFrom": {
                        "type": "string",
                        "format": "date"
                      },
                      "dateTo": {
                        "type": "string",
                        "format": "date"
                      },
                      "exportMethod": {
                        "type": "string",
                        "enum": [
                          "dashboard",
                          "api",
                          "email_schedule",
                          "crm_push",
                          "enrichment"
                        ]
                      }
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Refresh results",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "refreshed": {
                          "type": "integer",
                          "description": "Number of snapshots successfully updated"
                        },
                        "results": {
                          "type": "array",
                          "items": {
                            "type": "object",
                            "properties": {
                              "trackingId": {
                                "type": "integer"
                              },
                              "refreshed": {
                                "type": "boolean"
                              },
                              "changed": {
                                "type": "boolean",
                                "description": "True when the live lead has a newer buying signal than the stored snapshot (such an entry sends lead.updated to your integrations, unless the lead is on your export blacklist)"
                              },
                              "changedFields": {
                                "type": "array",
                                "items": {
                                  "type": "string"
                                },
                                "description": "Fields that changed since the snapshot"
                              }
                            }
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "202": {
            "$ref": "#/components/responses/JobAccepted"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "409": {
            "description": "idempotency_key_reused: this Idempotency-Key was already used with a different request on this endpoint. Use a new key for a new request.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "idempotencyKeyReused": {
                    "summary": "Idempotency-Key reused",
                    "value": {
                      "error": "idempotency_key_reused",
                      "message": "This Idempotency-Key was already used for a different request. Use a new key for a new request."
                    }
                  }
                }
              }
            }
          },
          "410": {
            "$ref": "#/components/responses/JobResultExpired"
          },
          "429": {
            "description": "Too many requests, for one of 2 reasons. (1) a rate limit was exceeded (error 'Rate limit exceeded', with a retryAfter field equal to the Retry-After header). (2) too many long-running requests are running at once, for your account or on the server (error 'job_capacity_busy', Retry-After: 30). Nothing was refreshed. Tell them apart by the body's error field. Retry after the Retry-After delay; a 429 does not use up an Idempotency-Key, so the retry may reuse it.",
            "headers": {
              "Retry-After": {
                "schema": {
                  "type": "integer"
                },
                "description": "Seconds to wait before retrying"
              },
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests allowed in the window"
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left in the window (0 on a 'Rate limit exceeded' 429; the other 429s show the normal count)"
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "string",
                  "format": "date-time"
                },
                "description": "When the window resets, as an ISO 8601 date-time (for example 2026-10-07T14:05:00.000Z)"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "oneOf": [
                    {
                      "$ref": "#/components/schemas/RateLimitError"
                    },
                    {
                      "$ref": "#/components/schemas/Error"
                    }
                  ]
                },
                "examples": {
                  "rateLimited": {
                    "summary": "Rate limit exceeded",
                    "value": {
                      "error": "Rate limit exceeded",
                      "message": "API rate limit exceeded. Please slow down your requests.",
                      "retryAfter": 42
                    }
                  },
                  "jobCapacityBusy": {
                    "summary": "Too many long-running requests",
                    "value": {
                      "error": "job_capacity_busy",
                      "message": "The server is busy with other long-running requests. Retry after the indicated delay."
                    }
                  }
                }
              }
            }
          },
          "500": {
            "description": "internal_error, or job_timeout when the refresh is still running after 2 hours. No credits are charged; check the entries with GET /lead-export-history before refreshing them again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "internal_error",
                  "message": "Failed to refresh lead export history"
                }
              }
            }
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        }
      }
    },
    "/lead-export-history/refresh-and-export": {
      "post": {
        "tags": [
          "Lead Export History"
        ],
        "operationId": "refreshAndExportLeadHistory",
        "summary": "Refresh snapshots and export as file",
        "description": "Refreshes stored lead snapshots with live data then immediately exports them as a downloadable file. All selected IDs must be from the same database type. No credits charged. Accepts either trackingIds or a filters object. Up to 5,000 tracking ids per request (a filters selection counts too). Counts toward the 5 per minute export rate limit. Always returns a single file (export format file splitting is not applied). Everything is checked before any entry changes: a 400 or 404 response leaves the entries unchanged (snapshots, leadLastUpdated and lastChangedFields). A successful call refreshes the snapshots and clears lastChangedFields on the selected entries. A refresh that finds newer data for a lead sends lead.updated to your integrations, unless the lead is on your export blacklist. When the selection comes from filters (no trackingIds array), an invalid filters.dateFrom or filters.dateTo (not a valid date with a year from 1 to 9999) returns 400 bad_request ('filters.dateFrom must be a valid date (e.g. 2026-08-18)', or the same for filters.dateTo), and a filters.databaseType or filters.exportMethod that is not a string returns 400 bad_request ('filters.databaseType must be a string' or 'filters.exportMethod must be a string'). With a trackingIds array the filters are ignored. Repeated tracking ids are refreshed and exported once.\n\nLong-running request: it answers exactly as described here when the work finishes within about 75 seconds (or within the wait you set with the Prefer header); otherwise it answers 202 Accepted with a jobId. Poll GET /jobs/{jobId}, which then returns exactly this response. Send an Idempotency-Key header so that a retry after a timeout returns the same job instead of running the request again (see the Background Jobs tag). After a 202 the file is kept for pickup with GET /jobs/{jobId} for up to 60 minutes after it is ready; it is never saved to Export History. The snapshots are refreshed when the job finishes; lastChangedFields are cleared only when the file is delivered to you (in this response or by GET /jobs/{jobId}), so a file you never collect leaves them set. After the 60 minutes, run this request again (it is free) or get the same rows free with POST /lead-export-history/re-export.",
        "parameters": [
          {
            "$ref": "#/components/parameters/JobIdempotencyKey"
          },
          {
            "$ref": "#/components/parameters/PreferWait"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "trackingIds": {
                    "type": "array",
                    "items": {
                      "type": "integer"
                    },
                    "description": "Explicit list of tracking IDs"
                  },
                  "filters": {
                    "type": "object",
                    "description": "Filter object (alternative to trackingIds)",
                    "properties": {
                      "databaseType": {
                        "type": "string",
                        "enum": [
                          "other"
                        ],
                        "description": "Filter by lead database. Only 'other' (the Main database) is available. Legacy values are still accepted and resolve to the Main database. Omit for no filter."
                      },
                      "dateFrom": {
                        "type": "string"
                      },
                      "dateTo": {
                        "type": "string"
                      },
                      "exportMethod": {
                        "type": "string"
                      }
                    }
                  },
                  "formatId": {
                    "type": "integer",
                    "description": "Optional export format ID. Defaults to CSV with all fields."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Refreshed export file download",
            "content": {
              "text/csv": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              },
              "application/json": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              },
              "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              }
            }
          },
          "202": {
            "$ref": "#/components/responses/JobAccepted"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "description": "idempotency_key_reused: this Idempotency-Key was already used with a different request on this endpoint. Use a new key for a new request.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "idempotencyKeyReused": {
                    "summary": "Idempotency-Key reused",
                    "value": {
                      "error": "idempotency_key_reused",
                      "message": "This Idempotency-Key was already used for a different request. Use a new key for a new request."
                    }
                  }
                }
              }
            }
          },
          "410": {
            "$ref": "#/components/responses/JobResultExpired"
          },
          "429": {
            "description": "Too many requests, for one of 2 reasons. (1) a rate limit was exceeded (error 'Rate limit exceeded', with a retryAfter field equal to the Retry-After header). (2) too many long-running requests are running at once, for your account or on the server (error 'job_capacity_busy', Retry-After: 30). Nothing was refreshed. Tell them apart by the body's error field. Retry after the Retry-After delay; a 429 does not use up an Idempotency-Key, so the retry may reuse it.",
            "headers": {
              "Retry-After": {
                "schema": {
                  "type": "integer"
                },
                "description": "Seconds to wait before retrying"
              },
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests allowed in the window"
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left in the window (0 on a 'Rate limit exceeded' 429; the other 429s show the normal count)"
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "string",
                  "format": "date-time"
                },
                "description": "When the window resets, as an ISO 8601 date-time (for example 2026-10-07T14:05:00.000Z)"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "oneOf": [
                    {
                      "$ref": "#/components/schemas/RateLimitError"
                    },
                    {
                      "$ref": "#/components/schemas/Error"
                    }
                  ]
                },
                "examples": {
                  "rateLimited": {
                    "summary": "Rate limit exceeded",
                    "value": {
                      "error": "Rate limit exceeded",
                      "message": "API rate limit exceeded. Please slow down your requests.",
                      "retryAfter": 42
                    }
                  },
                  "jobCapacityBusy": {
                    "summary": "Too many long-running requests",
                    "value": {
                      "error": "job_capacity_busy",
                      "message": "The server is busy with other long-running requests. Retry after the indicated delay."
                    }
                  }
                }
              }
            }
          },
          "500": {
            "description": "internal_error ('Failed to generate export file' or 'Failed to refresh and export leads'), or job_timeout when the job is still running after 2 hours. No credits are charged.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "internal_error",
                  "message": "Failed to generate export file"
                }
              }
            }
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        }
      }
    },
    "/settings/database": {
      "get": {
        "tags": [
          "Account Settings"
        ],
        "operationId": "getDatabaseSettings",
        "summary": "Get database settings",
        "description": "Returns the lead database your account uses. Every account uses the Main database, so currentDatabase is always 'other'. canSwitch and daysRemaining are kept for backward compatibility.",
        "responses": {
          "200": {
            "description": "Database settings retrieved",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "currentDatabase": {
                          "type": "string",
                          "enum": [
                            "other"
                          ],
                          "description": "Always 'other' (the Main database)."
                        },
                        "canSwitch": {
                          "type": "boolean",
                          "description": "Always true. Kept for backward compatibility."
                        },
                        "daysRemaining": {
                          "type": "integer",
                          "description": "Always 0. Kept for backward compatibility."
                        },
                        "lastSwitchedAt": {
                          "type": "string",
                          "format": "date-time",
                          "nullable": true,
                          "description": "When the account's lead database last changed, or null if it never did."
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        }
      }
    },
    "/ai/suggest-categories": {
      "post": {
        "tags": [
          "AI Features"
        ],
        "operationId": "suggestCategories",
        "summary": "AI category suggestions",
        "description": "Get AI-powered SMB category suggestions based on your company profile. Optionally analyzes your company website for better results. Rate limited to 5 requests per minute.\n\nLong-running request: it answers exactly as described here when the work finishes within about 75 seconds (or within the wait you set with the Prefer header); otherwise it answers 202 Accepted with a jobId. Poll GET /jobs/{jobId}, which then returns exactly this response. Send an Idempotency-Key header so that a retry after a timeout returns the same job instead of running the request again (see the Background Jobs tag). Nothing is charged. After a 202, the suggestions are kept for up to 60 minutes after they finish (then 410 job_result_expired for about a day, later 404 job_not_found: run the request again, it is free).",
        "parameters": [
          {
            "$ref": "#/components/parameters/JobIdempotencyKey"
          },
          {
            "$ref": "#/components/parameters/PreferWait"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "companyName",
                  "companyDescription",
                  "productService"
                ],
                "properties": {
                  "companyName": {
                    "type": "string",
                    "description": "Your company name",
                    "example": "Acme Business Solutions"
                  },
                  "companyDescription": {
                    "type": "string",
                    "description": "What your company does",
                    "example": "We provide accounting software and bookkeeping services to small businesses"
                  },
                  "productService": {
                    "type": "string",
                    "description": "Specific product or service you sell",
                    "example": "Cloud accounting software, payroll services, tax preparation"
                  },
                  "companyWebsite": {
                    "type": "string",
                    "description": "Your company website URL (optional, used for AI analysis)",
                    "example": "https://acmebusinesssolutions.com"
                  },
                  "excludeCategories": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    },
                    "description": "Categories to exclude from suggestions"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Category suggestions generated",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "categories": {
                          "type": "array",
                          "items": {
                            "type": "string"
                          },
                          "description": "Suggested SMB categories"
                        },
                        "websiteAnalysisFailed": {
                          "type": "boolean"
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "202": {
            "$ref": "#/components/responses/JobAccepted"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "409": {
            "description": "idempotency_key_reused: this Idempotency-Key was already used with a different request on this endpoint. Use a new key for a new request.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "idempotencyKeyReused": {
                    "summary": "Idempotency-Key reused",
                    "value": {
                      "error": "idempotency_key_reused",
                      "message": "This Idempotency-Key was already used for a different request. Use a new key for a new request."
                    }
                  }
                }
              }
            }
          },
          "410": {
            "$ref": "#/components/responses/JobResultExpired"
          },
          "429": {
            "description": "Too many requests, for one of 2 reasons. (1) a rate limit was exceeded (error 'Rate limit exceeded', with a retryAfter field equal to the Retry-After header). (2) too many long-running requests are running at once, for your account or on the server (error 'job_capacity_busy', Retry-After: 30). Nothing was run. Tell them apart by the body's error field. Retry after the Retry-After delay; a 429 does not use up an Idempotency-Key, so the retry may reuse it.",
            "headers": {
              "Retry-After": {
                "schema": {
                  "type": "integer"
                },
                "description": "Seconds to wait before retrying"
              },
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests allowed in the window"
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left in the window (0 on a 'Rate limit exceeded' 429; the other 429s show the normal count)"
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "string",
                  "format": "date-time"
                },
                "description": "When the window resets, as an ISO 8601 date-time (for example 2026-10-07T14:05:00.000Z)"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "oneOf": [
                    {
                      "$ref": "#/components/schemas/RateLimitError"
                    },
                    {
                      "$ref": "#/components/schemas/Error"
                    }
                  ]
                },
                "examples": {
                  "rateLimited": {
                    "summary": "Rate limit exceeded",
                    "value": {
                      "error": "Rate limit exceeded",
                      "message": "API rate limit exceeded. Please slow down your requests.",
                      "retryAfter": 42
                    }
                  },
                  "jobCapacityBusy": {
                    "summary": "Too many long-running requests",
                    "value": {
                      "error": "job_capacity_busy",
                      "message": "The server is busy with other long-running requests. Retry after the indicated delay."
                    }
                  }
                }
              }
            }
          },
          "500": {
            "description": "internal_error: the suggestions could not be generated, or job_timeout when they took longer than 15 minutes. Nothing is charged, so it is safe to retry.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "internal_error",
                  "message": "Failed to generate category suggestions"
                }
              }
            }
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        }
      }
    },
    "/ai/generate-keywords": {
      "post": {
        "tags": [
          "AI Features"
        ],
        "operationId": "generateKeywords",
        "summary": "AI keyword generation",
        "description": "Trigger AI-powered keyword generation based on your company profile and target categories (up to 3 per list). Keywords are generated as wildcard patterns and saved to keyword lists with auto-refine enabled by default. Runs asynchronously - use /ai/keyword-status to check progress. Rate limited to 5 requests per minute.",
        "responses": {
          "200": {
            "description": "Keyword generation started",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "message": {
                          "type": "string",
                          "example": "Keyword generation started"
                        },
                        "status": {
                          "type": "string",
                          "example": "pending"
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        }
      }
    },
    "/purchase-credits": {
      "post": {
        "tags": [
          "Credits & Subscription"
        ],
        "operationId": "purchaseCredits",
        "summary": "Purchase additional credits",
        "description": "Purchase additional credits using your saved payment method (Stripe off-session charge). Purchased credits roll over month to month and never expire while your subscription is active; however, your remaining balance (including purchased credits) is forfeited when your subscription ends and expires when a plan downgrade takes effect at renewal. Provide either creditCount (minimum 100) or dollarAmount (minimum $1). Maximum per purchase is 5x your plan's monthly allocation (Starter: 2,500, Growth: 10,000, Scale: 50,000, Platinum: 500,000, Enterprise: 1,250,000). When using dollarAmount, credits are calculated from your plan's overage rate and you are charged only for whole credits. Any fractional cents are rounded down in your favor. Pricing: Starter 10\u00a2, Growth 7.5\u00a2, Scale 5\u00a2, Platinum 3\u00a2, Enterprise 2\u00a2 per credit. For automatic usage-based billing instead of upfront purchases, see the opt-in overage budget (GET/PATCH /overage-budget). While a plan change or another credit purchase for your account is still being processed, the call returns 409 plan_change_in_progress without charging; try again in a moment.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "schema": {
              "type": "string",
              "pattern": "^[A-Za-z0-9_-]{8,64}$"
            },
            "description": "Same as the idempotencyKey body field (the body field wins when both are sent)."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "creditCount": {
                    "type": "integer",
                    "minimum": 100,
                    "description": "Number of credits to purchase (min 100, max 5x your plan's monthly credits). Mutually exclusive with dollarAmount.",
                    "example": 500
                  },
                  "dollarAmount": {
                    "type": "number",
                    "minimum": 1,
                    "description": "Dollar amount to spend on credits (minimum $1). Credits are calculated from your plan rate and you are charged for whole credits only. Mutually exclusive with creditCount.",
                    "example": 50
                  },
                  "idempotencyKey": {
                    "type": "string",
                    "pattern": "^[A-Za-z0-9_-]{8,64}$",
                    "description": "Optional. A unique key (8-64 letters, digits, '_' or '-') for this purchase; reuse it when retrying the same purchase so the card is not charged twice. May also be sent as the Idempotency-Key header. Without a key, an identical request for the same number of credits within the same minute is treated as a retry of the first."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Credits purchased successfully",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "success": {
                          "type": "boolean"
                        },
                        "creditsAdded": {
                          "type": "integer"
                        },
                        "totalPermanentCredits": {
                          "type": "integer"
                        },
                        "amountCharged": {
                          "type": "string"
                        },
                        "amountChargedCents": {
                          "type": "integer"
                        },
                        "pricePerCredit": {
                          "type": "string"
                        },
                        "stripePaymentIntentId": {
                          "type": "string"
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "bad_request (invalid creditCount, dollarAmount or idempotencyKey, or both amounts sent) or no_payment_method (add a payment method in the dashboard first)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "description": "payment_failed (the card was declined or the payment did not succeed) or authentication_required (the bank requires authentication; complete the purchase in the dashboard). No credits were added.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Not on an active credit plan"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "description": "A plan change or another credit purchase for this user is still being processed, so nothing was charged or granted. Retry in a moment.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string",
                      "enum": [
                        "plan_change_in_progress"
                      ]
                    },
                    "message": {
                      "type": "string",
                      "description": "Human-readable message, safe to show as is"
                    }
                  }
                },
                "example": {
                  "error": "plan_change_in_progress",
                  "message": "Another purchase or plan change is being processed for your account. Please try again in a moment."
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "description": "internal_error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "service_unavailable: payment processing is not configured",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/auto-top-up": {
      "get": {
        "tags": [
          "Credits & Subscription"
        ],
        "operationId": "getAutoTopUp",
        "summary": "Get auto top-up configuration",
        "description": "Get your auto top-up configuration including trigger threshold, purchase amount, monthly spending cap, and current month's usage.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Auto top-up configuration",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "enabled": {
                          "type": "boolean"
                        },
                        "triggerType": {
                          "type": "string",
                          "enum": [
                            "credits",
                            "dollars"
                          ],
                          "nullable": true
                        },
                        "triggerAmount": {
                          "type": "integer",
                          "nullable": true
                        },
                        "purchaseType": {
                          "type": "string",
                          "enum": [
                            "credits",
                            "dollars"
                          ],
                          "nullable": true
                        },
                        "purchaseAmount": {
                          "type": "integer",
                          "nullable": true
                        },
                        "capType": {
                          "type": "string",
                          "enum": [
                            "credits",
                            "dollars"
                          ],
                          "nullable": true
                        },
                        "capAmount": {
                          "type": "integer",
                          "nullable": true
                        },
                        "monthlyUsage": {
                          "type": "object",
                          "properties": {
                            "creditsAutoTopUp": {
                              "type": "integer",
                              "description": "Credits purchased via auto top-up this month"
                            },
                            "amountAutoTopUp": {
                              "type": "string",
                              "description": "Dollar amount spent on auto top-up this month (e.g. '$12.50')"
                            },
                            "amountAutoTopUpCents": {
                              "type": "integer",
                              "description": "Amount spent on auto top-up this month in cents"
                            },
                            "capResetAt": {
                              "type": "string",
                              "format": "date-time",
                              "nullable": true,
                              "description": "When the monthly usage cap resets"
                            }
                          }
                        },
                        "pricePerCredit": {
                          "type": "string",
                          "nullable": true,
                          "description": "Price per credit for your plan (e.g. '$0.10')"
                        },
                        "pricePerCreditCents": {
                          "type": "number",
                          "nullable": true,
                          "description": "Price per credit in cents (e.g. 10, 7.5, 5, 3, or 2)"
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "Not on an active credit plan"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        }
      },
      "patch": {
        "tags": [
          "Credits & Subscription"
        ],
        "operationId": "updateAutoTopUp",
        "summary": "Configure auto top-up",
        "description": "Configure automatic credit purchases. When enabled, credits are purchased automatically when your purchased credit balance falls below the trigger threshold. Supports trigger by credits or dollars, purchase by credits or dollars, and an optional monthly spending cap. Auto top-up cannot be enabled during a free trial (403 forbidden). enabled: false always works, also during a trial, and clears the configuration; a configuration saved before the trial does not run until the trial converts to a paid plan.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "enabled"
                ],
                "properties": {
                  "enabled": {
                    "type": "boolean",
                    "description": "Enable or disable auto top-up"
                  },
                  "triggerType": {
                    "type": "string",
                    "enum": [
                      "credits",
                      "dollars"
                    ],
                    "description": "What the trigger threshold measures"
                  },
                  "triggerAmount": {
                    "type": "integer",
                    "minimum": 1,
                    "description": "Threshold value that triggers a purchase"
                  },
                  "purchaseType": {
                    "type": "string",
                    "enum": [
                      "credits",
                      "dollars"
                    ],
                    "description": "Whether to buy by credit count or dollar amount"
                  },
                  "purchaseAmount": {
                    "type": "integer",
                    "minimum": 1,
                    "description": "Amount to purchase (min 100 credits or $1)"
                  },
                  "capType": {
                    "type": "string",
                    "enum": [
                      "credits",
                      "dollars"
                    ],
                    "nullable": true,
                    "description": "Monthly cap type, or null to disable"
                  },
                  "capAmount": {
                    "type": "integer",
                    "minimum": 1,
                    "nullable": true,
                    "description": "Monthly cap value"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Auto top-up configuration updated",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "enabled": {
                          "type": "boolean"
                        },
                        "triggerType": {
                          "type": "string",
                          "enum": [
                            "credits",
                            "dollars"
                          ],
                          "nullable": true
                        },
                        "triggerAmount": {
                          "type": "integer",
                          "nullable": true
                        },
                        "purchaseType": {
                          "type": "string",
                          "enum": [
                            "credits",
                            "dollars"
                          ],
                          "nullable": true
                        },
                        "purchaseAmount": {
                          "type": "integer",
                          "nullable": true
                        },
                        "capType": {
                          "type": "string",
                          "enum": [
                            "credits",
                            "dollars"
                          ],
                          "nullable": true
                        },
                        "capAmount": {
                          "type": "integer",
                          "nullable": true
                        },
                        "monthlyUsage": {
                          "type": "object",
                          "properties": {
                            "creditsAutoTopUp": {
                              "type": "integer",
                              "description": "Credits purchased via auto top-up this month"
                            },
                            "amountAutoTopUp": {
                              "type": "string",
                              "description": "Dollar amount spent on auto top-up this month (e.g. '$12.50')"
                            },
                            "amountAutoTopUpCents": {
                              "type": "integer",
                              "description": "Amount spent on auto top-up this month in cents"
                            },
                            "capResetAt": {
                              "type": "string",
                              "format": "date-time",
                              "nullable": true,
                              "description": "When the monthly usage cap resets"
                            }
                          }
                        },
                        "pricePerCredit": {
                          "type": "string",
                          "nullable": true,
                          "description": "Price per credit for your plan (e.g. '$0.10')"
                        },
                        "pricePerCreditCents": {
                          "type": "number",
                          "nullable": true,
                          "description": "Price per credit in cents (e.g. 10, 7.5, 5, 3, or 2)"
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "Only when enabling: not on an active credit plan, or on a free trial (auto top-up becomes available once the trial converts). Disabling (enabled: false) is never refused."
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        }
      }
    },
    "/overage-budget": {
      "get": {
        "tags": [
          "Credits & Subscription"
        ],
        "operationId": "getOverageBudget",
        "summary": "Get overage budget configuration",
        "description": "Get your overage budget configuration and usage. The overage budget is an opt-in spending limit that lets lead usage continue past a zero credit balance: extra credits accrue at your plan's overage rate (Starter 10\u00a2, Growth 7.5\u00a2, Scale 5\u00a2, Platinum 3\u00a2, Enterprise 2\u00a2 per credit) and are charged to your card on file once per day (accruals under Stripe's $0.50 charge minimum roll to the next day). The budget applies per billing cycle and resets at renewal. If a daily charge fails, overage usage is suspended until a retry succeeds; after 5 failed attempts the feature is disabled automatically.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Overage budget configuration and usage",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "enabled": {
                          "type": "boolean"
                        },
                        "budgetType": {
                          "type": "string",
                          "enum": [
                            "dollars",
                            "credits"
                          ],
                          "nullable": true,
                          "description": "Budget unit. 'dollars' is the default when enabling without a type."
                        },
                        "budgetAmount": {
                          "type": "integer",
                          "nullable": true,
                          "description": "Whole dollars or credits per billing cycle."
                        },
                        "cycleUsage": {
                          "type": "object",
                          "properties": {
                            "creditsUsed": {
                              "type": "integer",
                              "description": "Overage credits consumed this billing cycle"
                            },
                            "amountUsed": {
                              "type": "string",
                              "example": "$12.50"
                            },
                            "amountUsedCents": {
                              "type": "integer"
                            }
                          }
                        },
                        "pendingCharge": {
                          "type": "object",
                          "description": "Accrued overage not yet billed - collected by the next daily billing run.",
                          "properties": {
                            "credits": {
                              "type": "integer"
                            },
                            "amount": {
                              "type": "string",
                              "example": "$4.20"
                            },
                            "amountCents": {
                              "type": "integer"
                            }
                          }
                        },
                        "headroomCredits": {
                          "type": "integer",
                          "description": "How many more credits can be consumed under the remaining budget right now"
                        },
                        "billingSuspended": {
                          "type": "boolean",
                          "description": "True while a daily overage charge is failing; overage usage is paused until a retry succeeds"
                        },
                        "billingSuspendedSince": {
                          "type": "string",
                          "format": "date-time",
                          "nullable": true
                        },
                        "pricePerCredit": {
                          "type": "string",
                          "nullable": true,
                          "example": "$0.10"
                        },
                        "pricePerCreditCents": {
                          "type": "number",
                          "nullable": true,
                          "description": "Your plan's overage rate in cents per credit (e.g. 10, 7.5, 5, 3, or 2)"
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        }
      },
      "patch": {
        "tags": [
          "Credits & Subscription"
        ],
        "operationId": "updateOverageBudget",
        "summary": "Update overage budget configuration",
        "description": "Enable, disable, or edit the overage budget. When enabled=true, budgetAmount (whole number, minimum 1) is required and budgetType defaults to 'dollars' when omitted. Maximum budget: 5x your plan's monthly credit allocation for 'credits', or its dollar equivalent at your plan's overage rate for 'dollars'. Requires an active credit plan and a payment method on file. Disabling clears the budget config; any accrued unbilled overage is still collected by the daily billing runs once it reaches Stripe's $0.50 minimum (smaller remaining balances are written off).",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "enabled"
                ],
                "properties": {
                  "enabled": {
                    "type": "boolean"
                  },
                  "budgetType": {
                    "type": "string",
                    "enum": [
                      "dollars",
                      "credits"
                    ],
                    "description": "Budget unit. Defaults to 'dollars' when omitted."
                  },
                  "budgetAmount": {
                    "type": "integer",
                    "minimum": 1,
                    "description": "Whole dollars or credits per billing cycle. Required when enabled=true."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Updated overage budget configuration (same shape as GET)",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        }
      }
    },
    "/subscription/cancel": {
      "post": {
        "tags": [
          "Credits & Subscription"
        ],
        "operationId": "cancelSubscription",
        "summary": "Cancel subscription",
        "description": "Cancel your subscription at the end of the current billing period. Your access continues until the period ends.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Subscription cancellation scheduled",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "message": {
                          "type": "string"
                        },
                        "cancelAtPeriodEnd": {
                          "type": "boolean"
                        },
                        "currentPeriodEnd": {
                          "type": "integer"
                        },
                        "status": {
                          "type": "string"
                        },
                        "isTrial": {
                          "type": "boolean",
                          "description": "True when the cancelled subscription was still in its 14-day free trial, so nothing was ever charged."
                        },
                        "trialEndsAt": {
                          "type": "string",
                          "format": "date-time",
                          "nullable": true,
                          "description": "When the free trial and its access end. Null outside a trial."
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "description": "No active subscription found"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        }
      }
    },
    "/subscription/change-plan": {
      "post": {
        "tags": [
          "Credits & Subscription"
        ],
        "operationId": "changePlan",
        "summary": "Change subscription plan",
        "description": "Upgrade or downgrade your subscription plan. Available target plans: starter, growth, scale. On upgrade, all unused monthly credits are carried over to your credit balance, the card on file is charged the new plan's full price immediately, and the billing cycle restarts with the new plan's credits available right away. Downgrades take effect at renewal: credits stay usable until the renewal date, then the remaining balance (unused, rolled-over, and purchased credits) expires and the new plan's allocation is granted. While a downgrade is pending, moving back to your original plan (or a higher plan) before renewal voids it and keeps your credits (changeType 'upgrade', charged now); choosing another plan that is still below your original plan changes the pending downgrade (changeType 'downgrade', no charge), and the expiry still applies at renewal. During a free trial, mode 'switch_trial' (the default) changes the plan with no charge and keeps the trial end date; the trial has one spend counter, so credits already used stay used and creditsRemaining is 50% of the new plan minus what was used, never below zero. Mode 'activate_now' ends the trial, charges the target plan now, grants its full monthly allocation, and carries that plan's remaining trial allowance (50% of the plan minus what was used) into the rolling credit balance as permanentCreditsAdded. If the activation had already been processed, the response has alreadyProcessed: true and no credit figures; treat it as success.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "targetPlan"
                ],
                "properties": {
                  "targetPlan": {
                    "type": "string",
                    "enum": [
                      "starter",
                      "growth",
                      "scale"
                    ],
                    "description": "The plan to switch to"
                  },
                  "mode": {
                    "type": "string",
                    "enum": [
                      "switch_trial",
                      "activate_now"
                    ],
                    "description": "Only used while the subscription is in a free trial. 'switch_trial' (default during a trial) changes the plan with no charge and keeps the trial end date. 'activate_now' ends the trial and starts the paid plan immediately. Ignored outside a trial."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Plan changed successfully",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "success": {
                          "type": "boolean",
                          "description": "Present on trial responses (switch_trial, activate_now)"
                        },
                        "message": {
                          "type": "string"
                        },
                        "changeType": {
                          "type": "string",
                          "enum": [
                            "upgrade",
                            "downgrade",
                            "trial_switch",
                            "trial_activated"
                          ],
                          "description": "'upgrade' also covers a move back to the original plan while a downgrade is pending; 'downgrade' also covers re-targeting a pending downgrade to another lower plan"
                        },
                        "previousPlan": {
                          "type": "string"
                        },
                        "newPlan": {
                          "type": "string"
                        },
                        "newMonthlyCredits": {
                          "type": "integer",
                          "description": "Only present on upgrades"
                        },
                        "permanentCreditsAdded": {
                          "type": "integer",
                          "description": "Only present on upgrades and on activate_now (the credits actually carried into the rolling balance)"
                        },
                        "newCredits": {
                          "type": "integer",
                          "description": "Trial responses only. switch_trial: the new plan's trial allowance (50% of the plan). activate_now: the plan's full monthly allocation"
                        },
                        "creditsRemaining": {
                          "type": "integer",
                          "description": "switch_trial only: what is left of the new trial allowance after the credits already used during the trial (never below zero)"
                        },
                        "trialEndsAt": {
                          "type": "string",
                          "format": "date-time",
                          "nullable": true,
                          "description": "switch_trial only: the unchanged trial end date"
                        },
                        "amountChargedCents": {
                          "type": "integer",
                          "description": "activate_now only: the amount charged, in cents"
                        },
                        "alreadyProcessed": {
                          "type": "boolean",
                          "description": "activate_now only: true when the activation had already been processed. The response then carries no credit figures; treat it as success"
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "description": "Payment failed. On activate_now: { error: 'payment_declined', message, declineCode, trialStillActive: true } - nothing changed, the trial keeps running, and the call can be retried after the payment method is updated. On a paid upgrade: { error: 'payment_failed', message }.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "description": "No active subscription found"
          },
          "409": {
            "description": "The request conflicts with the current state and nothing was changed. error is one of: plan_change_in_progress (another plan change, or a credit purchase, for this user is still running), plan_state_changed (the plan Stripe bills differs from the account's plan, e.g. a change made elsewhere that has not synced yet; refresh and try again; outside a trial the account's plan is resynced to the billed plan before this answer, so a retry after re-reading state works, and during a trial it normally clears within seconds), trial_conversion_pending (the trial has just ended and its first payment is still being processed, or a trial move was sent less than two minutes before the trial end; includes retryAfterSeconds. The trial will have converted by then, so mode no longer applies and a retry runs as a paid plan change - re-read state first), trial_state_changed (the trial status changed; refresh and try again - mode no longer applies once the trial is over), trial_purchase_rate_lock (credits bought during the trial at a cheaper plan's rate are still left, so the trial cannot move to a plan with a higher credit price), renewal_in_progress (paid moves: a renewal is being processed; the message says whether the renewal payment has not gone through yet and the payment method needs updating, it was just paid and is still being applied, or it is still being processed; also returned for paid moves from 5 minutes before the current billing period ends until the renewal invoice has been created), activation_in_progress (an activate_now request is already being processed). message is customer-ready.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string",
                      "enum": [
                        "plan_change_in_progress",
                        "plan_state_changed",
                        "trial_conversion_pending",
                        "trial_state_changed",
                        "trial_purchase_rate_lock",
                        "renewal_in_progress",
                        "activation_in_progress"
                      ]
                    },
                    "message": {
                      "type": "string",
                      "description": "Human-readable message, safe to show as is"
                    },
                    "retryAfterSeconds": {
                      "type": "integer",
                      "description": "trial_conversion_pending only"
                    }
                  }
                },
                "example": {
                  "error": "plan_change_in_progress",
                  "message": "Your previous plan change is still being processed. Please refresh in a moment."
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        }
      }
    },
    "/export-blacklist": {
      "get": {
        "tags": [
          "Export Blacklist"
        ],
        "operationId": "listExportBlacklist",
        "summary": "List blacklisted entries",
        "description": "Retrieve all leads/domains on your export blacklist.",
        "responses": {
          "200": {
            "description": "Blacklist entries retrieved",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/BlacklistEntry"
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        }
      },
      "post": {
        "tags": [
          "Export Blacklist"
        ],
        "operationId": "addToBlacklist",
        "summary": "Add to blacklist",
        "description": "Add a lead or domain to your export blacklist. Supports single entries or batch adding via the 'entries' array.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "oneOf": [
                  {
                    "$ref": "#/components/schemas/BlacklistEntryInput"
                  },
                  {
                    "type": "object",
                    "properties": {
                      "entries": {
                        "type": "array",
                        "items": {
                          "$ref": "#/components/schemas/BlacklistEntryInput"
                        }
                      }
                    }
                  }
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Blacklist entry/entries added",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "oneOf": [
                        {
                          "$ref": "#/components/schemas/BlacklistEntry",
                          "description": "Single entry added (when using single value/entryType request)"
                        },
                        {
                          "type": "array",
                          "items": {
                            "$ref": "#/components/schemas/BlacklistEntry"
                          },
                          "description": "Array of entries added (when using batch entries request)"
                        }
                      ]
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        }
      }
    },
    "/export-blacklist/{id}": {
      "delete": {
        "tags": [
          "Export Blacklist"
        ],
        "operationId": "removeFromBlacklist",
        "summary": "Remove from blacklist",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "$ref": "#/components/responses/DeleteSuccess"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        }
      }
    },
    "/ai/auto-refine/status": {
      "get": {
        "tags": [
          "AI Features"
        ],
        "operationId": "getAutoRefineStatus",
        "summary": "Get auto-refine status",
        "description": "Get auto-refine status for keyword lists. Optionally filter by a specific list ID. Returns refinement progress, quality scores per round, timing, and paired list information. The auto-refine system uses a single-pass 4-phase process: Phase 1 validates each positive keyword individually (50% threshold, up to 2 variation attempts), Phase 1B discovers up to 15 new positives (5-added target), Phase 2 checks negative keyword collateral damage (40% threshold), Phase 2B discovers up to 5 new negatives. High-volume keywords use multi-sample scoring (3x50 leads, median). Final quality score (1-10) determines if retry pass needed.",
        "parameters": [
          {
            "name": "listId",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer"
            },
            "description": "Filter by specific keyword list ID"
          }
        ],
        "responses": {
          "200": {
            "description": "Auto-refine status retrieved",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "oneOf": [
                        {
                          "type": "array",
                          "description": "Array of all keyword lists with refine info (when no listId specified)",
                          "items": {
                            "$ref": "#/components/schemas/AutoRefineStatus"
                          }
                        },
                        {
                          "$ref": "#/components/schemas/AutoRefineStatus",
                          "description": "Single list refine info (when listId specified)"
                        }
                      ]
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "description": "Keyword list not found (when listId specified)"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        }
      }
    },
    "/ai/auto-refine/enable": {
      "post": {
        "tags": [
          "AI Features"
        ],
        "operationId": "enableAutoRefine",
        "summary": "Enable AI auto-refine",
        "description": "Enable AI auto-refine for a keyword list and start the single-pass 4-phase optimization process. Phase 1 validates each positive keyword individually (50% applicability threshold) with up to 2 variation attempts (3 candidates each). Phase 1B discovers up to 15 new positive keywords in a single AI call, testing all concurrently (5-added target based on incremental lead contribution). Phase 2 checks each negative keyword for collateral damage (40% threshold) with the same variation strategy. Phase 2B samples ~80 leads and discovers up to 5 new negative keywords. High-volume keywords use multi-sample scoring (3x50 leads, median). Final quality score (1-10, median of 3) determines if retry needed (score <=5). Auto-refine turns off when complete. Each list uses its sourceCategories (up to 3 recommended) for accurate AI scoring. Rate limited to 5 requests per minute.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "listId"
                ],
                "properties": {
                  "listId": {
                    "type": "integer",
                    "description": "Keyword list ID to enable auto-refine for"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Auto-refine enabled successfully",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "message": {
                          "type": "string"
                        },
                        "listId": {
                          "type": "integer"
                        },
                        "autoRefineEnabled": {
                          "type": "boolean"
                        },
                        "refinementStatus": {
                          "type": "string"
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        }
      }
    },
    "/ai/auto-refine/disable": {
      "post": {
        "tags": [
          "AI Features"
        ],
        "operationId": "disableAutoRefine",
        "summary": "Disable AI auto-refine",
        "description": "Disable AI auto-refine for a keyword list. Pauses any in-progress refinement and stops future optimization runs. Rate limited to 600 requests per minute.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "listId"
                ],
                "properties": {
                  "listId": {
                    "type": "integer",
                    "description": "Keyword list ID to disable auto-refine for"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Auto-refine disabled successfully",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "message": {
                          "type": "string"
                        },
                        "listId": {
                          "type": "integer"
                        },
                        "autoRefineEnabled": {
                          "type": "boolean"
                        },
                        "refinementStatus": {
                          "type": "string"
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        }
      }
    },
    "/ai/keyword-status": {
      "get": {
        "tags": [
          "AI Features"
        ],
        "operationId": "getKeywordStatus",
        "summary": "Check keyword generation job status",
        "description": "Check the status of keyword generation jobs. Rate limited to 600 requests per minute.",
        "responses": {
          "200": {
            "description": "Keyword generation status retrieved",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "hasJobs": {
                          "type": "boolean"
                        },
                        "jobs": {
                          "type": "array",
                          "items": {
                            "type": "object",
                            "properties": {
                              "id": {
                                "type": "integer"
                              },
                              "status": {
                                "type": "string"
                              },
                              "refinementStatus": {
                                "type": "string"
                              },
                              "currentRound": {
                                "type": "integer"
                              },
                              "maxRounds": {
                                "type": "integer"
                              },
                              "createdAt": {
                                "type": "string",
                                "format": "date-time"
                              },
                              "lastRefinedAt": {
                                "type": "string",
                                "format": "date-time",
                                "nullable": true
                              }
                            }
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        }
      }
    },
    "/email-schedules/{id}/trigger": {
      "post": {
        "tags": [
          "Email Schedules"
        ],
        "operationId": "triggerEmailSchedule",
        "summary": "Manually trigger an email schedule",
        "description": "Manually trigger an active email schedule to send immediately. Counts toward the 5 per minute export rate limit. A paused schedule returns 400. A 200 does not always mean an email went out: when nothing new matches, nothing is sent; with no credits left to spend, only leads you already received (which are free) are sent, and if every match is a new lead nothing is sent and your schedules pause.\n\nLong-running request: it answers exactly as described here when the work finishes within about 75 seconds (or within the wait you set with the Prefer header); otherwise it answers 202 Accepted with a jobId. Poll GET /jobs/{jobId}, which then returns exactly this response. Send an Idempotency-Key header so that a retry after a timeout returns the same job instead of running the request again (see the Background Jobs tag).\n\n**Retrying without sending twice.** The send keeps running after a 202, and its answer is kept for up to 24 hours. Repeating the request with the same Idempotency-Key returns the same send instead of sending again (the key is kept with the job for up to about 48 hours; a server restart forgets it, so then check lastSent in GET /email-schedules first), and triggering the same schedule while a trigger of it is still running returns that running job. If the schedule's regular run is sending it at that moment, the answer is 409 schedule_run_in_progress and this request sent nothing.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            }
          },
          {
            "$ref": "#/components/parameters/JobIdempotencyKey"
          },
          {
            "$ref": "#/components/parameters/PreferWait"
          }
        ],
        "responses": {
          "200": {
            "description": "Email schedule triggered successfully",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "message": {
                          "type": "string"
                        },
                        "scheduleId": {
                          "type": "integer"
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "202": {
            "$ref": "#/components/responses/JobAccepted"
          },
          "400": {
            "description": "bad_request: invalid schedule id, or the schedule is paused",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "description": "schedule_run_in_progress: the schedule's regular run is sending it right now, so this request sent nothing and the Idempotency-Key is not used up (check lastSent in GET /email-schedules when it finishes and trigger it again only if needed); or idempotency_key_reused: this Idempotency-Key was already used with a different request on this endpoint. Use a new key for a new request.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "scheduleRunInProgress": {
                    "summary": "The schedule is being sent by its regular run",
                    "value": {
                      "error": "schedule_run_in_progress",
                      "message": "This schedule is being sent right now by its regular schedule. When it finishes, check lastSent in GET /api/v1/email-schedules and trigger it again only if needed."
                    }
                  },
                  "idempotencyKeyReused": {
                    "summary": "Idempotency-Key reused",
                    "value": {
                      "error": "idempotency_key_reused",
                      "message": "This Idempotency-Key was already used for a different request. Use a new key for a new request."
                    }
                  }
                }
              }
            }
          },
          "410": {
            "$ref": "#/components/responses/JobResultExpired"
          },
          "429": {
            "description": "Too many requests, for one of 3 reasons. (1) a rate limit was exceeded (error 'Rate limit exceeded', with a retryAfter field equal to the Retry-After header). (2) lead search capacity is busy (error 'lead_query_capacity_busy', Retry-After: 10). (3) too many long-running requests are running at once, for your account or on the server (error 'job_capacity_busy', Retry-After: 30). None of them sent anything. Tell them apart by the body's error field. Retry after the Retry-After delay; a 429 does not use up an Idempotency-Key, so the retry may reuse it.",
            "headers": {
              "Retry-After": {
                "schema": {
                  "type": "integer"
                },
                "description": "Seconds to wait before retrying"
              },
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests allowed in the window"
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left in the window (0 on a 'Rate limit exceeded' 429; the other 429s show the normal count)"
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "string",
                  "format": "date-time"
                },
                "description": "When the window resets, as an ISO 8601 date-time (for example 2026-10-07T14:05:00.000Z)"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "oneOf": [
                    {
                      "$ref": "#/components/schemas/RateLimitError"
                    },
                    {
                      "$ref": "#/components/schemas/Error"
                    }
                  ]
                },
                "examples": {
                  "rateLimited": {
                    "summary": "Rate limit exceeded",
                    "value": {
                      "error": "Rate limit exceeded",
                      "message": "API rate limit exceeded. Please slow down your requests.",
                      "retryAfter": 42
                    }
                  },
                  "leadQueryCapacityBusy": {
                    "summary": "Lead search capacity busy",
                    "value": {
                      "error": "lead_query_capacity_busy",
                      "message": "Lead search capacity is currently busy. Retry after the indicated delay."
                    }
                  },
                  "jobCapacityBusy": {
                    "summary": "Too many long-running requests",
                    "value": {
                      "error": "job_capacity_busy",
                      "message": "The server is busy with other long-running requests. Retry after the indicated delay."
                    }
                  }
                }
              }
            }
          },
          "500": {
            "description": "internal_error. Do not retry blindly: the email may already have been sent and charged. Check the schedule's lastSent with GET /email-schedules first. With the same Idempotency-Key, a retry returns this same answer instead of sending again (for 24 hours, then 410 job_result_expired for about a day, still without sending). A send still running after 6 hours answers 500 job_timeout: check lastSent and totalSentCount before triggering it again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        }
      }
    },
    "/enrichments": {
      "post": {
        "tags": [
          "Enrichments"
        ],
        "summary": "Start an enrichment run",
        "description": "Accepts up to 100,000 URLs after deduplication by host. Returns 202 with a run id; the run continues in the background.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "urls"
                ],
                "properties": {
                  "urls": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    },
                    "maxItems": 100000,
                    "description": "The URLs to enrich. Duplicates are removed by host. Send the full URL including any path - a deep link often carries better contact data than the root."
                  },
                  "maxCredits": {
                    "type": "integer",
                    "minimum": 1,
                    "description": "Most credits this run may spend. Omit to allow the whole available balance, which is the default."
                  },
                  "databaseMatchPercent": {
                    "type": "integer",
                    "minimum": 0,
                    "maximum": 100,
                    "default": 100,
                    "description": "Most of maxCredits that may go to NEW database matches. Default 100, which is recommended. Unused database budget flows to live fetching, never the reverse. Records you have already paid for are always returned free and are not affected by this."
                  },
                  "contactTypes": {
                    "type": "string",
                    "enum": [
                      "phone",
                      "email",
                      "either"
                    ],
                    "default": "either",
                    "description": "The minimum contact details a result must carry to be delivered and charged. \"phone\" returns every result with a phone number, including those that also have an email; \"email\" returns every result with an email, including those that also have a phone number. \"either\" (the default) means a phone number OR an email - the widest setting, not the most restrictive."
                  },
                  "emailResults": {
                    "type": "boolean",
                    "description": "Email you a download link when the file is ready. Defaults to your account setting. The file is always downloadable regardless, so there is no flag to disable the download."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "202": {
            "description": "Accepted and queued",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "id": {
                          "type": "integer"
                        },
                        "status": {
                          "type": "string"
                        },
                        "urlCount": {
                          "type": "integer"
                        },
                        "duplicatesRemoved": {
                          "type": "integer"
                        },
                        "skippedInvalid": {
                          "type": "integer"
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "No usable URLs, too many URLs, or an invalid option"
          },
          "401": {
            "description": "Missing or invalid API key"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        }
      },
      "get": {
        "tags": [
          "Enrichments"
        ],
        "summary": "List your enrichment runs",
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 25
            }
          },
          {
            "name": "offset",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 0,
              "default": 0
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Newest first",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Enrichment"
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        }
      }
    },
    "/enrichments/{id}": {
      "get": {
        "tags": [
          "Enrichments"
        ],
        "summary": "Get one enrichment run",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The run",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/Enrichment"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "description": "Not found, or not yours"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        }
      }
    },
    "/enrichments/{id}/download": {
      "get": {
        "tags": [
          "Enrichments"
        ],
        "summary": "Get a download URL for a finished enrichment",
        "description": "Returns a short-lived presigned URL. The file has one row per URL you submitted, each with a `status` column: `matched_paid` (1 credit), `matched_free` (already bought, no charge), `scraped` (0.1 credit), `scrape_failed`, `skipped_db_budget`, `skipped_scrape_budget`, `matched_no_contact`, `scraped_no_contact`, `skipped_invalid` - every one of those last six costs nothing.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "A presigned download URL",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "downloadUrl": {
                          "type": "string"
                        },
                        "expiresInSeconds": {
                          "type": "integer"
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "409": {
            "description": "The run has not finished yet"
          },
          "404": {
            "description": "Not found, or not yours"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        }
      }
    },
    "/jobs/{jobId}": {
      "get": {
        "tags": [
          "Background Jobs"
        ],
        "operationId": "getJob",
        "summary": "Get the result of a long-running request",
        "description": "Follow a request that answered 202 Accepted. While the job runs this answers 202 again (same body as the original 202, with fresh phase, progress and elapsedMs). Once it has finished it answers exactly what the original request would have returned: the same status code, the same headers (Content-Type, Content-Disposition, X-Credits-Capped, Retry-After) and the same body, including an error answer, plus X-SMBSB-Job-Id and X-SMBSB-Job-Status. Any API key of the same account can poll. Results are normally kept 60 minutes after the job finishes (24 hours for an email schedule trigger) and can be dropped sooner under heavy load or after a server restart. After that this answers 410 job_result_expired for about a day, with a message that says where to look (exports: the files are in GET /export-history for 90 days and the leads in GET /lead-export-history; files from the Lead Export History endpoints: run the free re-export; schedule sends: lastSent in GET /email-schedules), and then 404 job_not_found. Never repeat an export or a schedule trigger then: check what it already did first. Polls count toward the normal rate limit of 600 requests per minute per key.",
        "parameters": [
          {
            "name": "jobId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "pattern": "^[a-f0-9]{32}$"
            },
            "description": "The jobId from a 202 answer (also in its Location and X-SMBSB-Job-Id headers)"
          },
          {
            "name": "Prefer",
            "in": "header",
            "required": false,
            "schema": {
              "type": "string",
              "pattern": "^wait=[0-9]+$"
            },
            "description": "wait=N (0-60 seconds): wait up to N seconds, counted from when this poll arrived, for the job to finish before answering 202 again (a long poll). Default: answer at once."
          }
        ],
        "responses": {
          "200": {
            "description": "The job finished: the original request's answer, replayed byte for byte (its status code may be any success code; JSON or a file such as CSV or XLSX).",
            "headers": {
              "X-SMBSB-Job-Id": {
                "schema": {
                  "type": "string",
                  "pattern": "^[a-f0-9]{32}$"
                },
                "description": "The background job id"
              },
              "X-SMBSB-Job-Status": {
                "schema": {
                  "type": "string",
                  "enum": [
                    "completed",
                    "failed"
                  ]
                },
                "description": "completed (the original answer was a success) or failed (the original answer was an error)"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "description": "The original request's JSON body",
                  "additionalProperties": true
                }
              },
              "text/csv": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              },
              "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              }
            }
          },
          "202": {
            "$ref": "#/components/responses/JobAccepted"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "description": "job_not_found: no job with this id for your account: a malformed id, another account's job, a job that finished more than about a day ago, or a job a server restart interrupted. Check what it already did before running anything again (exports: GET /export-history and GET /lead-export-history; schedule sends: lastSent in GET /email-schedules).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "job_not_found",
                  "message": "No background job with this id for your account: the id is unknown, the job finished more than a day ago, or a server restart interrupted it. Before running anything again, check what it already did: exports in GET /api/v1/export-history and /api/v1/lead-export-history (a re-run with maxCredits 0 returns leads you already paid for at no charge), schedule sends in lastSent of GET /api/v1/email-schedules."
                }
              }
            }
          },
          "410": {
            "description": "job_result_expired: the job finished, but its answer is no longer kept (normally 60 minutes after it finished, 24 hours for a schedule trigger; sooner under heavy load). Answered for about a day, then 404 job_not_found. The message says where to look for that kind of request; do not simply repeat the request.",
            "headers": {
              "X-SMBSB-Job-Id": {
                "schema": {
                  "type": "string",
                  "pattern": "^[a-f0-9]{32}$"
                },
                "description": "The background job id"
              },
              "X-SMBSB-Job-Status": {
                "schema": {
                  "type": "string",
                  "enum": [
                    "expired"
                  ]
                },
                "description": "expired"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "export": {
                    "summary": "An export",
                    "value": {
                      "error": "job_result_expired",
                      "message": "This export's answer is no longer kept (answers are kept up to 60 minutes after the export finishes). Do not repeat the export: a new export picks new leads first and charges for them. Check what it did first: its files are in GET /api/v1/export-history (kept 90 days) and its leads in GET /api/v1/lead-export-history; a re-run with maxCredits 0 (without excludePurchased) returns leads you already paid for at no charge."
                    }
                  },
                  "scheduleTrigger": {
                    "summary": "A schedule trigger",
                    "value": {
                      "error": "job_result_expired",
                      "message": "This send's answer is no longer kept (answers are kept up to 24 hours after the send finishes). Do not trigger the schedule again blindly: check lastSent and totalSentCount in GET /api/v1/email-schedules first, and trigger it again only if this send did not go out."
                    }
                  }
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          },
          "default": {
            "description": "The job finished with an error: the original request's error answer, replayed (for example 400 bad_request, 402 insufficient_credits, 409, 429 or 500), with X-SMBSB-Job-Status: failed.",
            "headers": {
              "X-SMBSB-Job-Id": {
                "schema": {
                  "type": "string",
                  "pattern": "^[a-f0-9]{32}$"
                },
                "description": "The background job id"
              },
              "X-SMBSB-Job-Status": {
                "schema": {
                  "type": "string",
                  "enum": [
                    "completed",
                    "failed"
                  ]
                },
                "description": "completed (the original answer was a success) or failed (the original answer was an error)"
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "BearerAuth": {
        "type": "http",
        "scheme": "bearer",
        "description": "API key with 'smbk_' prefix. Generate from Dashboard > API tab. Available on Starter, Growth, Scale, Platinum, and Enterprise plans, including during the 14-day free trial."
      }
    },
    "schemas": {
      "AutoRefineStatus": {
        "type": "object",
        "properties": {
          "listId": {
            "type": "integer",
            "description": "Keyword list ID"
          },
          "name": {
            "type": "string",
            "description": "Keyword list name"
          },
          "type": {
            "type": "string",
            "enum": [
              "positive",
              "negative"
            ],
            "description": "Keyword list type"
          },
          "autoRefineEnabled": {
            "type": "boolean",
            "description": "Whether auto-refine is enabled"
          },
          "refinementStatus": {
            "type": "string",
            "enum": [
              "running",
              "paused",
              "completed",
              "failed"
            ],
            "nullable": true,
            "description": "Current refinement status"
          },
          "currentRound": {
            "type": "integer",
            "description": "Current refinement round (0 if not started)"
          },
          "maxRounds": {
            "type": "integer",
            "description": "Maximum number of refinement rounds (default 10)"
          },
          "scoresPerRound": {
            "type": "array",
            "description": "Quality scores from each completed round. Uses multi-sample scoring (3\u00d750 leads, median) for high-volume keywords.",
            "items": {
              "type": "object",
              "properties": {
                "round": {
                  "type": "integer",
                  "description": "Round number"
                },
                "score": {
                  "type": "number",
                  "description": "Quality score (1-10)"
                },
                "reasoning": {
                  "type": "string",
                  "description": "Explanation of the score, e.g. 'Median of [6, 7, 5]'"
                }
              }
            }
          },
          "lastRefinedAt": {
            "type": "string",
            "format": "date-time",
            "nullable": true,
            "description": "ISO timestamp of last refinement run"
          },
          "pairedListId": {
            "type": "integer",
            "nullable": true,
            "description": "ID of the paired positive/negative keyword list (refined together)"
          }
        }
      },
      "UserProfile": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "email": {
            "type": "string",
            "format": "email"
          },
          "firstName": {
            "type": "string",
            "nullable": true
          },
          "lastName": {
            "type": "string",
            "nullable": true
          },
          "companyName": {
            "type": "string",
            "nullable": true
          },
          "companyWebsite": {
            "type": "string",
            "nullable": true
          },
          "smbType": {
            "type": "string",
            "enum": [
              "other"
            ],
            "description": "The lead database your account uses: always 'other' (the Main database)."
          },
          "subscriptionPlan": {
            "type": "string",
            "enum": [
              "free",
              "starter",
              "growth",
              "scale",
              "platinum",
              "enterprise",
              "pro",
              "agency"
            ],
            "description": "Current plan. `pro` and `agency` are legacy plans retained for historical accounts - they are not sellable, but existing subscribers on them keep full API access."
          },
          "subscriptionStatus": {
            "type": "string",
            "nullable": true,
            "description": "`trialing` during a 14-day free trial. A trialing account has full access and `isSubscribed` true, but has not paid yet."
          },
          "isSubscribed": {
            "type": "boolean"
          },
          "freeTrialEligible": {
            "type": "boolean",
            "description": "Reported for completeness. An API key only works on an account with an active or trialing paid subscription, so this is false for every caller that can reach this endpoint. Trial eligibility for a new purchase is returned by POST /purchase in trial.eligible."
          },
          "trial": {
            "type": "object",
            "description": "Free-trial state. Starter, Growth and Scale start with a 14-day trial; Platinum and Enterprise never do.",
            "properties": {
              "isTrialing": {
                "type": "boolean"
              },
              "startedAt": {
                "type": "string",
                "format": "date-time",
                "nullable": true
              },
              "endsAt": {
                "type": "string",
                "format": "date-time",
                "nullable": true,
                "description": "When the plan price is charged automatically unless the subscription is cancelled first."
              },
              "plan": {
                "type": "string",
                "nullable": true
              },
              "outcome": {
                "type": "string",
                "nullable": true,
                "description": "converted | converted_early | cancelled | payment_failed | duplicate_converted | duplicate_cancelled"
              },
              "convertedAt": {
                "type": "string",
                "format": "date-time",
                "nullable": true
              },
              "firstChargeAmountCents": {
                "type": "integer",
                "nullable": true,
                "description": "While trialing: the plan's list price in cents, charged when the trial ends (does not reflect discounts). Null when not trialing."
              },
              "freeTrialUsedAt": {
                "type": "string",
                "format": "date-time",
                "nullable": true
              },
              "daysRemaining": {
                "type": "integer",
                "description": "Whole days left in the trial, rounded up; 0 when not trialing"
              }
            }
          },
          "freeLeadsUsed": {
            "type": "integer"
          },
          "totalLeadsExported": {
            "type": "integer",
            "description": "Total leads exported across all time"
          },
          "monthlyLeadsExported": {
            "type": "integer",
            "description": "Leads exported in the current billing cycle"
          },
          "onboardingCompleted": {
            "type": "boolean"
          },
          "targetCategories": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "nullable": true
          },
          "targetLocations": {
            "type": "object",
            "nullable": true,
            "description": "Geographic targeting. `data` lists the selected states or cities and is omitted or empty for the whole-country types.",
            "properties": {
              "type": {
                "type": "string",
                "enum": [
                  "continental_usa",
                  "all_usa",
                  "specific_states",
                  "specific_cities"
                ]
              },
              "data": {
                "type": "array",
                "items": {
                  "type": "string"
                }
              }
            }
          },
          "monthlyCredits": {
            "type": "integer",
            "description": "Total monthly credit allocation for the plan"
          },
          "monthlyCreditsUsed": {
            "type": "integer",
            "description": "Credits used this billing cycle"
          },
          "monthlyCreditsRemaining": {
            "type": "integer",
            "description": "Monthly credits remaining this billing cycle"
          },
          "temporaryCreditsRemaining": {
            "type": "integer",
            "description": "Promotional or admin-granted credits that expire on their own schedule. Spent before monthly and rolling credits, and included in totalCreditsRemaining."
          },
          "permanentCredits": {
            "type": "integer",
            "description": "Rolling credit balance (purchased and rolled-over credits; used after the current cycle's credits). Persists across billing cycles while your subscription is active, but is forfeited when the subscription ends and expires when a plan downgrade takes effect at renewal."
          },
          "totalCreditsRemaining": {
            "type": "integer",
            "description": "Combined remaining credits: monthly + rolling balance + temporary credits"
          },
          "creditOverageRate": {
            "type": "number",
            "description": "Cost per additional credit in cents (e.g., 10 = $0.10 for Starter, 7.5 = $0.075 for Growth)"
          },
          "autoTopUp": {
            "type": "object",
            "description": "Auto top-up configuration for automatic credit purchases",
            "properties": {
              "enabled": {
                "type": "boolean"
              },
              "triggerType": {
                "type": "string",
                "enum": [
                  "credits",
                  "dollars"
                ],
                "nullable": true
              },
              "triggerAmount": {
                "type": "integer",
                "nullable": true
              },
              "purchaseType": {
                "type": "string",
                "enum": [
                  "credits",
                  "dollars"
                ],
                "nullable": true
              },
              "purchaseAmount": {
                "type": "integer",
                "nullable": true
              },
              "capType": {
                "type": "string",
                "enum": [
                  "credits",
                  "dollars"
                ],
                "nullable": true
              },
              "capAmount": {
                "type": "integer",
                "nullable": true
              }
            }
          },
          "overageBudget": {
            "type": "object",
            "description": "Opt-in overage budget (spend past a zero balance, billed to the card on file once per day). See GET /overage-budget for full usage details.",
            "properties": {
              "enabled": {
                "type": "boolean"
              },
              "budgetType": {
                "type": "string",
                "enum": [
                  "dollars",
                  "credits"
                ],
                "nullable": true
              },
              "budgetAmount": {
                "type": "integer",
                "nullable": true
              },
              "creditsUsedThisCycle": {
                "type": "integer"
              },
              "amountUsedThisCycleCents": {
                "type": "integer"
              },
              "pendingChargeCents": {
                "type": "integer",
                "description": "Accrued overage not yet billed - collected by the next daily billing run"
              },
              "billingSuspended": {
                "type": "boolean"
              }
            }
          }
        }
      },
      "UserProfileBasic": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "email": {
            "type": "string"
          },
          "firstName": {
            "type": "string",
            "nullable": true
          },
          "lastName": {
            "type": "string",
            "nullable": true
          },
          "companyName": {
            "type": "string",
            "nullable": true
          },
          "companyWebsite": {
            "type": "string",
            "nullable": true
          }
        }
      },
      "Lead": {
        "type": "object",
        "description": "Lead data from the Main database. Field keys use display names with spaces. See OtherLead for the exact fields returned.",
        "allOf": [
          {
            "$ref": "#/components/schemas/OtherLead"
          }
        ]
      },
      "OtherLead": {
        "type": "object",
        "description": "Lead from the Main database (web registration-sourced). Provides both phone numbers and email addresses. Field keys use display names with spaces.",
        "properties": {
          "id": {
            "type": "integer"
          },
          "Last Buying Signal": {
            "type": "string",
            "format": "date-time",
            "nullable": true,
            "description": "Date of the lead's most recent buying signal: any of the key contact/address fields changed (primary phone, Total Phones, primary email, the Tier 1-2 email lists, or full address), or a social profile was added or changed for the business (tracked per source, e.g. Facebook, Instagram, Google Maps), or the record was newly added."
          },
          "Last Updated": {
            "type": "string",
            "nullable": true,
            "description": "Deprecated duplicate of Last Buying Signal, same value. Still returned so existing integrations keep working.",
            "deprecated": true
          },
          "Buying Signal Type": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "nullable": true,
            "description": "Which buying signal(s) set the Last Buying Signal date. Values: Newly Added, Phone Primary, Total Phones, Email Primary, Tier 1 Emails, Tier 2 Emails, Address Full, Instagram, LinkedIn, Facebook, YouTube, TikTok, X (Twitter), Google Maps, Yelp, Pinterest, Other Social (filter on them with buyingSignalTypeFilter)."
          },
          "Registered URL": {
            "type": "string",
            "nullable": true,
            "description": "Originally registered domain URL"
          },
          "Registration Date": {
            "type": "string",
            "nullable": true,
            "description": "Domain registration date"
          },
          "Redirect": {
            "type": "string",
            "nullable": true,
            "description": "Whether the registered URL redirects (Y/N)"
          },
          "Crawled URL": {
            "type": "string",
            "nullable": true,
            "description": "Final URL after following redirects"
          },
          "Company Name": {
            "type": "string",
            "nullable": true,
            "description": "Business name"
          },
          "AI Categories": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "nullable": true,
            "description": "AI-estimated business categories"
          },
          "Phone Primary": {
            "type": "string",
            "nullable": true,
            "description": "Primary phone number (masked on GET /leads/preview until you have received the lead)"
          },
          "Total Phones": {
            "type": "string",
            "nullable": true,
            "description": "Total phones list, comma-separated (masked on GET /leads/preview until you have received the lead)"
          },
          "Email Primary": {
            "type": "string",
            "nullable": true,
            "description": "Primary email address (masked on GET /leads/preview until you have received the lead)"
          },
          "Tier 1 Emails": {
            "type": "string",
            "nullable": true,
            "description": "Tier 1 emails list, comma-separated (masked on GET /leads/preview until you have received the lead)"
          },
          "Tier 2 Emails": {
            "type": "string",
            "nullable": true,
            "description": "Tier 2 emails list, comma-separated (masked on GET /leads/preview until you have received the lead)"
          },
          "Tier 3 Emails": {
            "type": "string",
            "nullable": true,
            "description": "Tier 3 emails list, comma-separated (masked on GET /leads/preview until you have received the lead)"
          },
          "Tier 4 Emails": {
            "type": "string",
            "nullable": true,
            "description": "Tier 4 emails list, comma-separated (masked on GET /leads/preview until you have received the lead)"
          },
          "Tier 5 Emails": {
            "type": "string",
            "nullable": true,
            "description": "Tier 5 emails list, comma-separated (masked on GET /leads/preview until you have received the lead)"
          },
          "Tier 6 Emails": {
            "type": "string",
            "nullable": true,
            "description": "Tier 6 emails list, comma-separated (masked on GET /leads/preview until you have received the lead)"
          },
          "Website Schema": {
            "type": "string",
            "nullable": true,
            "description": "Schema.org category from the website"
          },
          "Description Short": {
            "type": "string",
            "nullable": true,
            "description": "Brief business description"
          },
          "Description Long": {
            "type": "string",
            "nullable": true,
            "description": "Detailed business description"
          },
          "Address Full": {
            "type": "string",
            "nullable": true,
            "description": "Full address string"
          },
          "Street": {
            "type": "string",
            "nullable": true,
            "description": "Street address"
          },
          "City": {
            "type": "string",
            "nullable": true
          },
          "State": {
            "type": "string",
            "nullable": true
          },
          "Zip": {
            "type": "string",
            "nullable": true
          },
          "Platform": {
            "type": "string",
            "nullable": true,
            "description": "Website platform/tech list (comma-separated)"
          },
          "Booking/Ops/CRM Software": {
            "type": "string",
            "nullable": true,
            "description": "Booking, operations, and CRM software detected (comma-separated)"
          },
          "Reputation/Chat Software": {
            "type": "string",
            "nullable": true,
            "description": "Reputation and chat software detected (comma-separated)"
          },
          "Marketing/Email Software": {
            "type": "string",
            "nullable": true,
            "description": "Marketing and email software detected (comma-separated)"
          },
          "Ad Pixels": {
            "type": "string",
            "nullable": true,
            "description": "Advertising pixels detected (comma-separated)"
          },
          "Payment/Financing Software": {
            "type": "string",
            "nullable": true,
            "description": "Payment and financing software detected (comma-separated)"
          },
          "Employees": {
            "type": "string",
            "nullable": true,
            "description": "Employee count"
          },
          "Number of Job Postings": {
            "type": "string",
            "nullable": true,
            "description": "Number of open positions found on the business's website. Empty/null = no careers page found; \"0\" = careers page exists but has no listings."
          },
          "Founding Date": {
            "type": "string",
            "nullable": true,
            "description": "Founding date"
          },
          "Number of Locations": {
            "type": "string",
            "nullable": true,
            "description": "Number of business locations"
          },
          "Is Chain?": {
            "type": "string",
            "nullable": true,
            "description": "Whether the business is a chain"
          },
          "Rating Value": {
            "type": "string",
            "nullable": true,
            "description": "Average rating value"
          },
          "Review Count": {
            "type": "string",
            "nullable": true,
            "description": "Number of reviews"
          },
          "Price Range": {
            "type": "string",
            "nullable": true,
            "description": "Price range (e.g. $, $$, $$$)"
          },
          "Legal Name": {
            "type": "string",
            "nullable": true,
            "description": "Registered legal entity name"
          },
          "NAICS Code": {
            "type": "string",
            "nullable": true,
            "description": "NAICS industry code"
          },
          "DUNS Number": {
            "type": "string",
            "nullable": true,
            "description": "DUNS number"
          },
          "Founder Name(s)": {
            "type": "string",
            "nullable": true,
            "description": "Founder names list (comma-separated)"
          },
          "Founder Email(s)": {
            "type": "string",
            "nullable": true,
            "description": "Founder emails list, comma-separated (masked until the lead is unlocked/exported)"
          },
          "Founder Phone(s)": {
            "type": "string",
            "nullable": true,
            "description": "Founder phones list, comma-separated (masked until the lead is unlocked/exported)"
          },
          "Latitude": {
            "type": "string",
            "nullable": true,
            "description": "Geographic latitude"
          },
          "Longitude": {
            "type": "string",
            "nullable": true,
            "description": "Geographic longitude"
          },
          "Opening Hours": {
            "type": "string",
            "nullable": true,
            "description": "Opening hours list (comma-separated)"
          },
          "Area Served": {
            "type": "string",
            "nullable": true,
            "description": "Areas served list (comma-separated)"
          },
          "Offers": {
            "type": "string",
            "nullable": true,
            "description": "Offers/services list (comma-separated)"
          },
          "Brand": {
            "type": "string",
            "nullable": true,
            "description": "Brands list (comma-separated)"
          },
          "Slogan": {
            "type": "string",
            "nullable": true,
            "description": "Business slogan"
          },
          "Logo URL": {
            "type": "string",
            "nullable": true,
            "description": "Logo image URL"
          },
          "Social Profiles": {
            "type": "string",
            "nullable": true,
            "description": "Social profile URLs list (comma-separated)"
          },
          "Address Source": {
            "type": "string",
            "nullable": true,
            "description": "Where the address was derived from."
          },
          "Email Primary Provider": {
            "type": "string",
            "nullable": true,
            "description": "Mail provider detected for the primary email."
          },
          "Website Email Provider": {
            "type": "string",
            "nullable": true,
            "description": "Mail provider detected for the website domain."
          },
          "Website Email Security": {
            "type": "string",
            "nullable": true,
            "description": "Email security posture detected for the website domain (e.g. SPF/DMARC)."
          },
          "Time Scraped": {
            "type": "string",
            "nullable": true,
            "description": "When the data was collected"
          },
          "contactExported": {
            "type": "boolean",
            "description": "Present and true on preview responses when this lead was already purchased by you (revealed unmasked). Absent on leads that are still masked."
          }
        }
      },
      "FilterPreset": {
        "type": "object",
        "properties": {
          "id": {
            "type": "integer"
          },
          "name": {
            "type": "string"
          },
          "filters": {
            "type": "object",
            "description": "Saved filter configuration"
          },
          "databaseType": {
            "type": "string",
            "enum": [
              "other"
            ],
            "description": "The lead database this preset runs against: always 'other' (the Main database)."
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "FilterPresetInput": {
        "type": "object",
        "required": [
          "name",
          "filters"
        ],
        "properties": {
          "name": {
            "type": "string",
            "description": "Preset name"
          },
          "filters": {
            "type": "object",
            "description": "Filter configuration to save"
          },
          "databaseType": {
            "type": "string",
            "enum": [
              "other"
            ],
            "default": "other",
            "description": "Lead database for this preset. Only 'other' (the Main database) is available. Legacy values are still accepted and resolve to the Main database."
          }
        }
      },
      "KeywordList": {
        "type": "object",
        "properties": {
          "id": {
            "type": "integer"
          },
          "name": {
            "type": "string"
          },
          "type": {
            "type": "string",
            "enum": [
              "positive",
              "negative"
            ],
            "description": "Whether this list contains include or exclude keywords"
          },
          "keywords": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Keyword patterns (wildcards supported, e.g. *dentist*)"
          },
          "sourceCategories": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Target categories this list was generated for. Up to 3 per list is recommended for the best AI quality; more are allowed."
          },
          "autoRefineEnabled": {
            "type": "boolean",
            "description": "Whether AI auto-refine is active for this list"
          },
          "refinementStatus": {
            "type": "string",
            "nullable": true,
            "enum": [
              "running",
              "completed",
              "paused",
              "failed",
              null
            ],
            "description": "Current refinement status"
          },
          "pairedListId": {
            "type": "integer",
            "nullable": true,
            "description": "ID of the paired positive/negative list (auto-refine manages both together)"
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "KeywordListInput": {
        "type": "object",
        "required": [
          "name",
          "type",
          "keywords"
        ],
        "description": "Only these four fields are read; any other field is ignored. Invalid input returns 400 validation_error.",
        "properties": {
          "name": {
            "type": "string",
            "minLength": 1,
            "maxLength": 255,
            "description": "List name. Leading and trailing spaces are trimmed; 1-255 characters after trimming."
          },
          "type": {
            "type": "string",
            "enum": [
              "positive",
              "negative"
            ],
            "description": "positive: keywords to include; negative: keywords to exclude"
          },
          "keywords": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "minItems": 1,
            "description": "Keyword strings (at least one). Supports * wildcards, for example *plumb*."
          },
          "sourceCategories": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "nullable": true,
            "description": "Optional target categories the list was built for (used by AI auto-refine scoring): an array of strings, or null."
          }
        }
      },
      "KeywordListUpdate": {
        "type": "object",
        "description": "Send at least one of these fields; any other field is ignored.",
        "properties": {
          "name": {
            "type": "string",
            "minLength": 1,
            "maxLength": 255
          },
          "type": {
            "type": "string",
            "enum": [
              "positive",
              "negative"
            ]
          },
          "keywords": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "minItems": 1
          },
          "sourceCategories": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "nullable": true,
            "description": "Target categories for AI auto-refine scoring: an array of strings, or null to clear them (the refiner then uses your account's categories)."
          }
        }
      },
      "EmailSchedule": {
        "type": "object",
        "properties": {
          "id": {
            "type": "integer"
          },
          "name": {
            "type": "string"
          },
          "filterPresetId": {
            "type": "integer"
          },
          "exportFormatId": {
            "type": "integer",
            "nullable": true
          },
          "intervalValue": {
            "type": "integer"
          },
          "intervalUnit": {
            "type": "string",
            "enum": [
              "hours",
              "days"
            ]
          },
          "isActive": {
            "type": "boolean"
          },
          "pauseReason": {
            "type": "string",
            "nullable": true,
            "enum": [
              "user",
              "insufficient_credits",
              null
            ],
            "description": "Why the schedule is paused. 'user' = manually paused, 'insufficient_credits' = auto-paused due to zero credits, null = not paused (active)"
          },
          "recipients": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "email": {
                  "type": "string",
                  "format": "email"
                },
                "splitParts": {
                  "type": "array",
                  "items": {
                    "type": "integer",
                    "minimum": 1
                  },
                  "description": "Which file split parts this recipient receives (only relevant when export format uses split files)"
                }
              },
              "required": [
                "email"
              ]
            },
            "description": "Recipients as objects with email and optional splitParts assignment"
          },
          "distributionMode": {
            "type": "string",
            "enum": [
              "full_copy",
              "split_evenly"
            ],
            "description": "How leads are distributed among recipients"
          },
          "fullCopyRecipients": {
            "type": "array",
            "items": {
              "type": "string",
              "format": "email"
            },
            "description": "Recipients who receive the full lead list when distributionMode is split_evenly"
          },
          "maxLeadsPerEmail": {
            "type": "integer",
            "nullable": true,
            "minimum": 1,
            "maximum": 100000,
            "description": "Most leads per email delivery (1-100,000), or null for no cap. New leads that do not fit, and new leads your credits could not cover, are kept in the lead reservoir (one pool per account, shared by your schedules and API exports, up to your plan's monthly credit allowance; when it is full, more leads are not kept) and sent with a later email. On credit plans, leads you already received that do not fit are not kept."
          },
          "combinedFileEnabled": {
            "type": "boolean",
            "description": "Whether combined file delivery is enabled"
          },
          "combinedRecipients": {
            "type": "array",
            "items": {
              "type": "string",
              "format": "email"
            },
            "description": "Recipients who receive the merged combined file"
          },
          "combinedIncludeAssignee": {
            "type": "boolean",
            "description": "Whether to add assignee attribution column to merged file"
          },
          "combinedIncludeFileName": {
            "type": "boolean",
            "description": "Whether to add file name attribution column to merged file"
          },
          "combinedColumnPosition": {
            "type": "string",
            "enum": [
              "beginning",
              "end"
            ],
            "description": "Where to place attribution columns in merged file"
          },
          "combinedAssigneeColumnName": {
            "type": "string",
            "description": "Custom header for the assignee column (1-50 chars)"
          },
          "combinedFileNameColumnName": {
            "type": "string",
            "description": "Custom header for the file name column (1-50 chars)"
          },
          "lastSent": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "totalSentCount": {
            "type": "integer"
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "EmailScheduleInput": {
        "type": "object",
        "required": [
          "name",
          "filterPresetId",
          "intervalValue",
          "intervalUnit",
          "recipients"
        ],
        "properties": {
          "name": {
            "type": "string",
            "maxLength": 100
          },
          "filterPresetId": {
            "type": "integer",
            "description": "ID of the filter preset to use"
          },
          "exportFormatId": {
            "type": "integer",
            "nullable": true,
            "description": "ID of export format (optional)"
          },
          "intervalValue": {
            "type": "integer",
            "minimum": 1,
            "description": "Delivery interval value: a whole number from 1 to 720",
            "maximum": 720
          },
          "intervalUnit": {
            "type": "string",
            "enum": [
              "hours",
              "days"
            ],
            "description": "Delivery interval unit"
          },
          "recipients": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "email": {
                  "type": "string",
                  "format": "email"
                },
                "splitParts": {
                  "type": "array",
                  "items": {
                    "type": "integer",
                    "minimum": 1
                  },
                  "description": "Which file split parts this recipient receives (only relevant when export format uses split files)"
                }
              },
              "required": [
                "email"
              ]
            },
            "minItems": 1,
            "maxItems": 50,
            "description": "Recipients as objects with email and optional splitParts assignment"
          },
          "distributionMode": {
            "type": "string",
            "enum": [
              "full_copy",
              "split_evenly"
            ],
            "default": "full_copy",
            "description": "full_copy sends all leads to every recipient; split_evenly divides leads evenly among recipients"
          },
          "fullCopyRecipients": {
            "type": "array",
            "items": {
              "type": "string",
              "format": "email"
            },
            "maxItems": 50,
            "description": "Optional recipients who receive the full lead list when distributionMode is split_evenly (e.g. managers)"
          },
          "combinedFileEnabled": {
            "type": "boolean",
            "default": false
          },
          "combinedRecipients": {
            "type": "array",
            "items": {
              "type": "string",
              "format": "email"
            },
            "maxItems": 50
          },
          "combinedIncludeAssignee": {
            "type": "boolean",
            "default": true
          },
          "combinedIncludeFileName": {
            "type": "boolean",
            "default": true
          },
          "combinedColumnPosition": {
            "type": "string",
            "enum": [
              "beginning",
              "end"
            ],
            "default": "end"
          },
          "combinedAssigneeColumnName": {
            "type": "string",
            "maxLength": 50,
            "default": "Assigned To"
          },
          "combinedFileNameColumnName": {
            "type": "string",
            "maxLength": 50,
            "default": "File Part"
          },
          "maxLeadsPerEmail": {
            "type": "integer",
            "nullable": true,
            "minimum": 1,
            "maximum": 100000,
            "description": "Most leads per email delivery (1-100,000), or null for no cap. New leads that do not fit, and new leads your credits could not cover, are kept in the lead reservoir (one pool per account, shared by your schedules and API exports, up to your plan's monthly credit allowance; when it is full, more leads are not kept) and sent with a later email. On credit plans, leads you already received that do not fit are not kept."
          },
          "isActive": {
            "type": "boolean",
            "default": true,
            "description": "Defaults to true. An active schedule sends its first email within about 15 minutes; send false to create it paused."
          }
        }
      },
      "EmailScheduleUpdate": {
        "type": "object",
        "properties": {
          "name": {
            "type": "string"
          },
          "isActive": {
            "type": "boolean"
          },
          "intervalValue": {
            "type": "integer",
            "minimum": 1,
            "maximum": 720,
            "description": "Delivery interval value: a whole number from 1 to 720"
          },
          "intervalUnit": {
            "type": "string",
            "enum": [
              "hours",
              "days"
            ]
          },
          "filterPresetId": {
            "type": "integer"
          },
          "exportFormatId": {
            "type": "integer",
            "nullable": true
          },
          "recipients": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "email": {
                  "type": "string",
                  "format": "email"
                },
                "splitParts": {
                  "type": "array",
                  "items": {
                    "type": "integer",
                    "minimum": 1
                  },
                  "description": "Which file split parts this recipient receives"
                }
              },
              "required": [
                "email"
              ]
            },
            "maxItems": 50
          },
          "distributionMode": {
            "type": "string",
            "enum": [
              "full_copy",
              "split_evenly"
            ],
            "description": "full_copy sends all leads to every recipient; split_evenly divides leads evenly among recipients"
          },
          "fullCopyRecipients": {
            "type": "array",
            "items": {
              "type": "string",
              "format": "email"
            },
            "maxItems": 50,
            "description": "Recipients who receive the full lead list when distributionMode is split_evenly"
          },
          "combinedFileEnabled": {
            "type": "boolean"
          },
          "combinedRecipients": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "maxItems": 50
          },
          "combinedIncludeAssignee": {
            "type": "boolean"
          },
          "combinedIncludeFileName": {
            "type": "boolean"
          },
          "combinedColumnPosition": {
            "type": "string",
            "enum": [
              "beginning",
              "end"
            ]
          },
          "combinedAssigneeColumnName": {
            "type": "string",
            "maxLength": 50
          },
          "combinedFileNameColumnName": {
            "type": "string",
            "maxLength": 50
          },
          "maxLeadsPerEmail": {
            "type": "integer",
            "nullable": true,
            "minimum": 1,
            "maximum": 100000,
            "description": "Most leads per email delivery (1-100,000), or null for no cap. New leads that do not fit, and new leads your credits could not cover, are kept in the lead reservoir (one pool per account, shared by your schedules and API exports, up to your plan's monthly credit allowance; when it is full, more leads are not kept) and sent with a later email. On credit plans, leads you already received that do not fit are not kept."
          }
        }
      },
      "ExportFormat": {
        "type": "object",
        "properties": {
          "id": {
            "type": "integer"
          },
          "name": {
            "type": "string"
          },
          "fileType": {
            "type": "string",
            "enum": [
              "csv",
              "json",
              "xlsx"
            ]
          },
          "startRow": {
            "type": "integer"
          },
          "fieldMappings": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/FieldMapping"
            }
          },
          "isDefault": {
            "type": "boolean"
          },
          "splitFiles": {
            "type": "boolean"
          },
          "splitMode": {
            "type": "string",
            "enum": [
              "max_rows",
              "equal_parts",
              "parts_max_rows"
            ]
          },
          "maxRowsPerFile": {
            "type": "integer",
            "nullable": true,
            "description": "Rows per file when splitFiles is on with splitMode max_rows or parts_max_rows (10-100,000). For an xlsx format keep it at 20,000 or less: an export that would put more than 20,000 rows in one xlsx file returns 400 export_too_large."
          },
          "numberOfParts": {
            "type": "integer",
            "nullable": true
          },
          "includeTotal": {
            "type": "boolean",
            "description": "When the export is split into several files, also produce one combined _total file with every lead. For xlsx the _total file counts toward the 20,000-rows-per-file limit, so an xlsx export of more than 20,000 leads with includeTotal on returns 400 export_too_large."
          },
          "includeHeaders": {
            "type": "boolean"
          },
          "databaseType": {
            "type": "string",
            "enum": [
              "other"
            ],
            "description": "The lead database this format is for: always 'other' (the Main database)."
          },
          "combinedAssigneeColumnName": {
            "type": "string",
            "description": "Default header for assignee column in combined files (1-50 chars)"
          },
          "combinedFileNameColumnName": {
            "type": "string",
            "description": "Default header for file name column in combined files (1-50 chars)"
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "ExportFormatInput": {
        "type": "object",
        "required": [
          "name",
          "fileType",
          "fieldMappings"
        ],
        "properties": {
          "name": {
            "type": "string",
            "maxLength": 100
          },
          "fileType": {
            "type": "string",
            "enum": [
              "csv",
              "json",
              "xlsx"
            ]
          },
          "startRow": {
            "type": "integer",
            "minimum": 1,
            "maximum": 100,
            "default": 1
          },
          "fieldMappings": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/FieldMapping"
            },
            "minItems": 1,
            "description": "The columns of the file, in order. In CSV and XLSX every entry is its own column, so spacers can repeat (even back to back) and two columns may share an exportName. JSON rows are objects, so there a repeated exportName (or a second spacer) keeps only the last value."
          },
          "maxRowsPerFile": {
            "type": "integer",
            "minimum": 10,
            "maximum": 100000,
            "nullable": true,
            "description": "Rows per file when splitFiles is on with splitMode max_rows or parts_max_rows (10-100,000). For an xlsx format keep it at 20,000 or less: an export that would put more than 20,000 rows in one xlsx file returns 400 export_too_large."
          },
          "splitFiles": {
            "type": "boolean",
            "default": false
          },
          "splitMode": {
            "type": "string",
            "enum": [
              "max_rows",
              "equal_parts",
              "parts_max_rows"
            ]
          },
          "numberOfParts": {
            "type": "integer",
            "minimum": 2,
            "maximum": 100,
            "nullable": true
          },
          "includeTotal": {
            "type": "boolean",
            "default": false,
            "description": "When the export is split into several files, also produce one combined _total file with every lead. For xlsx the _total file counts toward the 20,000-rows-per-file limit, so an xlsx export of more than 20,000 leads with includeTotal on returns 400 export_too_large."
          },
          "includeHeaders": {
            "type": "boolean",
            "default": true
          },
          "databaseType": {
            "type": "string",
            "enum": [
              "other"
            ],
            "description": "Lead database this format is for. Only 'other' (the Main database) is available. Legacy values are still accepted and resolve to the Main database.",
            "default": "other"
          },
          "combinedAssigneeColumnName": {
            "type": "string",
            "maxLength": 50,
            "default": "Assigned To",
            "description": "Default header for assignee column in combined files (1-50 chars)"
          },
          "combinedFileNameColumnName": {
            "type": "string",
            "maxLength": 50,
            "default": "File Part",
            "description": "Default header for file name column in combined files (1-50 chars)"
          }
        }
      },
      "ExportFormatUpdate": {
        "type": "object",
        "properties": {
          "name": {
            "type": "string",
            "maxLength": 100
          },
          "fileType": {
            "type": "string",
            "enum": [
              "csv",
              "json",
              "xlsx"
            ]
          },
          "startRow": {
            "type": "integer",
            "minimum": 1,
            "maximum": 100
          },
          "fieldMappings": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/FieldMapping"
            },
            "minItems": 1
          },
          "maxRowsPerFile": {
            "type": "integer",
            "minimum": 10,
            "maximum": 100000,
            "nullable": true,
            "description": "Rows per file when splitFiles is on with splitMode max_rows or parts_max_rows (10-100,000). For an xlsx format keep it at 20,000 or less: an export that would put more than 20,000 rows in one xlsx file returns 400 export_too_large."
          },
          "splitFiles": {
            "type": "boolean"
          },
          "splitMode": {
            "type": "string",
            "enum": [
              "max_rows",
              "equal_parts",
              "parts_max_rows"
            ]
          },
          "numberOfParts": {
            "type": "integer",
            "minimum": 2,
            "maximum": 100,
            "nullable": true
          },
          "includeTotal": {
            "type": "boolean",
            "description": "When the export is split into several files, also produce one combined _total file with every lead. For xlsx the _total file counts toward the 20,000-rows-per-file limit, so an xlsx export of more than 20,000 leads with includeTotal on returns 400 export_too_large."
          },
          "includeHeaders": {
            "type": "boolean"
          },
          "combinedAssigneeColumnName": {
            "type": "string",
            "maxLength": 50,
            "description": "Default header for assignee column in combined files (1-50 chars)"
          },
          "combinedFileNameColumnName": {
            "type": "string",
            "maxLength": 50,
            "description": "Default header for file name column in combined files (1-50 chars)"
          }
        }
      },
      "FieldMapping": {
        "type": "object",
        "required": [
          "sourceField",
          "exportName",
          "type"
        ],
        "properties": {
          "sourceField": {
            "type": "string",
            "description": "Internal field name"
          },
          "exportName": {
            "type": "string",
            "description": "Column header in export"
          },
          "type": {
            "type": "string",
            "enum": [
              "field",
              "spacer"
            ]
          }
        }
      },
      "ExportHistoryEntry": {
        "type": "object",
        "properties": {
          "id": {
            "type": "integer"
          },
          "fileName": {
            "type": "string"
          },
          "fileType": {
            "type": "string"
          },
          "leadCount": {
            "type": "integer"
          },
          "fileSize": {
            "type": "integer"
          },
          "formatName": {
            "type": "string"
          },
          "databaseType": {
            "type": "string",
            "description": "The lead database the export was taken from: 'other' (the Main database) for every new export. Older exports keep the value recorded when they were made."
          },
          "filters": {
            "type": "object",
            "nullable": true
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "expiresAt": {
            "type": "string",
            "format": "date-time",
            "nullable": true,
            "description": "End of the file's 90-day download window. Null when the file was not saved for download."
          },
          "downloadAvailable": {
            "type": "boolean",
            "description": "Whether the file can be downloaded right now (it was saved and its 90-day window has not ended)."
          },
          "unavailableReason": {
            "type": "string",
            "nullable": true,
            "enum": [
              "not_stored",
              "expired",
              null
            ],
            "description": "Why the file cannot be downloaded: expired (past its 90-day window) or not_stored (the file was not saved for download, as with exports made before 90-day file storage was introduced). Null when downloadAvailable is true."
          }
        },
        "description": "One exported file. Each file can be downloaded for 90 days after the export, then it is deleted. The leads themselves stay in Lead Export History, where re-exports cost no credits."
      },
      "BlacklistEntry": {
        "type": "object",
        "properties": {
          "id": {
            "type": "integer"
          },
          "value": {
            "type": "string",
            "description": "Domain to blacklist (e.g. example.com)"
          },
          "entryType": {
            "type": "string",
            "description": "Type of blacklist entry. 'domain' for every new entry; entries created on older accounts may carry a legacy type."
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "BlacklistEntryInput": {
        "type": "object",
        "required": [
          "value",
          "entryType"
        ],
        "properties": {
          "value": {
            "type": "string",
            "description": "Domain to blacklist (e.g. example.com)"
          },
          "entryType": {
            "type": "string",
            "enum": [
              "domain"
            ],
            "description": "Entry type: 'domain' blacklists every lead on that website domain."
          }
        }
      },
      "Error": {
        "type": "object",
        "properties": {
          "error": {
            "type": "string",
            "description": "Error code"
          },
          "message": {
            "type": "string",
            "description": "Human-readable error message"
          },
          "details": {
            "description": "Validation details (present on some 400 validation_error responses)"
          }
        }
      },
      "Integration": {
        "type": "object",
        "properties": {
          "id": {
            "type": "integer"
          },
          "provider": {
            "type": "string",
            "enum": [
              "hubspot",
              "salesforce",
              "pipedrive",
              "zapier",
              "n8n",
              "make",
              "pipedream",
              "clay",
              "generic_webhook"
            ]
          },
          "providerName": {
            "type": "string"
          },
          "displayName": {
            "type": "string",
            "nullable": true
          },
          "externalAccountId": {
            "type": "string",
            "nullable": true,
            "description": "Webhooks: the first 200 characters of the destination URL. CRMs: the connected account (HubSpot hub id, Salesforce instance URL, Pipedrive API domain)."
          },
          "authMethod": {
            "type": "string",
            "enum": [
              "oauth2",
              "webhook",
              "api_key_webhook"
            ]
          },
          "status": {
            "type": "string",
            "enum": [
              "connected",
              "disabled",
              "needs_attention"
            ],
            "description": "connected = sending; disabled = paused by the owner or an API key (nothing is sent); needs_attention = set by SMB Sales Boost after 5 failed deliveries in a row, pushes, tests and retries included (background events are not sent and pushes are refused until it is re-enabled; a CRM integration also becomes connected again when the same CRM account is connected again). You can only set connected or disabled."
          },
          "metadata": {
            "type": "object",
            "additionalProperties": true,
            "nullable": true,
            "description": "null for webhook integrations (Zapier, n8n, Make, Pipedream, Clay, Generic Webhook). For CRMs: connection details (hubId / instanceUrl / apiDomain), cached field lists, the field mapping and Pipedrive deal settings."
          },
          "consecutiveFailures": {
            "type": "integer",
            "description": "Failed deliveries in a row (live events, pushes, tests and retries all count); 5 set status needs_attention. One failed delivery is, for example, a live webhook event whose 5 attempts all failed, a failed CRM write, a failed push, a retry that could not reach the destination, or a test the destination rejected. A successful delivery, re-enabling the integration, or reconnecting the same CRM account resets it to 0."
          },
          "lastFailureAt": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "lastSuccessAt": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "tokenExpiresAt": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "updatedAt": {
            "type": "string",
            "format": "date-time"
          },
          "subscriptions": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "id": {
                  "type": "integer"
                },
                "eventType": {
                  "type": "string",
                  "enum": [
                    "lead.created",
                    "lead.updated",
                    "export.completed",
                    "email_schedule.sent",
                    "credit.low_balance"
                  ]
                },
                "targetUrl": {
                  "type": "string",
                  "nullable": true
                },
                "isActive": {
                  "type": "boolean",
                  "description": "false = the subscription is kept, but live events never use it, and webhook tests and pushes do not either (a CRM test or push still goes to the connected CRM). POST /integrations/{id}/deliveries/{deliveryId}/retry re-sends a past delivery to the URL it was first sent to, whatever the subscription's state now."
                },
                "config": {
                  "type": "object",
                  "additionalProperties": true,
                  "nullable": true
                }
              }
            },
            "description": "Event subscriptions. Each active subscription sends lead data to its targetUrl (webhooks) or the connected CRM on every matching event. HubSpot, Salesforce and Pipedrive integrations take only lead.created and lead.updated; a subscription to any other event returns 400 validation_error."
          },
          "createdVia": {
            "allOf": [
              {
                "$ref": "#/components/schemas/IntegrationChangeSource"
              }
            ],
            "nullable": true,
            "description": "Who created the integration. null = created before this was recorded (unknown)."
          },
          "lastChangedVia": {
            "allOf": [
              {
                "$ref": "#/components/schemas/IntegrationChangeSource"
              }
            ],
            "nullable": true,
            "description": "Who last changed where this integration sends data. Every API key (REST or MCP) change that sends data somewhere new is recorded (a new destination URL, events added or re-activated, re-enabling). A dashboard change is recorded only when it adds a destination URL the integration never had. Pausing the integration (including the pause offered when an API key is revoked), and resuming it, switching on more events or switching on a URL that was already on one of its subscriptions (for example one an API key added on a switched-off subscription) from the dashboard, keep the existing value, so an integration an API key re-pointed keeps showing that key. null = not changed since it was created."
          },
          "lastChangedAt": {
            "type": "string",
            "format": "date-time",
            "nullable": true,
            "description": "When lastChangedVia was recorded."
          }
        },
        "description": "An integration. Never includes OAuth tokens, the signing secret or stored API keys."
      },
      "Enrichment": {
        "type": "object",
        "properties": {
          "id": {
            "type": "integer"
          },
          "status": {
            "type": "string",
            "enum": [
              "queued",
              "matching",
              "scraping",
              "merging",
              "delivering",
              "completed",
              "partial",
              "failed",
              "cancelled"
            ],
            "description": "`partial` means the run was cut short by your credit limit (some URLs were skipped for budget); URLs that returned no data do not make a run partial. You were only charged for what you received. `delivering` is a short step while the file is published."
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "deliveredAt": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "options": {
            "type": "object",
            "properties": {
              "maxCredits": {
                "type": "number",
                "nullable": true,
                "description": "null means the whole available balance."
              },
              "databaseMatchPercent": {
                "type": "integer"
              },
              "contactTypes": {
                "type": "string",
                "enum": [
                  "phone",
                  "email",
                  "either"
                ]
              },
              "emailResults": {
                "type": "boolean"
              }
            }
          },
          "urls": {
            "type": "object",
            "properties": {
              "submitted": {
                "type": "integer"
              },
              "duplicatesRemoved": {
                "type": "integer"
              },
              "skippedInvalid": {
                "type": "integer"
              }
            }
          },
          "database": {
            "type": "object",
            "properties": {
              "matched": {
                "type": "integer"
              },
              "delivered": {
                "type": "integer"
              },
              "charged": {
                "type": "integer"
              },
              "free": {
                "type": "integer",
                "description": "Records you had already bought. Returned at no charge."
              },
              "skippedOverBudget": {
                "type": "integer"
              },
              "noRequestedContact": {
                "type": "integer"
              }
            }
          },
          "live": {
            "type": "object",
            "properties": {
              "sentToScraper": {
                "type": "integer"
              },
              "delivered": {
                "type": "integer"
              },
              "failed": {
                "type": "integer"
              },
              "noRequestedContact": {
                "type": "integer"
              },
              "skippedOverBudget": {
                "type": "integer"
              }
            }
          },
          "credits": {
            "type": "object",
            "properties": {
              "databaseCharged": {
                "type": "number"
              },
              "liveCharged": {
                "type": "number"
              },
              "total": {
                "type": "number"
              },
              "final": {
                "type": "boolean",
                "description": "False while the live half is still unsettled."
              }
            }
          },
          "result": {
            "type": "object",
            "properties": {
              "rowCount": {
                "type": "integer",
                "nullable": true
              },
              "ready": {
                "type": "boolean"
              }
            }
          },
          "error": {
            "type": "string",
            "nullable": true
          }
        }
      },
      "RateLimitError": {
        "type": "object",
        "properties": {
          "error": {
            "type": "string",
            "description": "Always 'Rate limit exceeded'",
            "example": "Rate limit exceeded"
          },
          "message": {
            "type": "string",
            "description": "Human-readable message naming the limit that was hit"
          },
          "retryAfter": {
            "type": "integer",
            "description": "Seconds to wait before retrying (same value as the Retry-After header)"
          }
        }
      },
      "InsufficientCreditsError": {
        "type": "object",
        "properties": {
          "error": {
            "type": "string",
            "enum": [
              "insufficient_credits"
            ]
          },
          "message": {
            "type": "string"
          },
          "creditsRemaining": {
            "type": "integer",
            "description": "Your total remaining credits at the time of the refusal"
          }
        }
      },
      "IntegrationChangeSource": {
        "type": "object",
        "description": "Which channel made a change, and with which API key.",
        "properties": {
          "channel": {
            "type": "string",
            "enum": [
              "dashboard",
              "api",
              "mcp"
            ],
            "description": "dashboard = signed in to the web app; api = REST API with an API key; mcp = the MCP server with an API key"
          },
          "apiKey": {
            "type": "object",
            "nullable": true,
            "description": "The API key used (null for dashboard changes). Never includes the key itself.",
            "properties": {
              "id": {
                "type": "integer"
              },
              "keyPrefix": {
                "type": "string",
                "example": "smbk_AbCdEfG..."
              },
              "name": {
                "type": "string"
              },
              "revoked": {
                "type": "boolean"
              }
            }
          }
        }
      },
      "IntegrationProvider": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "enum": [
              "hubspot",
              "salesforce",
              "pipedrive",
              "zapier",
              "n8n",
              "make",
              "pipedream",
              "clay",
              "generic_webhook"
            ]
          },
          "name": {
            "type": "string",
            "example": "Zapier"
          },
          "tagline": {
            "type": "string"
          },
          "description": {
            "type": "string"
          },
          "authMethod": {
            "type": "string",
            "enum": [
              "oauth2",
              "webhook",
              "api_key_webhook"
            ]
          },
          "ownerSetupRequired": {
            "type": "boolean",
            "description": "true for the CRMs (HubSpot, Salesforce, Pipedrive): they need an OAuth app configured for this deployment"
          },
          "defaultEvents": {
            "type": "array",
            "items": {
              "type": "string",
              "enum": [
                "lead.created",
                "lead.updated",
                "export.completed",
                "email_schedule.sent",
                "credit.low_balance"
              ]
            }
          },
          "supportsPerLeadPush": {
            "type": "boolean"
          },
          "setupHelpUrl": {
            "type": "string",
            "format": "uri"
          },
          "helpText": {
            "type": "string"
          },
          "available": {
            "type": "boolean",
            "description": "false when the provider is not configured for this deployment; creating or connecting it then returns 503 provider_unavailable"
          },
          "reason": {
            "type": "string",
            "description": "Why the provider is unavailable (only when available is false)"
          }
        }
      },
      "IntegrationDelivery": {
        "type": "object",
        "properties": {
          "id": {
            "type": "integer"
          },
          "eventType": {
            "type": "string"
          },
          "targetUrl": {
            "type": "string",
            "nullable": true
          },
          "status": {
            "type": "string",
            "enum": [
              "success",
              "failed",
              "pending"
            ]
          },
          "statusCode": {
            "type": "integer",
            "nullable": true
          },
          "attemptCount": {
            "type": "integer"
          },
          "latencyMs": {
            "type": "integer",
            "nullable": true
          },
          "errorMessage": {
            "type": "string",
            "nullable": true
          },
          "responseBody": {
            "type": "string",
            "nullable": true,
            "description": "The start of the destination's response body (up to 1,000 characters)"
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "completedAt": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          }
        }
      },
      "IntegrationDeliveryResult": {
        "type": "object",
        "properties": {
          "status": {
            "type": "string",
            "enum": [
              "success",
              "failed"
            ]
          },
          "statusCode": {
            "type": "integer",
            "description": "The destination's HTTP status (webhooks)"
          },
          "latencyMs": {
            "type": "integer"
          },
          "error": {
            "type": "string",
            "description": "Why the delivery failed (when status is failed)"
          }
        }
      },
      "IntegrationFieldMapping": {
        "type": "object",
        "description": "Which CRM field receives each SMB Sales Boost lead field, per record type. HubSpot uses contact and company, Salesforce uses lead, Pipedrive uses person and organization. Fields left out use the built-in defaults.",
        "properties": {
          "contact": {
            "type": "object",
            "additionalProperties": {
              "type": "string"
            },
            "description": "SMB Sales Boost field id -> CRM field key"
          },
          "company": {
            "type": "object",
            "additionalProperties": {
              "type": "string"
            },
            "description": "SMB Sales Boost field id -> CRM field key"
          },
          "lead": {
            "type": "object",
            "additionalProperties": {
              "type": "string"
            },
            "description": "SMB Sales Boost field id -> CRM field key"
          },
          "person": {
            "type": "object",
            "additionalProperties": {
              "type": "string"
            },
            "description": "SMB Sales Boost field id -> CRM field key"
          },
          "organization": {
            "type": "object",
            "additionalProperties": {
              "type": "string"
            },
            "description": "SMB Sales Boost field id -> CRM field key"
          }
        }
      },
      "IntegrationFieldCatalog": {
        "type": "object",
        "description": "The SMB Sales Boost lead fields, the CRM's fields (cached for 10 minutes; ?refresh=true reloads them) and the current mapping. HubSpot returns contactFields and companyFields; Salesforce returns leadFields, contactFields and accountFields; Pipedrive returns personFields and organizationFields.",
        "properties": {
          "smbFields": {
            "type": "array",
            "items": {
              "type": "object",
              "additionalProperties": true
            },
            "description": "The SMB Sales Boost lead fields that can be mapped"
          },
          "contactFields": {
            "type": "array",
            "items": {
              "type": "object",
              "additionalProperties": true
            }
          },
          "companyFields": {
            "type": "array",
            "items": {
              "type": "object",
              "additionalProperties": true
            }
          },
          "leadFields": {
            "type": "array",
            "items": {
              "type": "object",
              "additionalProperties": true
            }
          },
          "accountFields": {
            "type": "array",
            "items": {
              "type": "object",
              "additionalProperties": true
            }
          },
          "personFields": {
            "type": "array",
            "items": {
              "type": "object",
              "additionalProperties": true
            }
          },
          "organizationFields": {
            "type": "array",
            "items": {
              "type": "object",
              "additionalProperties": true
            }
          },
          "cachedAt": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "mapping": {
            "$ref": "#/components/schemas/IntegrationFieldMapping"
          }
        }
      },
      "MappingValidationResult": {
        "type": "object",
        "properties": {
          "ok": {
            "type": "boolean"
          },
          "scopes": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "scope": {
                  "type": "string"
                },
                "label": {
                  "type": "string"
                },
                "ok": {
                  "type": "boolean"
                },
                "issues": {
                  "type": "array",
                  "items": {
                    "type": "object",
                    "properties": {
                      "smbFieldId": {
                        "type": "string"
                      },
                      "targetField": {
                        "type": "string"
                      },
                      "message": {
                        "type": "string"
                      },
                      "severity": {
                        "type": "string",
                        "enum": [
                          "error",
                          "warning"
                        ]
                      }
                    }
                  }
                }
              }
            }
          }
        }
      },
      "PipedrivePipeline": {
        "type": "object",
        "properties": {
          "id": {
            "type": "integer"
          },
          "name": {
            "type": "string"
          },
          "active": {
            "type": "boolean"
          },
          "stages": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "id": {
                  "type": "integer"
                },
                "name": {
                  "type": "string"
                },
                "orderNr": {
                  "type": "integer"
                }
              }
            }
          }
        }
      },
      "JobAccepted": {
        "type": "object",
        "description": "Body of a 202 Accepted answer: the request is still running in the background. It is the same from the original request and from GET /jobs/{jobId}.",
        "required": [
          "jobId",
          "status",
          "statusUrl"
        ],
        "properties": {
          "jobId": {
            "type": "string",
            "pattern": "^[a-f0-9]{32}$",
            "description": "The background job (32 hex characters). Also in the Location and X-SMBSB-Job-Id headers."
          },
          "status": {
            "type": "string",
            "enum": [
              "running"
            ],
            "description": "Always running in a 202 answer. A finished job answers with the original request's status instead."
          },
          "kind": {
            "type": "string",
            "description": "What is running, for example api_leads_export"
          },
          "phase": {
            "type": "string",
            "description": "The current step, for example waiting_for_capacity, searching, charging, building_file or saving (exports), sending (schedule triggers), analyzing_website or suggesting (category suggestions), checking, refreshing or loading (Lead Export History)"
          },
          "progress": {
            "type": "object",
            "nullable": true,
            "description": "Progress so far, when the work reports it (null otherwise)",
            "properties": {
              "done": {
                "type": "integer",
                "description": "Items done so far"
              },
              "total": {
                "type": "integer",
                "nullable": true,
                "description": "Items in total, or null while not known yet"
              },
              "label": {
                "type": "string",
                "description": "What is being counted, for example Loading leads"
              }
            }
          },
          "elapsedMs": {
            "type": "integer",
            "description": "Milliseconds since the job started"
          },
          "statusUrl": {
            "type": "string",
            "description": "Where to poll: /api/v1/jobs/{jobId} (same API key; any key of the same account works)"
          },
          "retryAfterSeconds": {
            "type": "integer",
            "description": "Suggested seconds to wait before the next poll (also the Retry-After header)"
          },
          "message": {
            "type": "string",
            "description": "Human-readable explanation of what to do next"
          }
        },
        "example": {
          "jobId": "3f2a9c1e5b7d4a608e1f2c3b4d5e6f70",
          "status": "running",
          "kind": "api_leads_export",
          "phase": "searching",
          "progress": {
            "done": 2000,
            "total": 7000,
            "label": "Loading leads"
          },
          "elapsedMs": 75012,
          "statusUrl": "/api/v1/jobs/3f2a9c1e5b7d4a608e1f2c3b4d5e6f70",
          "retryAfterSeconds": 5,
          "message": "Still working in the background. GET statusUrl with the same API key until it no longer answers 202; it then returns exactly what this request would have returned (kept up to 60 minutes after the job finishes, 24 hours for a schedule send). Poll statusUrl instead of repeating this request: a repeat with the same Idempotency-Key header returns this same job, a repeat without one may run the work again."
        }
      }
    },
    "parameters": {
      "page": {
        "name": "page",
        "in": "query",
        "schema": {
          "type": "integer",
          "default": 1
        },
        "description": "Page number"
      },
      "limit": {
        "name": "limit",
        "in": "query",
        "schema": {
          "type": "integer",
          "default": 100,
          "maximum": 1000
        },
        "description": "Results per page (max 1000)"
      },
      "database": {
        "name": "database",
        "in": "query",
        "schema": {
          "type": "string",
          "enum": [
            "other"
          ],
          "default": "other"
        },
        "description": "Lead database. Only 'other' (the Main database) is available. Kept for backward compatibility: legacy values are still accepted and resolve to the Main database."
      },
      "positiveKeywords": {
        "name": "positiveKeywords",
        "in": "query",
        "schema": {
          "type": "string"
        },
        "description": "JSON array of keywords to include. Matched via OR across default columns. Supports * wildcard for flexible pattern matching (e.g., [\"*account*\", \"*book*keep*\", \"*tax*prep*\"]). Keywords without wildcards perform substring matching by default."
      },
      "negativeKeywords": {
        "name": "negativeKeywords",
        "in": "query",
        "schema": {
          "type": "string"
        },
        "description": "JSON array of keywords to exclude. Checked against same columns as positive keywords. Supports * wildcard (e.g., [\"*franchise*\"])."
      },
      "orColumns": {
        "name": "orColumns",
        "in": "query",
        "schema": {
          "type": "string"
        },
        "description": "JSON array of column names for keyword matching. Overrides defaults for both positive and negative keywords."
      },
      "stateInclude": {
        "name": "stateInclude",
        "in": "query",
        "schema": {
          "type": "string"
        },
        "description": "Comma-separated US state codes to include (e.g. CA,NY,TX)"
      },
      "stateExclude": {
        "name": "stateExclude",
        "in": "query",
        "schema": {
          "type": "string"
        },
        "description": "Comma-separated US state codes to exclude"
      },
      "cityInclude": {
        "name": "cityInclude",
        "in": "query",
        "schema": {
          "type": "string"
        },
        "description": "JSON array of cities to include"
      },
      "cityExclude": {
        "name": "cityExclude",
        "in": "query",
        "schema": {
          "type": "string"
        },
        "description": "JSON array of cities to exclude"
      },
      "zipInclude": {
        "name": "zipInclude",
        "in": "query",
        "schema": {
          "type": "string"
        },
        "description": "JSON array of ZIP codes to include"
      },
      "zipExclude": {
        "name": "zipExclude",
        "in": "query",
        "schema": {
          "type": "string"
        },
        "description": "JSON array of ZIP codes to exclude"
      },
      "nameIncludeTerms": {
        "name": "nameIncludeTerms",
        "in": "query",
        "schema": {
          "type": "string"
        },
        "description": "JSON array of business name include terms"
      },
      "nameExcludeTerms": {
        "name": "nameExcludeTerms",
        "in": "query",
        "schema": {
          "type": "string"
        },
        "description": "JSON array of business name exclude terms"
      },
      "sortBy": {
        "name": "sortBy",
        "in": "query",
        "schema": {
          "type": "string"
        },
        "description": "Field to sort by. Default: lastBuyingSignal (the Last Buying Signal date); the older value lastUpdated is still accepted."
      },
      "sortOrder": {
        "name": "sortOrder",
        "in": "query",
        "schema": {
          "type": "string",
          "enum": [
            "asc",
            "desc"
          ],
          "default": "desc"
        },
        "description": "Sort direction"
      },
      "minReviewCount": {
        "name": "minReviewCount",
        "in": "query",
        "schema": {
          "type": "integer"
        },
        "description": "Minimum Review Count (numeric range)."
      },
      "maxReviewCount": {
        "name": "maxReviewCount",
        "in": "query",
        "schema": {
          "type": "integer"
        },
        "description": "Maximum Review Count (numeric range)."
      },
      "descriptionEmptyFilter": {
        "name": "descriptionEmptyFilter",
        "in": "query",
        "schema": {
          "type": "string",
          "enum": [
            "any",
            "only_empty",
            "exclude_empty"
          ]
        },
        "description": "Empty/non-empty filter on short description."
      },
      "urlIncludeTerms": {
        "name": "urlIncludeTerms",
        "in": "query",
        "schema": {
          "type": "string"
        },
        "description": "JSON array of registered URL include terms"
      },
      "urlExcludeTerms": {
        "name": "urlExcludeTerms",
        "in": "query",
        "schema": {
          "type": "string"
        },
        "description": "JSON array of registered URL exclude terms"
      },
      "crawledUrlIncludeTerms": {
        "name": "crawledUrlIncludeTerms",
        "in": "query",
        "schema": {
          "type": "string"
        },
        "description": "JSON array of crawled URL include terms"
      },
      "crawledUrlExcludeTerms": {
        "name": "crawledUrlExcludeTerms",
        "in": "query",
        "schema": {
          "type": "string"
        },
        "description": "JSON array of crawled URL exclude terms"
      },
      "descriptionIncludeTerms": {
        "name": "descriptionIncludeTerms",
        "in": "query",
        "schema": {
          "type": "string"
        },
        "description": "JSON array of description include terms (matched against Description Short)"
      },
      "descriptionExcludeTerms": {
        "name": "descriptionExcludeTerms",
        "in": "query",
        "schema": {
          "type": "string"
        },
        "description": "JSON array of description exclude terms (matched against Description Short)"
      },
      "emailPrimaryInclude": {
        "name": "emailPrimaryInclude",
        "in": "query",
        "schema": {
          "type": "string"
        },
        "description": "JSON array of primary email terms. Each term is either a bare string (treated as 'contains') or an object {\"v\":\"@acme.com\",\"m\":\"ends\"} where m is one of 'contains'|'starts'|'ends'."
      },
      "emailPrimaryExclude": {
        "name": "emailPrimaryExclude",
        "in": "query",
        "schema": {
          "type": "string"
        },
        "description": "JSON array of primary email exclude terms. Same shape as emailPrimaryInclude; bare string defaults to 'contains'."
      },
      "phonePrimaryInclude": {
        "name": "phonePrimaryInclude",
        "in": "query",
        "schema": {
          "type": "string"
        },
        "description": "JSON array of primary phone terms. Each term is a bare string ('contains') or {\"v\":\"555\",\"m\":\"starts\"} with m in 'contains'|'starts'|'ends'."
      },
      "phonePrimaryExclude": {
        "name": "phonePrimaryExclude",
        "in": "query",
        "schema": {
          "type": "string"
        },
        "description": "JSON array of primary phone exclude terms. Same shape as phonePrimaryInclude."
      },
      "emailTier1Include": {
        "name": "emailTier1Include",
        "in": "query",
        "schema": {
          "type": "string"
        },
        "description": "JSON array of Tier 1 Emails list terms. Same per-term match-mode shape as emailPrimaryInclude."
      },
      "emailTier1Exclude": {
        "name": "emailTier1Exclude",
        "in": "query",
        "schema": {
          "type": "string"
        },
        "description": "JSON array of Tier 1 Emails list exclude terms. Same shape as emailTier1Include."
      },
      "emailTier2Include": {
        "name": "emailTier2Include",
        "in": "query",
        "schema": {
          "type": "string"
        },
        "description": "JSON array of Tier 2 Emails list terms. Same per-term match-mode shape as emailPrimaryInclude."
      },
      "emailTier2Exclude": {
        "name": "emailTier2Exclude",
        "in": "query",
        "schema": {
          "type": "string"
        },
        "description": "JSON array of Tier 2 Emails list exclude terms. Same shape as emailTier2Include."
      },
      "emailTier3Include": {
        "name": "emailTier3Include",
        "in": "query",
        "schema": {
          "type": "string"
        },
        "description": "JSON array of Tier 3 Emails list terms. Same per-term match-mode shape as emailPrimaryInclude."
      },
      "emailTier3Exclude": {
        "name": "emailTier3Exclude",
        "in": "query",
        "schema": {
          "type": "string"
        },
        "description": "JSON array of Tier 3 Emails list exclude terms. Same shape as emailTier3Include."
      },
      "emailTier4Include": {
        "name": "emailTier4Include",
        "in": "query",
        "schema": {
          "type": "string"
        },
        "description": "JSON array of Tier 4 Emails list terms. Same per-term match-mode shape as emailPrimaryInclude."
      },
      "emailTier4Exclude": {
        "name": "emailTier4Exclude",
        "in": "query",
        "schema": {
          "type": "string"
        },
        "description": "JSON array of Tier 4 Emails list exclude terms. Same shape as emailTier4Include."
      },
      "emailTier5Include": {
        "name": "emailTier5Include",
        "in": "query",
        "schema": {
          "type": "string"
        },
        "description": "JSON array of Tier 5 Emails list terms. Same per-term match-mode shape as emailPrimaryInclude."
      },
      "emailTier5Exclude": {
        "name": "emailTier5Exclude",
        "in": "query",
        "schema": {
          "type": "string"
        },
        "description": "JSON array of Tier 5 Emails list exclude terms. Same shape as emailTier5Include."
      },
      "emailTier6Include": {
        "name": "emailTier6Include",
        "in": "query",
        "schema": {
          "type": "string"
        },
        "description": "JSON array of Tier 6 Emails list terms. Same per-term match-mode shape as emailPrimaryInclude."
      },
      "emailTier6Exclude": {
        "name": "emailTier6Exclude",
        "in": "query",
        "schema": {
          "type": "string"
        },
        "description": "JSON array of Tier 6 Emails list exclude terms. Same shape as emailTier6Include."
      },
      "phoneTotalInclude": {
        "name": "phoneTotalInclude",
        "in": "query",
        "schema": {
          "type": "string"
        },
        "description": "JSON array of Total Phones list terms. Same per-term match-mode shape as phonePrimaryInclude."
      },
      "phoneTotalExclude": {
        "name": "phoneTotalExclude",
        "in": "query",
        "schema": {
          "type": "string"
        },
        "description": "JSON array of Total Phones list exclude terms. Same shape as phoneTotalInclude."
      },
      "employeeCountEmptyFilter": {
        "name": "employeeCountEmptyFilter",
        "in": "query",
        "schema": {
          "type": "string",
          "enum": [
            "any",
            "only_empty",
            "exclude_empty"
          ]
        },
        "description": "Empty/non-empty filter on Employees."
      },
      "employeeCountInclude": {
        "name": "employeeCountInclude",
        "in": "query",
        "schema": {
          "type": "string"
        },
        "description": "JSON array of Employees include terms."
      },
      "employeeCountExclude": {
        "name": "employeeCountExclude",
        "in": "query",
        "schema": {
          "type": "string"
        },
        "description": "JSON array of Employees exclude terms."
      },
      "foundingDateEmptyFilter": {
        "name": "foundingDateEmptyFilter",
        "in": "query",
        "schema": {
          "type": "string",
          "enum": [
            "any",
            "only_empty",
            "exclude_empty"
          ]
        },
        "description": "Empty/non-empty filter on Founding Date."
      },
      "foundingDateInclude": {
        "name": "foundingDateInclude",
        "in": "query",
        "schema": {
          "type": "string"
        },
        "description": "JSON array of Founding Date include terms."
      },
      "foundingDateExclude": {
        "name": "foundingDateExclude",
        "in": "query",
        "schema": {
          "type": "string"
        },
        "description": "JSON array of Founding Date exclude terms."
      },
      "isChainEmptyFilter": {
        "name": "isChainEmptyFilter",
        "in": "query",
        "schema": {
          "type": "string",
          "enum": [
            "any",
            "only_empty",
            "exclude_empty"
          ]
        },
        "description": "Empty/non-empty filter on Is Chain?."
      },
      "isChainInclude": {
        "name": "isChainInclude",
        "in": "query",
        "schema": {
          "type": "string"
        },
        "description": "JSON array of Is Chain? include terms."
      },
      "isChainExclude": {
        "name": "isChainExclude",
        "in": "query",
        "schema": {
          "type": "string"
        },
        "description": "JSON array of Is Chain? exclude terms."
      },
      "priceRangeEmptyFilter": {
        "name": "priceRangeEmptyFilter",
        "in": "query",
        "schema": {
          "type": "string",
          "enum": [
            "any",
            "only_empty",
            "exclude_empty"
          ]
        },
        "description": "Empty/non-empty filter on Price Range."
      },
      "priceRangeInclude": {
        "name": "priceRangeInclude",
        "in": "query",
        "schema": {
          "type": "string"
        },
        "description": "JSON array of Price Range include terms."
      },
      "priceRangeExclude": {
        "name": "priceRangeExclude",
        "in": "query",
        "schema": {
          "type": "string"
        },
        "description": "JSON array of Price Range exclude terms."
      },
      "legalNameEmptyFilter": {
        "name": "legalNameEmptyFilter",
        "in": "query",
        "schema": {
          "type": "string",
          "enum": [
            "any",
            "only_empty",
            "exclude_empty"
          ]
        },
        "description": "Empty/non-empty filter on Legal Name."
      },
      "legalNameInclude": {
        "name": "legalNameInclude",
        "in": "query",
        "schema": {
          "type": "string"
        },
        "description": "JSON array of Legal Name include terms."
      },
      "legalNameExclude": {
        "name": "legalNameExclude",
        "in": "query",
        "schema": {
          "type": "string"
        },
        "description": "JSON array of Legal Name exclude terms."
      },
      "industryNaicsEmptyFilter": {
        "name": "industryNaicsEmptyFilter",
        "in": "query",
        "schema": {
          "type": "string",
          "enum": [
            "any",
            "only_empty",
            "exclude_empty"
          ]
        },
        "description": "Empty/non-empty filter on NAICS Code."
      },
      "industryNaicsInclude": {
        "name": "industryNaicsInclude",
        "in": "query",
        "schema": {
          "type": "string"
        },
        "description": "JSON array of NAICS Code include terms."
      },
      "industryNaicsExclude": {
        "name": "industryNaicsExclude",
        "in": "query",
        "schema": {
          "type": "string"
        },
        "description": "JSON array of NAICS Code exclude terms."
      },
      "dunsEmptyFilter": {
        "name": "dunsEmptyFilter",
        "in": "query",
        "schema": {
          "type": "string",
          "enum": [
            "any",
            "only_empty",
            "exclude_empty"
          ]
        },
        "description": "Empty/non-empty filter on DUNS Number."
      },
      "dunsInclude": {
        "name": "dunsInclude",
        "in": "query",
        "schema": {
          "type": "string"
        },
        "description": "JSON array of DUNS Number include terms."
      },
      "dunsExclude": {
        "name": "dunsExclude",
        "in": "query",
        "schema": {
          "type": "string"
        },
        "description": "JSON array of DUNS Number exclude terms."
      },
      "founderEmptyFilter": {
        "name": "founderEmptyFilter",
        "in": "query",
        "schema": {
          "type": "string",
          "enum": [
            "any",
            "only_empty",
            "exclude_empty"
          ]
        },
        "description": "Empty/non-empty filter on Founder Name(s)."
      },
      "founderInclude": {
        "name": "founderInclude",
        "in": "query",
        "schema": {
          "type": "string"
        },
        "description": "JSON array of Founder Name(s) include terms."
      },
      "founderExclude": {
        "name": "founderExclude",
        "in": "query",
        "schema": {
          "type": "string"
        },
        "description": "JSON array of Founder Name(s) exclude terms."
      },
      "founderEmailEmptyFilter": {
        "name": "founderEmailEmptyFilter",
        "in": "query",
        "schema": {
          "type": "string",
          "enum": [
            "any",
            "only_empty",
            "exclude_empty"
          ]
        },
        "description": "Empty/non-empty filter on Founder Email(s)."
      },
      "founderEmailInclude": {
        "name": "founderEmailInclude",
        "in": "query",
        "schema": {
          "type": "string"
        },
        "description": "JSON array of Founder Email(s) include terms. Values are masked until the lead is unlocked/exported."
      },
      "founderEmailExclude": {
        "name": "founderEmailExclude",
        "in": "query",
        "schema": {
          "type": "string"
        },
        "description": "JSON array of Founder Email(s) exclude terms."
      },
      "founderPhoneEmptyFilter": {
        "name": "founderPhoneEmptyFilter",
        "in": "query",
        "schema": {
          "type": "string",
          "enum": [
            "any",
            "only_empty",
            "exclude_empty"
          ]
        },
        "description": "Empty/non-empty filter on Founder Phone(s)."
      },
      "founderPhoneInclude": {
        "name": "founderPhoneInclude",
        "in": "query",
        "schema": {
          "type": "string"
        },
        "description": "JSON array of Founder Phone(s) include terms. Values are masked until the lead is unlocked/exported."
      },
      "founderPhoneExclude": {
        "name": "founderPhoneExclude",
        "in": "query",
        "schema": {
          "type": "string"
        },
        "description": "JSON array of Founder Phone(s) exclude terms."
      },
      "openingHoursEmptyFilter": {
        "name": "openingHoursEmptyFilter",
        "in": "query",
        "schema": {
          "type": "string",
          "enum": [
            "any",
            "only_empty",
            "exclude_empty"
          ]
        },
        "description": "Empty/non-empty filter on Opening Hours."
      },
      "openingHoursInclude": {
        "name": "openingHoursInclude",
        "in": "query",
        "schema": {
          "type": "string"
        },
        "description": "JSON array of Opening Hours include terms."
      },
      "openingHoursExclude": {
        "name": "openingHoursExclude",
        "in": "query",
        "schema": {
          "type": "string"
        },
        "description": "JSON array of Opening Hours exclude terms."
      },
      "areaServedEmptyFilter": {
        "name": "areaServedEmptyFilter",
        "in": "query",
        "schema": {
          "type": "string",
          "enum": [
            "any",
            "only_empty",
            "exclude_empty"
          ]
        },
        "description": "Empty/non-empty filter on Area Served."
      },
      "areaServedInclude": {
        "name": "areaServedInclude",
        "in": "query",
        "schema": {
          "type": "string"
        },
        "description": "JSON array of Area Served include terms."
      },
      "areaServedExclude": {
        "name": "areaServedExclude",
        "in": "query",
        "schema": {
          "type": "string"
        },
        "description": "JSON array of Area Served exclude terms."
      },
      "offersEmptyFilter": {
        "name": "offersEmptyFilter",
        "in": "query",
        "schema": {
          "type": "string",
          "enum": [
            "any",
            "only_empty",
            "exclude_empty"
          ]
        },
        "description": "Empty/non-empty filter on Offers."
      },
      "offersInclude": {
        "name": "offersInclude",
        "in": "query",
        "schema": {
          "type": "string"
        },
        "description": "JSON array of Offers include terms."
      },
      "offersExclude": {
        "name": "offersExclude",
        "in": "query",
        "schema": {
          "type": "string"
        },
        "description": "JSON array of Offers exclude terms."
      },
      "brandEmptyFilter": {
        "name": "brandEmptyFilter",
        "in": "query",
        "schema": {
          "type": "string",
          "enum": [
            "any",
            "only_empty",
            "exclude_empty"
          ]
        },
        "description": "Empty/non-empty filter on Brand."
      },
      "brandInclude": {
        "name": "brandInclude",
        "in": "query",
        "schema": {
          "type": "string"
        },
        "description": "JSON array of Brand include terms."
      },
      "brandExclude": {
        "name": "brandExclude",
        "in": "query",
        "schema": {
          "type": "string"
        },
        "description": "JSON array of Brand exclude terms."
      },
      "sloganEmptyFilter": {
        "name": "sloganEmptyFilter",
        "in": "query",
        "schema": {
          "type": "string",
          "enum": [
            "any",
            "only_empty",
            "exclude_empty"
          ]
        },
        "description": "Empty/non-empty filter on Slogan."
      },
      "sloganInclude": {
        "name": "sloganInclude",
        "in": "query",
        "schema": {
          "type": "string"
        },
        "description": "JSON array of Slogan include terms."
      },
      "sloganExclude": {
        "name": "sloganExclude",
        "in": "query",
        "schema": {
          "type": "string"
        },
        "description": "JSON array of Slogan exclude terms."
      },
      "logoUrlEmptyFilter": {
        "name": "logoUrlEmptyFilter",
        "in": "query",
        "schema": {
          "type": "string",
          "enum": [
            "any",
            "only_empty",
            "exclude_empty"
          ]
        },
        "description": "Empty/non-empty filter on Logo URL."
      },
      "logoUrlInclude": {
        "name": "logoUrlInclude",
        "in": "query",
        "schema": {
          "type": "string"
        },
        "description": "JSON array of Logo URL include terms."
      },
      "logoUrlExclude": {
        "name": "logoUrlExclude",
        "in": "query",
        "schema": {
          "type": "string"
        },
        "description": "JSON array of Logo URL exclude terms."
      },
      "socialProfilesEmptyFilter": {
        "name": "socialProfilesEmptyFilter",
        "in": "query",
        "schema": {
          "type": "string",
          "enum": [
            "any",
            "only_empty",
            "exclude_empty"
          ]
        },
        "description": "Empty/non-empty filter on Social Profiles."
      },
      "socialProfilesInclude": {
        "name": "socialProfilesInclude",
        "in": "query",
        "schema": {
          "type": "string"
        },
        "description": "JSON array of Social Profiles include terms."
      },
      "socialProfilesExclude": {
        "name": "socialProfilesExclude",
        "in": "query",
        "schema": {
          "type": "string"
        },
        "description": "JSON array of Social Profiles exclude terms."
      },
      "platformEmptyFilter": {
        "name": "platformEmptyFilter",
        "in": "query",
        "schema": {
          "type": "string",
          "enum": [
            "any",
            "only_empty",
            "exclude_empty"
          ]
        },
        "description": "Empty/non-empty filter on Platform."
      },
      "platformInclude": {
        "name": "platformInclude",
        "in": "query",
        "schema": {
          "type": "string"
        },
        "description": "JSON array of Platform include terms."
      },
      "platformExclude": {
        "name": "platformExclude",
        "in": "query",
        "schema": {
          "type": "string"
        },
        "description": "JSON array of Platform exclude terms."
      },
      "bookingOpsCrmSoftwareEmptyFilter": {
        "name": "bookingOpsCrmSoftwareEmptyFilter",
        "in": "query",
        "schema": {
          "type": "string",
          "enum": [
            "any",
            "only_empty",
            "exclude_empty"
          ]
        },
        "description": "Empty/non-empty filter on Booking/Ops/CRM Software."
      },
      "bookingOpsCrmSoftwareInclude": {
        "name": "bookingOpsCrmSoftwareInclude",
        "in": "query",
        "schema": {
          "type": "string"
        },
        "description": "JSON array of Booking/Ops/CRM Software include terms."
      },
      "bookingOpsCrmSoftwareExclude": {
        "name": "bookingOpsCrmSoftwareExclude",
        "in": "query",
        "schema": {
          "type": "string"
        },
        "description": "JSON array of Booking/Ops/CRM Software exclude terms."
      },
      "reputationChatSoftwareEmptyFilter": {
        "name": "reputationChatSoftwareEmptyFilter",
        "in": "query",
        "schema": {
          "type": "string",
          "enum": [
            "any",
            "only_empty",
            "exclude_empty"
          ]
        },
        "description": "Empty/non-empty filter on Reputation/Chat Software."
      },
      "reputationChatSoftwareInclude": {
        "name": "reputationChatSoftwareInclude",
        "in": "query",
        "schema": {
          "type": "string"
        },
        "description": "JSON array of Reputation/Chat Software include terms."
      },
      "reputationChatSoftwareExclude": {
        "name": "reputationChatSoftwareExclude",
        "in": "query",
        "schema": {
          "type": "string"
        },
        "description": "JSON array of Reputation/Chat Software exclude terms."
      },
      "marketingEmailSoftwareEmptyFilter": {
        "name": "marketingEmailSoftwareEmptyFilter",
        "in": "query",
        "schema": {
          "type": "string",
          "enum": [
            "any",
            "only_empty",
            "exclude_empty"
          ]
        },
        "description": "Empty/non-empty filter on Marketing/Email Software."
      },
      "marketingEmailSoftwareInclude": {
        "name": "marketingEmailSoftwareInclude",
        "in": "query",
        "schema": {
          "type": "string"
        },
        "description": "JSON array of Marketing/Email Software include terms."
      },
      "marketingEmailSoftwareExclude": {
        "name": "marketingEmailSoftwareExclude",
        "in": "query",
        "schema": {
          "type": "string"
        },
        "description": "JSON array of Marketing/Email Software exclude terms."
      },
      "adPixelsEmptyFilter": {
        "name": "adPixelsEmptyFilter",
        "in": "query",
        "schema": {
          "type": "string",
          "enum": [
            "any",
            "only_empty",
            "exclude_empty"
          ]
        },
        "description": "Empty/non-empty filter on Ad Pixels."
      },
      "adPixelsInclude": {
        "name": "adPixelsInclude",
        "in": "query",
        "schema": {
          "type": "string"
        },
        "description": "JSON array of Ad Pixels include terms."
      },
      "adPixelsExclude": {
        "name": "adPixelsExclude",
        "in": "query",
        "schema": {
          "type": "string"
        },
        "description": "JSON array of Ad Pixels exclude terms."
      },
      "paymentFinancingSoftwareEmptyFilter": {
        "name": "paymentFinancingSoftwareEmptyFilter",
        "in": "query",
        "schema": {
          "type": "string",
          "enum": [
            "any",
            "only_empty",
            "exclude_empty"
          ]
        },
        "description": "Empty/non-empty filter on Payment/Financing Software."
      },
      "paymentFinancingSoftwareInclude": {
        "name": "paymentFinancingSoftwareInclude",
        "in": "query",
        "schema": {
          "type": "string"
        },
        "description": "JSON array of Payment/Financing Software include terms."
      },
      "paymentFinancingSoftwareExclude": {
        "name": "paymentFinancingSoftwareExclude",
        "in": "query",
        "schema": {
          "type": "string"
        },
        "description": "JSON array of Payment/Financing Software exclude terms."
      },
      "emailPrimaryMxValues": {
        "name": "emailPrimaryMxValues",
        "in": "query",
        "schema": {
          "type": "string"
        },
        "description": "JSON array of exact Email Primary Provider values to match (IN filter)."
      },
      "emailPrimaryMxEmptyFilter": {
        "name": "emailPrimaryMxEmptyFilter",
        "in": "query",
        "schema": {
          "type": "string",
          "enum": [
            "any",
            "only_empty",
            "exclude_empty"
          ]
        },
        "description": "Empty/non-empty filter on Email Primary Provider."
      },
      "emailDomainMxValues": {
        "name": "emailDomainMxValues",
        "in": "query",
        "schema": {
          "type": "string"
        },
        "description": "JSON array of exact Website Email Provider values to match (IN filter)."
      },
      "emailDomainMxEmptyFilter": {
        "name": "emailDomainMxEmptyFilter",
        "in": "query",
        "schema": {
          "type": "string",
          "enum": [
            "any",
            "only_empty",
            "exclude_empty"
          ]
        },
        "description": "Empty/non-empty filter on Website Email Provider."
      },
      "emailSecurityValues": {
        "name": "emailSecurityValues",
        "in": "query",
        "schema": {
          "type": "string"
        },
        "description": "JSON array of exact Website Email Security values to match (IN filter)."
      },
      "emailSecurityEmptyFilter": {
        "name": "emailSecurityEmptyFilter",
        "in": "query",
        "schema": {
          "type": "string",
          "enum": [
            "any",
            "only_empty",
            "exclude_empty"
          ]
        },
        "description": "Empty/non-empty filter on Website Email Security."
      },
      "addressSourceValues": {
        "name": "addressSourceValues",
        "in": "query",
        "schema": {
          "type": "string"
        },
        "description": "JSON array of exact Address Source values to match (IN filter)."
      },
      "addressSourceEmptyFilter": {
        "name": "addressSourceEmptyFilter",
        "in": "query",
        "schema": {
          "type": "string",
          "enum": [
            "any",
            "only_empty",
            "exclude_empty"
          ]
        },
        "description": "Empty/non-empty filter on Address Source."
      },
      "minNumLocations": {
        "name": "minNumLocations",
        "in": "query",
        "schema": {
          "type": "number"
        },
        "description": "Minimum Number of Locations (numeric range)."
      },
      "maxNumLocations": {
        "name": "maxNumLocations",
        "in": "query",
        "schema": {
          "type": "number"
        },
        "description": "Maximum Number of Locations (numeric range)."
      },
      "minOpenPositions": {
        "name": "minOpenPositions",
        "in": "query",
        "schema": {
          "type": "number"
        },
        "description": "Minimum Number of Job Postings (numeric range)."
      },
      "maxOpenPositions": {
        "name": "maxOpenPositions",
        "in": "query",
        "schema": {
          "type": "number"
        },
        "description": "Maximum Number of Job Postings (numeric range)."
      },
      "openPositionsEmptyFilter": {
        "name": "openPositionsEmptyFilter",
        "in": "query",
        "schema": {
          "type": "string",
          "enum": [
            "any",
            "only_empty",
            "exclude_empty"
          ]
        },
        "description": "Empty/non-empty filter on Number of Job Postings. Empty = no careers page found; '0' = careers page exists but has no listings."
      },
      "minRatingValue": {
        "name": "minRatingValue",
        "in": "query",
        "schema": {
          "type": "number"
        },
        "description": "Minimum Rating Value (numeric range)."
      },
      "maxRatingValue": {
        "name": "maxRatingValue",
        "in": "query",
        "schema": {
          "type": "number"
        },
        "description": "Maximum Rating Value (numeric range)."
      },
      "redirectFilter": {
        "name": "redirectFilter",
        "in": "query",
        "schema": {
          "type": "string",
          "enum": [
            "yes",
            "no"
          ]
        },
        "description": "Filter by redirect status"
      },
      "buyingSignalTypeFilter": {
        "name": "buyingSignalTypeFilter",
        "in": "query",
        "schema": {
          "type": "string"
        },
        "description": "Comma-separated buying signal types to filter by (e.g., Newly Added,Phone Primary,Facebook); repeating the parameter also works. Returns leads whose Buying Signal Type list contains any of the values. Values: Newly Added, Phone Primary, Total Phones, Email Primary, Tier 1 Emails, Tier 2 Emails, Address Full, Instagram, LinkedIn, Facebook, YouTube, TikTok, X (Twitter), Google Maps, Yelp, Pinterest, Other Social (the social-source values fire when a profile for that source is added or changed). Replaces updateReasonFilter, which is still accepted."
      },
      "updateReasonFilter": {
        "name": "updateReasonFilter",
        "in": "query",
        "schema": {
          "type": "string"
        },
        "description": "Deprecated alias of buyingSignalTypeFilter, still accepted. Ignored when buyingSignalTypeFilter is also sent.",
        "deprecated": true
      },
      "websiteSchemaFilter": {
        "name": "websiteSchemaFilter",
        "in": "query",
        "schema": {
          "type": "string"
        },
        "description": "Comma-separated website schema types to filter by (e.g., LocalBusiness,Organization). Use GET /leads/other/schema-types for available values."
      },
      "lastBuyingSignalFrom": {
        "name": "lastBuyingSignalFrom",
        "in": "query",
        "schema": {
          "type": "string",
          "format": "date"
        },
        "description": "Filter leads by Last Buying Signal date (after this date). A buying signal means any of the key contact/address fields changed (primary phone, Total Phones, primary email, the Tier 1-2 email lists, or full address) or a social profile was added or changed (Facebook, Instagram, LinkedIn, YouTube, TikTok, X (Twitter), Google Maps, Yelp, Pinterest, or other sources). Also includes newly added records. This is the primary way to identify the freshest leads - many businesses launch a website before adding contact info, so Last Buying Signal captures when that information first becomes available. Accepts ISO 8601 or relative format (e.g. rel:7d). Replaces lastUpdatedFrom, which is still accepted."
      },
      "lastUpdatedFrom": {
        "name": "lastUpdatedFrom",
        "in": "query",
        "schema": {
          "type": "string",
          "format": "date"
        },
        "description": "Deprecated alias of lastBuyingSignalFrom, still accepted. Ignored when lastBuyingSignalFrom is also sent.",
        "deprecated": true
      },
      "lastBuyingSignalTo": {
        "name": "lastBuyingSignalTo",
        "in": "query",
        "schema": {
          "type": "string",
          "format": "date"
        },
        "description": "Filter leads by Last Buying Signal date (before this date). Replaces lastUpdatedTo, which is still accepted."
      },
      "lastUpdatedTo": {
        "name": "lastUpdatedTo",
        "in": "query",
        "schema": {
          "type": "string",
          "format": "date"
        },
        "description": "Deprecated alias of lastBuyingSignalTo, still accepted. Ignored when lastBuyingSignalTo is also sent.",
        "deprecated": true
      },
      "timeScrapedFrom": {
        "name": "timeScrapedFrom",
        "in": "query",
        "schema": {
          "type": "string",
          "format": "date"
        },
        "description": "Filter leads scraped after this date (ISO 8601 or relative format e.g. rel:30d)"
      },
      "timeScrapedTo": {
        "name": "timeScrapedTo",
        "in": "query",
        "schema": {
          "type": "string",
          "format": "date"
        },
        "description": "Filter leads scraped before this date (ISO 8601)"
      },
      "registrationDateFrom": {
        "name": "registrationDateFrom",
        "in": "query",
        "schema": {
          "type": "string",
          "format": "date"
        },
        "description": "Filter by domain registration date start. Accepts ISO or relative format (e.g. rel:6m)"
      },
      "registrationDateTo": {
        "name": "registrationDateTo",
        "in": "query",
        "schema": {
          "type": "string",
          "format": "date"
        },
        "description": "Filter by domain registration date end (ISO 8601)"
      },
      "search": {
        "name": "search",
        "in": "query",
        "schema": {
          "type": "string"
        },
        "description": "Case-insensitive substring search across company name, registered URL, crawled URL, short website description, city and primary phone. * is not a wildcard here, and search does not count as a positive filter."
      },
      "streetInclude": {
        "name": "streetInclude",
        "in": "query",
        "schema": {
          "type": "string"
        },
        "description": "JSON array of street address substrings to include (ILIKE %term%), matched against the Street column."
      },
      "streetExclude": {
        "name": "streetExclude",
        "in": "query",
        "schema": {
          "type": "string"
        },
        "description": "JSON array of street address substrings to exclude."
      },
      "streetEmptyFilter": {
        "name": "streetEmptyFilter",
        "in": "query",
        "schema": {
          "type": "string",
          "enum": [
            "any",
            "only_empty",
            "exclude_empty"
          ]
        },
        "description": "Empty/non-empty filter on street address. 'only_empty' returns rows where street is NULL or ''. 'exclude_empty' returns rows where street is non-empty."
      },
      "cityEmptyFilter": {
        "name": "cityEmptyFilter",
        "in": "query",
        "schema": {
          "type": "string",
          "enum": [
            "any",
            "only_empty",
            "exclude_empty"
          ]
        },
        "description": "Empty/non-empty filter on city."
      },
      "stateEmptyFilter": {
        "name": "stateEmptyFilter",
        "in": "query",
        "schema": {
          "type": "string",
          "enum": [
            "any",
            "only_empty",
            "exclude_empty"
          ]
        },
        "description": "Empty/non-empty filter on state."
      },
      "zipEmptyFilter": {
        "name": "zipEmptyFilter",
        "in": "query",
        "schema": {
          "type": "string",
          "enum": [
            "any",
            "only_empty",
            "exclude_empty"
          ]
        },
        "description": "Empty/non-empty filter on ZIP."
      },
      "phonePrimaryEmptyFilter": {
        "name": "phonePrimaryEmptyFilter",
        "in": "query",
        "schema": {
          "type": "string",
          "enum": [
            "any",
            "only_empty",
            "exclude_empty"
          ]
        },
        "description": "Empty/non-empty filter on primary phone."
      },
      "phoneTotalEmptyFilter": {
        "name": "phoneTotalEmptyFilter",
        "in": "query",
        "schema": {
          "type": "string",
          "enum": [
            "any",
            "only_empty",
            "exclude_empty"
          ]
        },
        "description": "Empty/non-empty filter on Total Phones list."
      },
      "emailPrimaryEmptyFilter": {
        "name": "emailPrimaryEmptyFilter",
        "in": "query",
        "schema": {
          "type": "string",
          "enum": [
            "any",
            "only_empty",
            "exclude_empty"
          ]
        },
        "description": "Empty/non-empty filter on primary email."
      },
      "emailTier1EmptyFilter": {
        "name": "emailTier1EmptyFilter",
        "in": "query",
        "schema": {
          "type": "string",
          "enum": [
            "any",
            "only_empty",
            "exclude_empty"
          ]
        },
        "description": "Empty/non-empty filter on Tier 1 Emails list."
      },
      "emailTier2EmptyFilter": {
        "name": "emailTier2EmptyFilter",
        "in": "query",
        "schema": {
          "type": "string",
          "enum": [
            "any",
            "only_empty",
            "exclude_empty"
          ]
        },
        "description": "Empty/non-empty filter on Tier 2 Emails list."
      },
      "emailTier3EmptyFilter": {
        "name": "emailTier3EmptyFilter",
        "in": "query",
        "schema": {
          "type": "string",
          "enum": [
            "any",
            "only_empty",
            "exclude_empty"
          ]
        },
        "description": "Empty/non-empty filter on Tier 3 Emails list."
      },
      "emailTier4EmptyFilter": {
        "name": "emailTier4EmptyFilter",
        "in": "query",
        "schema": {
          "type": "string",
          "enum": [
            "any",
            "only_empty",
            "exclude_empty"
          ]
        },
        "description": "Empty/non-empty filter on Tier 4 Emails list."
      },
      "emailTier5EmptyFilter": {
        "name": "emailTier5EmptyFilter",
        "in": "query",
        "schema": {
          "type": "string",
          "enum": [
            "any",
            "only_empty",
            "exclude_empty"
          ]
        },
        "description": "Empty/non-empty filter on Tier 5 Emails list."
      },
      "emailTier6EmptyFilter": {
        "name": "emailTier6EmptyFilter",
        "in": "query",
        "schema": {
          "type": "string",
          "enum": [
            "any",
            "only_empty",
            "exclude_empty"
          ]
        },
        "description": "Empty/non-empty filter on Tier 6 Emails list."
      },
      "JobIdempotencyKey": {
        "name": "Idempotency-Key",
        "in": "header",
        "required": false,
        "schema": {
          "type": "string",
          "pattern": "^[A-Za-z0-9_-]{8,64}$"
        },
        "description": "Optional, 8-64 characters (letters, numbers, underscore, dash). Repeating the request with the same key never runs it again: it returns the running job, its stored answer, or 410 job_result_expired once that answer is no longer kept. The key stays with the job for up to about 25 hours after it finished (about 48 hours for POST /email-schedules/{id}/trigger); a server restart forgets it. An answer of 409, 429 or 503 (and 402 insufficient_credits on POST /leads/export) does not use up the key, so a retry with it runs the request. Every other answer comes back again for the same key: after an error answer, or when you change any argument, use a new key; the same key with a different request answers 409 idempotency_key_reused. A key that does not match the pattern answers 400 bad_request and nothing runs."
      },
      "PreferWait": {
        "name": "Prefer",
        "in": "header",
        "required": false,
        "schema": {
          "type": "string",
          "pattern": "^(wait=[0-9]+|respond-async)$"
        },
        "description": "How long to wait for the work before answering, counted from when the request arrived. wait=N (0-75 seconds; larger values count as 75): answer with the finished result if it is done within N seconds of arrival, otherwise 202 Accepted with a jobId. respond-async: answer 202 Accepted at once. Default: wait up to 75 seconds (less when the request itself took long to arrive). Either way the answer starts within about 90 seconds of arrival. A Preference-Applied header echoes the preference that was honored. Use a shorter wait when your HTTP client times out before 75 seconds."
      }
    },
    "headers": {
      "XCreditsCapped": {
        "schema": {
          "type": "string",
          "enum": [
            "true"
          ]
        },
        "description": "Present (value 'true') when your spendable balance (credit balance plus any overage-budget headroom), not maxCredits, limited how many new leads this request delivered. Absent otherwise."
      },
      "XLeadQueryPath": {
        "schema": {
          "type": "string",
          "enum": [
            "category",
            "sidecar",
            "trigram",
            "cached"
          ]
        },
        "description": "Which search path answered the query. 'trigram' is the slow fallback (it can take a minute or more); 'cached' means the result came from a shared cache and no new scan ran."
      }
    },
    "responses": {
      "BadRequest": {
        "description": "Invalid request parameters",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "example": {
              "error": "bad_request",
              "message": "No valid fields provided"
            }
          }
        }
      },
      "Unauthorized": {
        "description": "Missing, invalid or revoked API key",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "example": {
              "error": "unauthorized",
              "message": "Missing or invalid Authorization header. Use: Bearer <api_key>"
            }
          }
        }
      },
      "Forbidden": {
        "description": "The API key's account has no active or trialing paid subscription (for example it expired or was cancelled)",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "example": {
              "error": "forbidden",
              "message": "API access requires an active paid subscription (Starter, Growth, Scale, Pro, Platinum, Enterprise, or Agency). Your subscription may have expired or been canceled."
            }
          }
        }
      },
      "NotFound": {
        "description": "Resource not found",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "example": {
              "error": "not_found",
              "message": "Resource not found"
            }
          }
        }
      },
      "RateLimited": {
        "description": "Rate limit exceeded. Wait for the Retry-After delay (also in the retryAfter body field), then retry.",
        "headers": {
          "Retry-After": {
            "schema": {
              "type": "integer"
            },
            "description": "Seconds to wait before retrying"
          },
          "X-RateLimit-Limit": {
            "schema": {
              "type": "integer"
            },
            "description": "Requests allowed in the window"
          },
          "X-RateLimit-Remaining": {
            "schema": {
              "type": "integer"
            },
            "description": "Requests left in the window (0 on a 429)"
          },
          "X-RateLimit-Reset": {
            "schema": {
              "type": "string",
              "format": "date-time"
            },
            "description": "When the window resets, as an ISO 8601 date-time (for example 2026-10-07T14:05:00.000Z)"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/RateLimitError"
            },
            "example": {
              "error": "Rate limit exceeded",
              "message": "API rate limit exceeded. Please slow down your requests.",
              "retryAfter": 42
            }
          }
        }
      },
      "RateLimitedOrLeadQueryBusy": {
        "description": "Too many requests, for one of two reasons. (1) A rate limit was exceeded: error 'Rate limit exceeded' with a retryAfter field (seconds, the same as the Retry-After header). (2) Lead search capacity is busy: error 'lead_query_capacity_busy' with Retry-After: 10. Both carry the X-RateLimit-* headers (the per-key rate limiter sets them on every request), so tell the two apart by the body's error field, not by the headers. Both are refused before any search runs, so nothing was charged; retry after the Retry-After delay, or narrow your filters so each query returns faster.",
        "headers": {
          "Retry-After": {
            "schema": {
              "type": "integer"
            },
            "description": "Seconds to wait before retrying"
          },
          "X-RateLimit-Limit": {
            "schema": {
              "type": "integer"
            },
            "description": "Requests allowed in the window"
          },
          "X-RateLimit-Remaining": {
            "schema": {
              "type": "integer"
            },
            "description": "Requests left in the window (0 on a 'Rate limit exceeded' 429; a lead_query_capacity_busy 429 shows the normal count)"
          },
          "X-RateLimit-Reset": {
            "schema": {
              "type": "string",
              "format": "date-time"
            },
            "description": "When the window resets, as an ISO 8601 date-time (for example 2026-10-07T14:05:00.000Z)"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "oneOf": [
                {
                  "$ref": "#/components/schemas/RateLimitError"
                },
                {
                  "$ref": "#/components/schemas/Error"
                }
              ]
            },
            "examples": {
              "rateLimited": {
                "summary": "Rate limit exceeded",
                "value": {
                  "error": "Rate limit exceeded",
                  "message": "API rate limit exceeded. Please slow down your requests.",
                  "retryAfter": 42
                }
              },
              "leadQueryCapacityBusy": {
                "summary": "Lead search capacity busy",
                "value": {
                  "error": "lead_query_capacity_busy",
                  "message": "Lead search capacity is currently busy. Retry after the indicated delay, or narrow your filters so each query returns faster."
                }
              }
            }
          }
        }
      },
      "ServiceUnavailable": {
        "description": "service_unavailable: a temporary failure (for example the database could not be reached during authentication). Safe to retry after the Retry-After delay.",
        "headers": {
          "Retry-After": {
            "schema": {
              "type": "integer"
            },
            "description": "Seconds to wait before retrying"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "example": {
              "error": "service_unavailable",
              "message": "Authentication is temporarily unavailable. Retry after the indicated delay."
            }
          }
        }
      },
      "DeleteSuccess": {
        "description": "Resource deleted successfully",
        "content": {
          "application/json": {
            "schema": {
              "type": "object",
              "properties": {
                "data": {
                  "type": "object",
                  "properties": {
                    "message": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          }
        }
      },
      "JobAccepted": {
        "description": "Accepted: still running in the background. Poll GET /jobs/{jobId} (the Location header) with the same API key until it no longer answers 202; it then returns exactly what this request would have returned. Retrying this request with the same Idempotency-Key header returns the same job instead of running it again.",
        "headers": {
          "Location": {
            "schema": {
              "type": "string"
            },
            "description": "Where to poll: /api/v1/jobs/{jobId}"
          },
          "Retry-After": {
            "schema": {
              "type": "integer"
            },
            "description": "Seconds to wait before the next poll"
          },
          "X-SMBSB-Job-Id": {
            "schema": {
              "type": "string",
              "pattern": "^[a-f0-9]{32}$"
            },
            "description": "The background job id"
          },
          "X-SMBSB-Job-Status": {
            "schema": {
              "type": "string",
              "enum": [
                "running"
              ]
            },
            "description": "running"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/JobAccepted"
            },
            "example": {
              "jobId": "3f2a9c1e5b7d4a608e1f2c3b4d5e6f70",
              "status": "running",
              "kind": "api_leads_export",
              "phase": "searching",
              "progress": {
                "done": 2000,
                "total": 7000,
                "label": "Loading leads"
              },
              "elapsedMs": 75012,
              "statusUrl": "/api/v1/jobs/3f2a9c1e5b7d4a608e1f2c3b4d5e6f70",
              "retryAfterSeconds": 5,
              "message": "Still working in the background. GET statusUrl with the same API key until it no longer answers 202; it then returns exactly what this request would have returned (kept up to 60 minutes after the job finishes, 24 hours for a schedule send). Poll statusUrl instead of repeating this request: a repeat with the same Idempotency-Key header returns this same job, a repeat without one may run the work again."
            }
          }
        }
      },
      "JobResultExpired": {
        "description": "job_result_expired: this Idempotency-Key belongs to a job that already finished, and its answer is no longer kept (normally 60 minutes after it finished, 24 hours for a schedule trigger; sooner under heavy load). Nothing was run again. Answered for about a day; the message says where to look (for an export: GET /export-history and GET /lead-export-history; for a schedule trigger: lastSent in GET /email-schedules), so do not simply repeat the request with a new key.",
        "headers": {
          "X-SMBSB-Job-Id": {
            "schema": {
              "type": "string",
              "pattern": "^[a-f0-9]{32}$"
            },
            "description": "The background job id"
          },
          "X-SMBSB-Job-Status": {
            "schema": {
              "type": "string",
              "enum": [
                "expired"
              ]
            },
            "description": "expired"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "examples": {
              "export": {
                "summary": "An export",
                "value": {
                  "error": "job_result_expired",
                  "message": "This export's answer is no longer kept (answers are kept up to 60 minutes after the export finishes). Do not repeat the export: a new export picks new leads first and charges for them. Check what it did first: its files are in GET /api/v1/export-history (kept 90 days) and its leads in GET /api/v1/lead-export-history; a re-run with maxCredits 0 (without excludePurchased) returns leads you already paid for at no charge."
                }
              },
              "scheduleTrigger": {
                "summary": "A schedule trigger",
                "value": {
                  "error": "job_result_expired",
                  "message": "This send's answer is no longer kept (answers are kept up to 24 hours after the send finishes). Do not trigger the schedule again blindly: check lastSent and totalSentCount in GET /api/v1/email-schedules first, and trigger it again only if this send did not go out."
                }
              }
            }
          }
        }
      }
    }
  }
}
