{
  "openapi": "3.1.0",
  "info": {
    "title": "WhoLayer REST API",
    "version": "1.0.0",
    "summary": "Identity and contact data: enrich people, search, verify emails, look up mobiles, company profiles, identified visitors.",
    "description": "JSON over HTTPS. Every endpoint is also a tool on the MCP server at https://mcp.wholayer.com/mcp.\n\nCredits: 1 per work email, 2 per personal email, 5 per mobile, 10 per reverse email lookup (email-only input that finds the person), 0.25 per verified address, 1 per company profile, 1 per 10 search results. Nothing is charged when there is no match. Every response carries X-Credits-Used. Quote any call for free with POST /estimate.\n\nBad results: POST /report. Credits charged for a reported result are refunded if it was charged in the last 30 days, once per result. Refunds in any rolling 30 days are capped at 10% of the credits the workspace spent in that window; reports above the cap wait for review by our team and nothing is refunded automatically.\n\nOne call may spend at most 250 credits unless X-Max-Credits raises it (up to 10,000). Test keys (wl_test_…) return sandbox data and never spend.\n\nBig batches: POST /jobs queues up to 100,000 enrich or verify rows and runs them in the background with the same prices and checks as /bulk; max_credits caps the whole job.\n\nRate limits per key, requests per second: free 2, starter 10, growth 25, scale 100, platform 1000.\n\nWithheld people (opted out, deleted through California DROP, on your suppression list, outside the US, or in a sensitive context) are never returned: endpoints respond 451 person_not_available with a reason and charge nothing. No match is 404 person_not_available.\n\nPlan gates: mobile numbers need the Growth plan or above on live keys; otherwise 403 plan_restricted with required_plan, current_plan, and upgrade_url. Test keys bypass plan gates.\n\nHuman docs: https://wholayer.com/docs/api. Markdown: https://wholayer.com/docs/api.md.",
    "termsOfService": "https://wholayer.com/legal/terms",
    "license": {
      "name": "Proprietary",
      "url": "https://wholayer.com/legal/terms"
    },
    "contact": {
      "name": "WhoLayer",
      "url": "https://wholayer.com/contact"
    }
  },
  "externalDocs": {
    "description": "REST API reference",
    "url": "https://wholayer.com/docs/api"
  },
  "servers": [
    {
      "url": "https://api.wholayer.com/v1"
    }
  ],
  "security": [
    {
      "bearerAuth": []
    }
  ],
  "tags": [
    {
      "name": "People",
      "description": "Enrich and search people."
    },
    {
      "name": "Emails",
      "description": "Email verification."
    },
    {
      "name": "Phones",
      "description": "Mobile lookup with carrier checks."
    },
    {
      "name": "Companies",
      "description": "Company profiles by domain."
    },
    {
      "name": "Visitors",
      "description": "People and companies identified on your sites."
    },
    {
      "name": "Account",
      "description": "Credit balance and caps."
    },
    {
      "name": "Billing",
      "description": "Free cost quotes and bad-data reports with refunds."
    },
    {
      "name": "Compliance",
      "description": "The policy WhoLayer enforces, and your workspace suppression list."
    },
    {
      "name": "Jobs",
      "description": "Async bulk enrich and verify, up to 100,000 rows per job."
    }
  ],
  "paths": {
    "/people/enrich": {
      "post": {
        "operationId": "enrichPerson",
        "tags": [
          "People"
        ],
        "summary": "Enrich a person",
        "description": "Find a person's work email, personal email, and/or mobile from linkedin_url, email, or name + company_domain. Costs 1 credit per work email, 2 per personal email, and 5 per mobile returned, plus 10 for a reverse lookup when you send only an email and we find the person. Nothing if no match. When you send only an email, the reverse lookup returns the person's profile (at least their LinkedIn URL) even if nothing in reveal is found. Revealing mobile needs the Growth plan or above on live keys (403 plan_restricted).",
        "x-credit-cost": {
          "work_email": 1,
          "personal_email": 2,
          "mobile": 5,
          "reverse_email_lookup": 10
        },
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          },
          {
            "$ref": "#/components/parameters/MaxCredits"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "linkedin_url": {
                    "anyOf": [
                      {
                        "type": "string",
                        "format": "uri"
                      },
                      {
                        "type": "null"
                      }
                    ]
                  },
                  "email": {
                    "anyOf": [
                      {
                        "type": "string",
                        "format": "email",
                        "pattern": "^(?:[A-Za-z0-9_'+\\-]+\\.)*[A-Za-z0-9_'+\\-]*[A-Za-z0-9_+-]@(?:[A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$"
                      },
                      {
                        "type": "null"
                      }
                    ]
                  },
                  "name": {
                    "anyOf": [
                      {
                        "type": "string",
                        "minLength": 1,
                        "maxLength": 200
                      },
                      {
                        "type": "null"
                      }
                    ]
                  },
                  "company_domain": {
                    "anyOf": [
                      {
                        "type": "string",
                        "minLength": 3,
                        "maxLength": 253
                      },
                      {
                        "type": "null"
                      }
                    ]
                  },
                  "reveal": {
                    "default": [
                      "work_email"
                    ],
                    "minItems": 1,
                    "type": "array",
                    "items": {
                      "type": "string",
                      "enum": [
                        "work_email",
                        "personal_email",
                        "mobile"
                      ]
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Person with the requested contact details.",
            "headers": {
              "X-Credits-Used": {
                "description": "Credits this call charged (same as credits_used). Sent on every response; 0 when free, on errors, for test keys, and for idempotent replays.",
                "schema": {
                  "type": "number"
                }
              },
              "X-Credits-Remaining": {
                "description": "Credits left in the workspace wallet after this call (omitted when unknown).",
                "schema": {
                  "type": "number"
                }
              },
              "X-WhoLayer-Mode": {
                "description": "live or test, from the API key used.",
                "schema": {
                  "type": "string",
                  "enum": [
                    "live",
                    "test"
                  ]
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "string",
                      "description": "per_… id; pass to /phones/lookup as person_id."
                    },
                    "name": {
                      "anyOf": [
                        {
                          "type": "string"
                        },
                        {
                          "type": "null"
                        }
                      ]
                    },
                    "title": {
                      "anyOf": [
                        {
                          "type": "string"
                        },
                        {
                          "type": "null"
                        }
                      ]
                    },
                    "seniority": {
                      "anyOf": [
                        {
                          "type": "string"
                        },
                        {
                          "type": "null"
                        }
                      ]
                    },
                    "department": {
                      "anyOf": [
                        {
                          "type": "string"
                        },
                        {
                          "type": "null"
                        }
                      ]
                    },
                    "linkedin_url": {
                      "anyOf": [
                        {
                          "type": "string"
                        },
                        {
                          "type": "null"
                        }
                      ]
                    },
                    "company": {
                      "anyOf": [
                        {
                          "type": "object",
                          "properties": {
                            "name": {
                              "type": "string"
                            },
                            "domain": {
                              "type": "string"
                            },
                            "employees": {
                              "anyOf": [
                                {
                                  "type": "integer"
                                },
                                {
                                  "type": "null"
                                }
                              ]
                            }
                          },
                          "required": [
                            "name",
                            "domain",
                            "employees"
                          ]
                        },
                        {
                          "type": "null"
                        }
                      ]
                    },
                    "work_email": {
                      "anyOf": [
                        {
                          "type": "object",
                          "properties": {
                            "address": {
                              "type": "string"
                            },
                            "risk": {
                              "type": "string",
                              "enum": [
                                "A",
                                "B",
                                "C",
                                "D",
                                "F"
                              ]
                            },
                            "verified_at": {
                              "type": "string",
                              "format": "date-time"
                            }
                          },
                          "required": [
                            "address",
                            "risk",
                            "verified_at"
                          ]
                        },
                        {
                          "type": "null"
                        }
                      ]
                    },
                    "personal_email": {
                      "anyOf": [
                        {
                          "type": "object",
                          "properties": {
                            "address": {
                              "type": "string"
                            },
                            "risk": {
                              "type": "string",
                              "enum": [
                                "A",
                                "B",
                                "C",
                                "D",
                                "F"
                              ]
                            },
                            "verified_at": {
                              "type": "string",
                              "format": "date-time"
                            }
                          },
                          "required": [
                            "address",
                            "risk",
                            "verified_at"
                          ]
                        },
                        {
                          "type": "null"
                        }
                      ]
                    },
                    "mobile": {
                      "anyOf": [
                        {
                          "type": "object",
                          "properties": {
                            "number": {
                              "type": "string",
                              "description": "E.164"
                            },
                            "line_type": {
                              "type": "string"
                            },
                            "carrier": {
                              "anyOf": [
                                {
                                  "type": "string"
                                },
                                {
                                  "type": "null"
                                }
                              ]
                            },
                            "checked_at": {
                              "type": "string",
                              "format": "date-time"
                            }
                          },
                          "required": [
                            "number",
                            "line_type",
                            "carrier",
                            "checked_at"
                          ]
                        },
                        {
                          "type": "null"
                        }
                      ]
                    },
                    "credits_used": {
                      "type": "number",
                      "description": "Credits charged for this call. 0 for test keys and idempotent replays."
                    },
                    "test_mode": {
                      "type": "boolean",
                      "description": "Present and true when called with a wl_test_ key (sandbox data, nothing charged)."
                    },
                    "idempotent_replay": {
                      "type": "boolean",
                      "description": "Present and true when this is a stored result replayed for a reused Idempotency-Key (credits_used is 0)."
                    }
                  },
                  "required": [
                    "id",
                    "work_email",
                    "personal_email",
                    "mobile",
                    "credits_used"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "`invalid_request`: A field is missing or malformed, the call could exceed the per-call credit ceiling, or a cursor is invalid. The message says which.",
            "headers": {
              "X-Credits-Used": {
                "schema": {
                  "const": 0
                },
                "description": "Always 0: errors never charge."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "invalid_request",
                    "message": "A field is missing or malformed, the call could exceed the per-call credit ceiling, or a cursor is invalid. The message says which."
                  }
                }
              }
            }
          },
          "401": {
            "description": "`unauthorized`: Missing or invalid API key.",
            "headers": {
              "X-Credits-Used": {
                "schema": {
                  "const": 0
                },
                "description": "Always 0: errors never charge."
              },
              "WWW-Authenticate": {
                "schema": {
                  "type": "string"
                },
                "description": "Bearer realm=\"wholayer\""
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "unauthorized",
                    "message": "Missing or invalid API key."
                  }
                }
              }
            }
          },
          "402": {
            "description": "`credits_exhausted`: Wallet empty (Free plan), monthly spend cap reached, this key's daily cap reached, or (agency client workspaces) the client's monthly budget is used up.",
            "headers": {
              "X-Credits-Used": {
                "schema": {
                  "const": 0
                },
                "description": "Always 0: errors never charge."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "credits_exhausted",
                    "message": "Wallet empty (Free plan), monthly spend cap reached, this key's daily cap reached, or (agency client workspaces) the client's monthly budget is used up."
                  }
                }
              }
            }
          },
          "403": {
            "description": "`forbidden`: A read-only key was used on an endpoint that reveals contact details, spends credits, or changes the suppression list. `plan_restricted`: The workspace's plan doesn't include this feature (mobile numbers need Growth or above on live keys; test keys bypass plan gates). The error carries required_plan, current_plan, and upgrade_url.",
            "headers": {
              "X-Credits-Used": {
                "schema": {
                  "const": 0
                },
                "description": "Always 0: errors never charge."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "forbidden",
                    "message": "A read-only key was used on an endpoint that reveals contact details, spends credits, or changes the suppression list."
                  }
                }
              }
            }
          },
          "404": {
            "description": "`person_not_available`: 404: no match. 451: the person is withheld (opted out, deleted through California DROP, on your suppression list, outside the US, or a sensitive context) and the error carries a reason. You are not charged either way.",
            "headers": {
              "X-Credits-Used": {
                "schema": {
                  "const": 0
                },
                "description": "Always 0: errors never charge."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "person_not_available",
                    "message": "404: no match. 451: the person is withheld (opted out, deleted through California DROP, on your suppression list, outside the US, or a sensitive context) and the error carries a reason. You are not charged either way."
                  }
                }
              }
            }
          },
          "409": {
            "description": "`duplicate_request`: This Idempotency-Key was already used with a different request body. `request_in_progress`: A request with this Idempotency-Key is still running. Retry in a few seconds to get its stored result.",
            "headers": {
              "X-Credits-Used": {
                "schema": {
                  "const": 0
                },
                "description": "Always 0: errors never charge."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "duplicate_request",
                    "message": "This Idempotency-Key was already used with a different request body."
                  }
                }
              }
            }
          },
          "413": {
            "description": "`payload_too_large`: Body over 4 MB (gzip /jobs bodies), more than 1,000 rows sent to /bulk or 100,000 to /jobs, or the suppression list is full.",
            "headers": {
              "X-Credits-Used": {
                "schema": {
                  "const": 0
                },
                "description": "Always 0: errors never charge."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "payload_too_large",
                    "message": "Body over 4 MB (gzip /jobs bodies), more than 1,000 rows sent to /bulk or 100,000 to /jobs, or the suppression list is full."
                  }
                }
              }
            }
          },
          "429": {
            "description": "`rate_limited`: Too many requests per second for your plan, or 2 jobs already queued or running in this workspace. Retry after the Retry-After header (seconds).",
            "headers": {
              "X-Credits-Used": {
                "schema": {
                  "const": 0
                },
                "description": "Always 0: errors never charge."
              },
              "Retry-After": {
                "schema": {
                  "type": "integer"
                },
                "description": "Seconds to wait."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "rate_limited",
                    "message": "Too many requests per second for your plan, or 2 jobs already queued or running in this workspace. Retry after the Retry-After header (seconds)."
                  }
                }
              }
            }
          },
          "451": {
            "description": "`person_not_available` with a `reason` (opted_out, drop_deleted, customer_suppressed, non_us, sensitive): this person is withheld and no data is returned. You are not charged.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "reason": "opted_out",
                    "code": "person_not_available",
                    "message": "This person has opted out of WhoLayer. No data is returned and you were not charged."
                  }
                }
              }
            }
          },
          "500": {
            "description": "`internal_error`: Our fault. Retry with the same Idempotency-Key.",
            "headers": {
              "X-Credits-Used": {
                "schema": {
                  "const": 0
                },
                "description": "Always 0: errors never charge."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "internal_error",
                    "message": "Our fault. Retry with the same Idempotency-Key."
                  }
                }
              }
            }
          },
          "503": {
            "description": "`provider_unavailable`: A data source is temporarily unavailable, or the lookup is switched off on live keys (live people search stays off until its supplier cost is verified). You are not charged. Retry later; test keys always work.",
            "headers": {
              "X-Credits-Used": {
                "schema": {
                  "const": 0
                },
                "description": "Always 0: errors never charge."
              },
              "Retry-After": {
                "schema": {
                  "type": "integer"
                },
                "description": "Seconds to wait."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "provider_unavailable",
                    "message": "A data source is temporarily unavailable, or the lookup is switched off on live keys (live people search stays off until its supplier cost is verified). You are not charged. Retry later; test keys always work."
                  }
                }
              }
            }
          }
        }
      }
    },
    "/people/search": {
      "post": {
        "operationId": "searchPeople",
        "tags": [
          "People"
        ],
        "summary": "Search people",
        "description": "Search by title, seniority, department, company domains, company size, industry, and country. Returns profiles without contact details; reveal them with /people/enrich. Costs 1 credit per 10 results returned, rounded up. Live keys get 503 provider_unavailable until live people search is switched on (PEOPLE_SEARCH_COST_VERIFIED); test keys always work.",
        "x-credit-cost": {
          "per_10_results": 1
        },
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          },
          {
            "$ref": "#/components/parameters/MaxCredits"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "titles": {
                    "maxItems": 12,
                    "type": "array",
                    "items": {
                      "type": "string"
                    }
                  },
                  "seniorities": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    }
                  },
                  "departments": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    }
                  },
                  "company_domains": {
                    "maxItems": 50,
                    "type": "array",
                    "items": {
                      "type": "string"
                    }
                  },
                  "company_employees": {
                    "type": "object",
                    "properties": {
                      "min": {
                        "type": "integer",
                        "minimum": 0,
                        "maximum": 9007199254740991
                      },
                      "max": {
                        "type": "integer",
                        "minimum": 0,
                        "maximum": 9007199254740991
                      }
                    }
                  },
                  "industries": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    }
                  },
                  "countries": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    }
                  },
                  "limit": {
                    "default": 25,
                    "type": "integer",
                    "minimum": 1,
                    "maximum": 100
                  },
                  "cursor": {
                    "type": [
                      "string",
                      "null"
                    ]
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Matching people.",
            "headers": {
              "X-Credits-Used": {
                "description": "Credits this call charged (same as credits_used). Sent on every response; 0 when free, on errors, for test keys, and for idempotent replays.",
                "schema": {
                  "type": "number"
                }
              },
              "X-Credits-Remaining": {
                "description": "Credits left in the workspace wallet after this call (omitted when unknown).",
                "schema": {
                  "type": "number"
                }
              },
              "X-WhoLayer-Mode": {
                "description": "live or test, from the API key used.",
                "schema": {
                  "type": "string",
                  "enum": [
                    "live",
                    "test"
                  ]
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "results": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "id": {
                            "type": "string"
                          },
                          "name": {
                            "type": "string"
                          },
                          "title": {
                            "anyOf": [
                              {
                                "type": "string"
                              },
                              {
                                "type": "null"
                              }
                            ]
                          },
                          "company_domain": {
                            "anyOf": [
                              {
                                "type": "string"
                              },
                              {
                                "type": "null"
                              }
                            ]
                          }
                        },
                        "required": [
                          "id",
                          "name",
                          "title",
                          "company_domain"
                        ]
                      }
                    },
                    "next_cursor": {
                      "anyOf": [
                        {
                          "type": "string"
                        },
                        {
                          "type": "null"
                        }
                      ]
                    },
                    "credits_used": {
                      "type": "number",
                      "description": "Credits charged for this call. 0 for test keys and idempotent replays."
                    },
                    "test_mode": {
                      "type": "boolean",
                      "description": "Present and true when called with a wl_test_ key (sandbox data, nothing charged)."
                    },
                    "idempotent_replay": {
                      "type": "boolean",
                      "description": "Present and true when this is a stored result replayed for a reused Idempotency-Key (credits_used is 0)."
                    }
                  },
                  "required": [
                    "results",
                    "next_cursor",
                    "credits_used"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "`invalid_request`: A field is missing or malformed, the call could exceed the per-call credit ceiling, or a cursor is invalid. The message says which.",
            "headers": {
              "X-Credits-Used": {
                "schema": {
                  "const": 0
                },
                "description": "Always 0: errors never charge."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "invalid_request",
                    "message": "A field is missing or malformed, the call could exceed the per-call credit ceiling, or a cursor is invalid. The message says which."
                  }
                }
              }
            }
          },
          "401": {
            "description": "`unauthorized`: Missing or invalid API key.",
            "headers": {
              "X-Credits-Used": {
                "schema": {
                  "const": 0
                },
                "description": "Always 0: errors never charge."
              },
              "WWW-Authenticate": {
                "schema": {
                  "type": "string"
                },
                "description": "Bearer realm=\"wholayer\""
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "unauthorized",
                    "message": "Missing or invalid API key."
                  }
                }
              }
            }
          },
          "402": {
            "description": "`credits_exhausted`: Wallet empty (Free plan), monthly spend cap reached, this key's daily cap reached, or (agency client workspaces) the client's monthly budget is used up.",
            "headers": {
              "X-Credits-Used": {
                "schema": {
                  "const": 0
                },
                "description": "Always 0: errors never charge."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "credits_exhausted",
                    "message": "Wallet empty (Free plan), monthly spend cap reached, this key's daily cap reached, or (agency client workspaces) the client's monthly budget is used up."
                  }
                }
              }
            }
          },
          "403": {
            "description": "`forbidden`: A read-only key was used on an endpoint that reveals contact details, spends credits, or changes the suppression list.",
            "headers": {
              "X-Credits-Used": {
                "schema": {
                  "const": 0
                },
                "description": "Always 0: errors never charge."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "forbidden",
                    "message": "A read-only key was used on an endpoint that reveals contact details, spends credits, or changes the suppression list."
                  }
                }
              }
            }
          },
          "409": {
            "description": "`duplicate_request`: This Idempotency-Key was already used with a different request body. `request_in_progress`: A request with this Idempotency-Key is still running. Retry in a few seconds to get its stored result.",
            "headers": {
              "X-Credits-Used": {
                "schema": {
                  "const": 0
                },
                "description": "Always 0: errors never charge."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "duplicate_request",
                    "message": "This Idempotency-Key was already used with a different request body."
                  }
                }
              }
            }
          },
          "413": {
            "description": "`payload_too_large`: Body over 4 MB (gzip /jobs bodies), more than 1,000 rows sent to /bulk or 100,000 to /jobs, or the suppression list is full.",
            "headers": {
              "X-Credits-Used": {
                "schema": {
                  "const": 0
                },
                "description": "Always 0: errors never charge."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "payload_too_large",
                    "message": "Body over 4 MB (gzip /jobs bodies), more than 1,000 rows sent to /bulk or 100,000 to /jobs, or the suppression list is full."
                  }
                }
              }
            }
          },
          "429": {
            "description": "`rate_limited`: Too many requests per second for your plan, or 2 jobs already queued or running in this workspace. Retry after the Retry-After header (seconds).",
            "headers": {
              "X-Credits-Used": {
                "schema": {
                  "const": 0
                },
                "description": "Always 0: errors never charge."
              },
              "Retry-After": {
                "schema": {
                  "type": "integer"
                },
                "description": "Seconds to wait."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "rate_limited",
                    "message": "Too many requests per second for your plan, or 2 jobs already queued or running in this workspace. Retry after the Retry-After header (seconds)."
                  }
                }
              }
            }
          },
          "500": {
            "description": "`internal_error`: Our fault. Retry with the same Idempotency-Key.",
            "headers": {
              "X-Credits-Used": {
                "schema": {
                  "const": 0
                },
                "description": "Always 0: errors never charge."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "internal_error",
                    "message": "Our fault. Retry with the same Idempotency-Key."
                  }
                }
              }
            }
          },
          "503": {
            "description": "`provider_unavailable`: A data source is temporarily unavailable, or the lookup is switched off on live keys (live people search stays off until its supplier cost is verified). You are not charged. Retry later; test keys always work.",
            "headers": {
              "X-Credits-Used": {
                "schema": {
                  "const": 0
                },
                "description": "Always 0: errors never charge."
              },
              "Retry-After": {
                "schema": {
                  "type": "integer"
                },
                "description": "Seconds to wait."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "provider_unavailable",
                    "message": "A data source is temporarily unavailable, or the lookup is switched off on live keys (live people search stays off until its supplier cost is verified). You are not charged. Retry later; test keys always work."
                  }
                }
              }
            }
          }
        }
      }
    },
    "/emails/verify": {
      "post": {
        "operationId": "verifyEmails",
        "tags": [
          "Emails"
        ],
        "summary": "Verify emails",
        "description": "Check deliverability for up to 1,000 addresses (deduplicated, case-insensitive), graded A (safe) to F (will bounce). Costs 0.25 credit per address.",
        "x-credit-cost": {
          "per_address": 0.25
        },
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          },
          {
            "$ref": "#/components/parameters/MaxCredits"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "emails": {
                    "minItems": 1,
                    "maxItems": 1000,
                    "type": "array",
                    "items": {
                      "type": "string",
                      "minLength": 3,
                      "maxLength": 320
                    }
                  }
                },
                "required": [
                  "emails"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "One result per unique address.",
            "headers": {
              "X-Credits-Used": {
                "description": "Credits this call charged (same as credits_used). Sent on every response; 0 when free, on errors, for test keys, and for idempotent replays.",
                "schema": {
                  "type": "number"
                }
              },
              "X-Credits-Remaining": {
                "description": "Credits left in the workspace wallet after this call (omitted when unknown).",
                "schema": {
                  "type": "number"
                }
              },
              "X-WhoLayer-Mode": {
                "description": "live or test, from the API key used.",
                "schema": {
                  "type": "string",
                  "enum": [
                    "live",
                    "test"
                  ]
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "results": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "email": {
                            "type": "string"
                          },
                          "status": {
                            "type": "string",
                            "enum": [
                              "deliverable",
                              "risky",
                              "undeliverable",
                              "unknown"
                            ]
                          },
                          "risk": {
                            "type": "string",
                            "enum": [
                              "A",
                              "B",
                              "C",
                              "D",
                              "F"
                            ]
                          },
                          "catch_all": {
                            "type": "boolean"
                          },
                          "reason": {
                            "type": "string",
                            "enum": [
                              "invalid_syntax",
                              "mailbox_not_found"
                            ]
                          }
                        },
                        "required": [
                          "email",
                          "status",
                          "risk",
                          "catch_all"
                        ]
                      }
                    },
                    "credits_used": {
                      "type": "number",
                      "description": "Credits charged for this call. 0 for test keys and idempotent replays."
                    },
                    "test_mode": {
                      "type": "boolean",
                      "description": "Present and true when called with a wl_test_ key (sandbox data, nothing charged)."
                    },
                    "idempotent_replay": {
                      "type": "boolean",
                      "description": "Present and true when this is a stored result replayed for a reused Idempotency-Key (credits_used is 0)."
                    }
                  },
                  "required": [
                    "results",
                    "credits_used"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "`invalid_request`: A field is missing or malformed, the call could exceed the per-call credit ceiling, or a cursor is invalid. The message says which.",
            "headers": {
              "X-Credits-Used": {
                "schema": {
                  "const": 0
                },
                "description": "Always 0: errors never charge."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "invalid_request",
                    "message": "A field is missing or malformed, the call could exceed the per-call credit ceiling, or a cursor is invalid. The message says which."
                  }
                }
              }
            }
          },
          "401": {
            "description": "`unauthorized`: Missing or invalid API key.",
            "headers": {
              "X-Credits-Used": {
                "schema": {
                  "const": 0
                },
                "description": "Always 0: errors never charge."
              },
              "WWW-Authenticate": {
                "schema": {
                  "type": "string"
                },
                "description": "Bearer realm=\"wholayer\""
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "unauthorized",
                    "message": "Missing or invalid API key."
                  }
                }
              }
            }
          },
          "402": {
            "description": "`credits_exhausted`: Wallet empty (Free plan), monthly spend cap reached, this key's daily cap reached, or (agency client workspaces) the client's monthly budget is used up.",
            "headers": {
              "X-Credits-Used": {
                "schema": {
                  "const": 0
                },
                "description": "Always 0: errors never charge."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "credits_exhausted",
                    "message": "Wallet empty (Free plan), monthly spend cap reached, this key's daily cap reached, or (agency client workspaces) the client's monthly budget is used up."
                  }
                }
              }
            }
          },
          "403": {
            "description": "`forbidden`: A read-only key was used on an endpoint that reveals contact details, spends credits, or changes the suppression list.",
            "headers": {
              "X-Credits-Used": {
                "schema": {
                  "const": 0
                },
                "description": "Always 0: errors never charge."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "forbidden",
                    "message": "A read-only key was used on an endpoint that reveals contact details, spends credits, or changes the suppression list."
                  }
                }
              }
            }
          },
          "409": {
            "description": "`duplicate_request`: This Idempotency-Key was already used with a different request body. `request_in_progress`: A request with this Idempotency-Key is still running. Retry in a few seconds to get its stored result.",
            "headers": {
              "X-Credits-Used": {
                "schema": {
                  "const": 0
                },
                "description": "Always 0: errors never charge."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "duplicate_request",
                    "message": "This Idempotency-Key was already used with a different request body."
                  }
                }
              }
            }
          },
          "413": {
            "description": "`payload_too_large`: Body over 4 MB (gzip /jobs bodies), more than 1,000 rows sent to /bulk or 100,000 to /jobs, or the suppression list is full.",
            "headers": {
              "X-Credits-Used": {
                "schema": {
                  "const": 0
                },
                "description": "Always 0: errors never charge."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "payload_too_large",
                    "message": "Body over 4 MB (gzip /jobs bodies), more than 1,000 rows sent to /bulk or 100,000 to /jobs, or the suppression list is full."
                  }
                }
              }
            }
          },
          "429": {
            "description": "`rate_limited`: Too many requests per second for your plan, or 2 jobs already queued or running in this workspace. Retry after the Retry-After header (seconds).",
            "headers": {
              "X-Credits-Used": {
                "schema": {
                  "const": 0
                },
                "description": "Always 0: errors never charge."
              },
              "Retry-After": {
                "schema": {
                  "type": "integer"
                },
                "description": "Seconds to wait."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "rate_limited",
                    "message": "Too many requests per second for your plan, or 2 jobs already queued or running in this workspace. Retry after the Retry-After header (seconds)."
                  }
                }
              }
            }
          },
          "500": {
            "description": "`internal_error`: Our fault. Retry with the same Idempotency-Key.",
            "headers": {
              "X-Credits-Used": {
                "schema": {
                  "const": 0
                },
                "description": "Always 0: errors never charge."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "internal_error",
                    "message": "Our fault. Retry with the same Idempotency-Key."
                  }
                }
              }
            }
          },
          "503": {
            "description": "`provider_unavailable`: A data source is temporarily unavailable, or the lookup is switched off on live keys (live people search stays off until its supplier cost is verified). You are not charged. Retry later; test keys always work.",
            "headers": {
              "X-Credits-Used": {
                "schema": {
                  "const": 0
                },
                "description": "Always 0: errors never charge."
              },
              "Retry-After": {
                "schema": {
                  "type": "integer"
                },
                "description": "Seconds to wait."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "provider_unavailable",
                    "message": "A data source is temporarily unavailable, or the lookup is switched off on live keys (live people search stays off until its supplier cost is verified). You are not charged. Retry later; test keys always work."
                  }
                }
              }
            }
          }
        }
      }
    },
    "/phones/lookup": {
      "post": {
        "operationId": "lookupPhone",
        "tags": [
          "Phones"
        ],
        "summary": "Look up a mobile",
        "description": "Find a mobile with a live carrier and line-type check, by person_id, email, linkedin_url, or name + company_domain. Landlines are not returned. Costs 5 credits when a mobile is returned. Needs the Growth plan or above on live keys (403 plan_restricted).",
        "x-credit-cost": {
          "mobile": 5
        },
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          },
          {
            "$ref": "#/components/parameters/MaxCredits"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "person_id": {
                    "type": [
                      "string",
                      "null"
                    ]
                  },
                  "email": {
                    "anyOf": [
                      {
                        "type": "string",
                        "format": "email",
                        "pattern": "^(?:[A-Za-z0-9_'+\\-]+\\.)*[A-Za-z0-9_'+\\-]*[A-Za-z0-9_+-]@(?:[A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$"
                      },
                      {
                        "type": "null"
                      }
                    ]
                  },
                  "linkedin_url": {
                    "anyOf": [
                      {
                        "type": "string",
                        "format": "uri"
                      },
                      {
                        "type": "null"
                      }
                    ]
                  },
                  "name": {
                    "type": [
                      "string",
                      "null"
                    ]
                  },
                  "company_domain": {
                    "type": [
                      "string",
                      "null"
                    ]
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Mobile number.",
            "headers": {
              "X-Credits-Used": {
                "description": "Credits this call charged (same as credits_used). Sent on every response; 0 when free, on errors, for test keys, and for idempotent replays.",
                "schema": {
                  "type": "number"
                }
              },
              "X-Credits-Remaining": {
                "description": "Credits left in the workspace wallet after this call (omitted when unknown).",
                "schema": {
                  "type": "number"
                }
              },
              "X-WhoLayer-Mode": {
                "description": "live or test, from the API key used.",
                "schema": {
                  "type": "string",
                  "enum": [
                    "live",
                    "test"
                  ]
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "string",
                      "description": "per_… result id (the person_id you sent, or a new one). Pass to /report if the number is wrong."
                    },
                    "number": {
                      "type": "string",
                      "description": "E.164"
                    },
                    "line_type": {
                      "type": "string"
                    },
                    "carrier": {
                      "anyOf": [
                        {
                          "type": "string"
                        },
                        {
                          "type": "null"
                        }
                      ]
                    },
                    "checked_at": {
                      "type": "string",
                      "format": "date-time"
                    },
                    "credits_used": {
                      "type": "number",
                      "description": "Credits charged for this call. 0 for test keys and idempotent replays."
                    },
                    "test_mode": {
                      "type": "boolean",
                      "description": "Present and true when called with a wl_test_ key (sandbox data, nothing charged)."
                    },
                    "idempotent_replay": {
                      "type": "boolean",
                      "description": "Present and true when this is a stored result replayed for a reused Idempotency-Key (credits_used is 0)."
                    }
                  },
                  "required": [
                    "id",
                    "number",
                    "line_type",
                    "carrier",
                    "checked_at",
                    "credits_used"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "`invalid_request`: A field is missing or malformed, the call could exceed the per-call credit ceiling, or a cursor is invalid. The message says which.",
            "headers": {
              "X-Credits-Used": {
                "schema": {
                  "const": 0
                },
                "description": "Always 0: errors never charge."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "invalid_request",
                    "message": "A field is missing or malformed, the call could exceed the per-call credit ceiling, or a cursor is invalid. The message says which."
                  }
                }
              }
            }
          },
          "401": {
            "description": "`unauthorized`: Missing or invalid API key.",
            "headers": {
              "X-Credits-Used": {
                "schema": {
                  "const": 0
                },
                "description": "Always 0: errors never charge."
              },
              "WWW-Authenticate": {
                "schema": {
                  "type": "string"
                },
                "description": "Bearer realm=\"wholayer\""
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "unauthorized",
                    "message": "Missing or invalid API key."
                  }
                }
              }
            }
          },
          "402": {
            "description": "`credits_exhausted`: Wallet empty (Free plan), monthly spend cap reached, this key's daily cap reached, or (agency client workspaces) the client's monthly budget is used up.",
            "headers": {
              "X-Credits-Used": {
                "schema": {
                  "const": 0
                },
                "description": "Always 0: errors never charge."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "credits_exhausted",
                    "message": "Wallet empty (Free plan), monthly spend cap reached, this key's daily cap reached, or (agency client workspaces) the client's monthly budget is used up."
                  }
                }
              }
            }
          },
          "403": {
            "description": "`forbidden`: A read-only key was used on an endpoint that reveals contact details, spends credits, or changes the suppression list. `plan_restricted`: The workspace's plan doesn't include this feature (mobile numbers need Growth or above on live keys; test keys bypass plan gates). The error carries required_plan, current_plan, and upgrade_url.",
            "headers": {
              "X-Credits-Used": {
                "schema": {
                  "const": 0
                },
                "description": "Always 0: errors never charge."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "forbidden",
                    "message": "A read-only key was used on an endpoint that reveals contact details, spends credits, or changes the suppression list."
                  }
                }
              }
            }
          },
          "404": {
            "description": "`not_found`: No company profile for the domain, unknown person_id for this workspace, or no such job (jobs are deleted after 30 days). You are not charged. `person_not_available`: 404: no match. 451: the person is withheld (opted out, deleted through California DROP, on your suppression list, outside the US, or a sensitive context) and the error carries a reason. You are not charged either way.",
            "headers": {
              "X-Credits-Used": {
                "schema": {
                  "const": 0
                },
                "description": "Always 0: errors never charge."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "not_found",
                    "message": "No company profile for the domain, unknown person_id for this workspace, or no such job (jobs are deleted after 30 days). You are not charged."
                  }
                }
              }
            }
          },
          "409": {
            "description": "`duplicate_request`: This Idempotency-Key was already used with a different request body. `request_in_progress`: A request with this Idempotency-Key is still running. Retry in a few seconds to get its stored result.",
            "headers": {
              "X-Credits-Used": {
                "schema": {
                  "const": 0
                },
                "description": "Always 0: errors never charge."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "duplicate_request",
                    "message": "This Idempotency-Key was already used with a different request body."
                  }
                }
              }
            }
          },
          "413": {
            "description": "`payload_too_large`: Body over 4 MB (gzip /jobs bodies), more than 1,000 rows sent to /bulk or 100,000 to /jobs, or the suppression list is full.",
            "headers": {
              "X-Credits-Used": {
                "schema": {
                  "const": 0
                },
                "description": "Always 0: errors never charge."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "payload_too_large",
                    "message": "Body over 4 MB (gzip /jobs bodies), more than 1,000 rows sent to /bulk or 100,000 to /jobs, or the suppression list is full."
                  }
                }
              }
            }
          },
          "429": {
            "description": "`rate_limited`: Too many requests per second for your plan, or 2 jobs already queued or running in this workspace. Retry after the Retry-After header (seconds).",
            "headers": {
              "X-Credits-Used": {
                "schema": {
                  "const": 0
                },
                "description": "Always 0: errors never charge."
              },
              "Retry-After": {
                "schema": {
                  "type": "integer"
                },
                "description": "Seconds to wait."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "rate_limited",
                    "message": "Too many requests per second for your plan, or 2 jobs already queued or running in this workspace. Retry after the Retry-After header (seconds)."
                  }
                }
              }
            }
          },
          "451": {
            "description": "`person_not_available` with a `reason` (opted_out, drop_deleted, customer_suppressed, non_us, sensitive): this person is withheld and no data is returned. You are not charged.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "reason": "opted_out",
                    "code": "person_not_available",
                    "message": "This person has opted out of WhoLayer. No data is returned and you were not charged."
                  }
                }
              }
            }
          },
          "500": {
            "description": "`internal_error`: Our fault. Retry with the same Idempotency-Key.",
            "headers": {
              "X-Credits-Used": {
                "schema": {
                  "const": 0
                },
                "description": "Always 0: errors never charge."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "internal_error",
                    "message": "Our fault. Retry with the same Idempotency-Key."
                  }
                }
              }
            }
          },
          "503": {
            "description": "`provider_unavailable`: A data source is temporarily unavailable, or the lookup is switched off on live keys (live people search stays off until its supplier cost is verified). You are not charged. Retry later; test keys always work.",
            "headers": {
              "X-Credits-Used": {
                "schema": {
                  "const": 0
                },
                "description": "Always 0: errors never charge."
              },
              "Retry-After": {
                "schema": {
                  "type": "integer"
                },
                "description": "Seconds to wait."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "provider_unavailable",
                    "message": "A data source is temporarily unavailable, or the lookup is switched off on live keys (live people search stays off until its supplier cost is verified). You are not charged. Retry later; test keys always work."
                  }
                }
              }
            }
          }
        }
      }
    },
    "/companies/{domain}": {
      "get": {
        "operationId": "getCompany",
        "tags": [
          "Companies"
        ],
        "summary": "Get a company profile",
        "description": "Company profile from a domain: size, revenue range, industry, HQ, technologies. Costs 1 credit when a profile is returned. Allowed for read-only keys.",
        "x-credit-cost": {
          "company": 1
        },
        "parameters": [
          {
            "name": "domain",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Website domain, e.g. fieldline.com (scheme, www., and paths are stripped)."
          }
        ],
        "responses": {
          "200": {
            "description": "Company profile.",
            "headers": {
              "X-Credits-Used": {
                "description": "Credits this call charged (same as credits_used). Sent on every response; 0 when free, on errors, for test keys, and for idempotent replays.",
                "schema": {
                  "type": "number"
                }
              },
              "X-Credits-Remaining": {
                "description": "Credits left in the workspace wallet after this call (omitted when unknown).",
                "schema": {
                  "type": "number"
                }
              },
              "X-WhoLayer-Mode": {
                "description": "live or test, from the API key used.",
                "schema": {
                  "type": "string",
                  "enum": [
                    "live",
                    "test"
                  ]
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "domain": {
                      "type": "string"
                    },
                    "name": {
                      "type": "string"
                    },
                    "employees": {
                      "anyOf": [
                        {
                          "type": "integer"
                        },
                        {
                          "type": "null"
                        }
                      ]
                    },
                    "revenue_range": {
                      "anyOf": [
                        {
                          "type": "string"
                        },
                        {
                          "type": "null"
                        }
                      ]
                    },
                    "industry": {
                      "anyOf": [
                        {
                          "type": "string"
                        },
                        {
                          "type": "null"
                        }
                      ]
                    },
                    "hq": {
                      "type": "object",
                      "properties": {
                        "city": {
                          "anyOf": [
                            {
                              "type": "string"
                            },
                            {
                              "type": "null"
                            }
                          ]
                        },
                        "region": {
                          "anyOf": [
                            {
                              "type": "string"
                            },
                            {
                              "type": "null"
                            }
                          ]
                        },
                        "country": {
                          "anyOf": [
                            {
                              "type": "string"
                            },
                            {
                              "type": "null"
                            }
                          ]
                        }
                      },
                      "required": [
                        "city",
                        "region",
                        "country"
                      ]
                    },
                    "technologies": {
                      "type": "array",
                      "items": {
                        "type": "string"
                      }
                    },
                    "credits_used": {
                      "type": "number",
                      "description": "Credits charged for this call. 0 for test keys and idempotent replays."
                    },
                    "test_mode": {
                      "type": "boolean",
                      "description": "Present and true when called with a wl_test_ key (sandbox data, nothing charged)."
                    }
                  },
                  "required": [
                    "domain",
                    "name",
                    "employees",
                    "revenue_range",
                    "industry",
                    "hq",
                    "technologies",
                    "credits_used"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "`invalid_request`: A field is missing or malformed, the call could exceed the per-call credit ceiling, or a cursor is invalid. The message says which.",
            "headers": {
              "X-Credits-Used": {
                "schema": {
                  "const": 0
                },
                "description": "Always 0: errors never charge."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "invalid_request",
                    "message": "A field is missing or malformed, the call could exceed the per-call credit ceiling, or a cursor is invalid. The message says which."
                  }
                }
              }
            }
          },
          "401": {
            "description": "`unauthorized`: Missing or invalid API key.",
            "headers": {
              "X-Credits-Used": {
                "schema": {
                  "const": 0
                },
                "description": "Always 0: errors never charge."
              },
              "WWW-Authenticate": {
                "schema": {
                  "type": "string"
                },
                "description": "Bearer realm=\"wholayer\""
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "unauthorized",
                    "message": "Missing or invalid API key."
                  }
                }
              }
            }
          },
          "402": {
            "description": "`credits_exhausted`: Wallet empty (Free plan), monthly spend cap reached, this key's daily cap reached, or (agency client workspaces) the client's monthly budget is used up.",
            "headers": {
              "X-Credits-Used": {
                "schema": {
                  "const": 0
                },
                "description": "Always 0: errors never charge."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "credits_exhausted",
                    "message": "Wallet empty (Free plan), monthly spend cap reached, this key's daily cap reached, or (agency client workspaces) the client's monthly budget is used up."
                  }
                }
              }
            }
          },
          "404": {
            "description": "`not_found`: No company profile for the domain, unknown person_id for this workspace, or no such job (jobs are deleted after 30 days). You are not charged.",
            "headers": {
              "X-Credits-Used": {
                "schema": {
                  "const": 0
                },
                "description": "Always 0: errors never charge."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "not_found",
                    "message": "No company profile for the domain, unknown person_id for this workspace, or no such job (jobs are deleted after 30 days). You are not charged."
                  }
                }
              }
            }
          },
          "429": {
            "description": "`rate_limited`: Too many requests per second for your plan, or 2 jobs already queued or running in this workspace. Retry after the Retry-After header (seconds).",
            "headers": {
              "X-Credits-Used": {
                "schema": {
                  "const": 0
                },
                "description": "Always 0: errors never charge."
              },
              "Retry-After": {
                "schema": {
                  "type": "integer"
                },
                "description": "Seconds to wait."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "rate_limited",
                    "message": "Too many requests per second for your plan, or 2 jobs already queued or running in this workspace. Retry after the Retry-After header (seconds)."
                  }
                }
              }
            }
          },
          "500": {
            "description": "`internal_error`: Our fault. Retry with the same Idempotency-Key.",
            "headers": {
              "X-Credits-Used": {
                "schema": {
                  "const": 0
                },
                "description": "Always 0: errors never charge."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "internal_error",
                    "message": "Our fault. Retry with the same Idempotency-Key."
                  }
                }
              }
            }
          },
          "503": {
            "description": "`provider_unavailable`: A data source is temporarily unavailable, or the lookup is switched off on live keys (live people search stays off until its supplier cost is verified). You are not charged. Retry later; test keys always work.",
            "headers": {
              "X-Credits-Used": {
                "schema": {
                  "const": 0
                },
                "description": "Always 0: errors never charge."
              },
              "Retry-After": {
                "schema": {
                  "type": "integer"
                },
                "description": "Seconds to wait."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "provider_unavailable",
                    "message": "A data source is temporarily unavailable, or the lookup is switched off on live keys (live people search stays off until its supplier cost is verified). You are not charged. Retry later; test keys always work."
                  }
                }
              }
            }
          }
        }
      }
    },
    "/visitors": {
      "get": {
        "operationId": "listVisitors",
        "tags": [
          "Visitors"
        ],
        "summary": "List identified visitors",
        "description": "People, shoppers, and companies identified on your sites, newest first, paginated with a cursor. Free (credits were spent when each visitor was identified). Allowed for read-only keys.",
        "x-credit-cost": 0,
        "parameters": [
          {
            "name": "site_id",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Only visitors on this site (the public site ID from the Sites page)."
          },
          {
            "name": "since",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "format": "date-time"
            },
            "description": "Only visitors last seen at or after this ISO 8601 time."
          },
          {
            "name": "level",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "person",
                "company",
                "shopper"
              ]
            },
            "description": "person, company, or shopper."
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "default": 50,
              "type": "integer",
              "minimum": 1,
              "maximum": 200
            },
            "description": "Page size."
          },
          {
            "name": "cursor",
            "in": "query",
            "required": false,
            "schema": {
              "type": [
                "string",
                "null"
              ]
            },
            "description": "next_cursor from the previous page."
          }
        ],
        "responses": {
          "200": {
            "description": "A page of visitors.",
            "headers": {
              "X-Credits-Used": {
                "description": "Credits this call charged (same as credits_used). Sent on every response; 0 when free, on errors, for test keys, and for idempotent replays.",
                "schema": {
                  "type": "number"
                }
              },
              "X-Credits-Remaining": {
                "description": "Credits left in the workspace wallet after this call (omitted when unknown).",
                "schema": {
                  "type": "number"
                }
              },
              "X-WhoLayer-Mode": {
                "description": "live or test, from the API key used.",
                "schema": {
                  "type": "string",
                  "enum": [
                    "live",
                    "test"
                  ]
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "results": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "id": {
                            "type": "string",
                            "description": "vis_<uuid>"
                          },
                          "level": {
                            "type": "string",
                            "enum": [
                              "person",
                              "company",
                              "shopper"
                            ]
                          },
                          "person_id": {
                            "anyOf": [
                              {
                                "type": "string"
                              },
                              {
                                "type": "null"
                              }
                            ],
                            "description": "per_<uuid> for person and shopper rows; null for companies."
                          },
                          "site_id": {
                            "anyOf": [
                              {
                                "type": "string"
                              },
                              {
                                "type": "null"
                              }
                            ]
                          },
                          "company_domain": {
                            "anyOf": [
                              {
                                "type": "string"
                              },
                              {
                                "type": "null"
                              }
                            ]
                          },
                          "first_seen": {
                            "type": "string",
                            "format": "date-time"
                          },
                          "last_seen": {
                            "type": "string",
                            "format": "date-time"
                          },
                          "pages": {
                            "type": "array",
                            "items": {
                              "type": "object",
                              "properties": {
                                "url": {
                                  "type": "string"
                                },
                                "seconds": {
                                  "type": "null"
                                }
                              },
                              "required": [
                                "url",
                                "seconds"
                              ]
                            }
                          },
                          "visits_this_month": {
                            "type": "integer"
                          },
                          "icp_score": {
                            "anyOf": [
                              {
                                "type": "integer"
                              },
                              {
                                "type": "null"
                              }
                            ],
                            "description": "ICP fit from 0 to 100 against the workspace's ideal customer description (deterministic rules, set in the dashboard). Null when no ideal customer is set, for shoppers, or not yet scored."
                          },
                          "icp_reason": {
                            "anyOf": [
                              {
                                "type": "string"
                              },
                              {
                                "type": "null"
                              }
                            ],
                            "description": "One-line plain-English reason for icp_score, e.g. \"VP Sales at a 210-person SaaS company; viewed pricing twice\"."
                          }
                        },
                        "required": [
                          "id",
                          "level",
                          "person_id",
                          "site_id",
                          "company_domain",
                          "first_seen",
                          "last_seen",
                          "pages",
                          "visits_this_month",
                          "icp_score",
                          "icp_reason"
                        ]
                      }
                    },
                    "next_cursor": {
                      "anyOf": [
                        {
                          "type": "string"
                        },
                        {
                          "type": "null"
                        }
                      ]
                    }
                  },
                  "required": [
                    "results",
                    "next_cursor"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "`invalid_request`: A field is missing or malformed, the call could exceed the per-call credit ceiling, or a cursor is invalid. The message says which.",
            "headers": {
              "X-Credits-Used": {
                "schema": {
                  "const": 0
                },
                "description": "Always 0: errors never charge."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "invalid_request",
                    "message": "A field is missing or malformed, the call could exceed the per-call credit ceiling, or a cursor is invalid. The message says which."
                  }
                }
              }
            }
          },
          "401": {
            "description": "`unauthorized`: Missing or invalid API key.",
            "headers": {
              "X-Credits-Used": {
                "schema": {
                  "const": 0
                },
                "description": "Always 0: errors never charge."
              },
              "WWW-Authenticate": {
                "schema": {
                  "type": "string"
                },
                "description": "Bearer realm=\"wholayer\""
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "unauthorized",
                    "message": "Missing or invalid API key."
                  }
                }
              }
            }
          },
          "429": {
            "description": "`rate_limited`: Too many requests per second for your plan, or 2 jobs already queued or running in this workspace. Retry after the Retry-After header (seconds).",
            "headers": {
              "X-Credits-Used": {
                "schema": {
                  "const": 0
                },
                "description": "Always 0: errors never charge."
              },
              "Retry-After": {
                "schema": {
                  "type": "integer"
                },
                "description": "Seconds to wait."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "rate_limited",
                    "message": "Too many requests per second for your plan, or 2 jobs already queued or running in this workspace. Retry after the Retry-After header (seconds)."
                  }
                }
              }
            }
          },
          "500": {
            "description": "`internal_error`: Our fault. Retry with the same Idempotency-Key.",
            "headers": {
              "X-Credits-Used": {
                "schema": {
                  "const": 0
                },
                "description": "Always 0: errors never charge."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "internal_error",
                    "message": "Our fault. Retry with the same Idempotency-Key."
                  }
                }
              }
            }
          }
        }
      }
    },
    "/balance": {
      "get": {
        "operationId": "getBalance",
        "tags": [
          "Account"
        ],
        "summary": "Get credit balance",
        "description": "Credits remaining, plan, monthly spend cap, and this key's daily cap. Free.",
        "x-credit-cost": 0,
        "responses": {
          "200": {
            "description": "Balance.",
            "headers": {
              "X-Credits-Used": {
                "description": "Credits this call charged (same as credits_used). Sent on every response; 0 when free, on errors, for test keys, and for idempotent replays.",
                "schema": {
                  "type": "number"
                }
              },
              "X-Credits-Remaining": {
                "description": "Credits left in the workspace wallet after this call (omitted when unknown).",
                "schema": {
                  "type": "number"
                }
              },
              "X-WhoLayer-Mode": {
                "description": "live or test, from the API key used.",
                "schema": {
                  "type": "string",
                  "enum": [
                    "live",
                    "test"
                  ]
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "credits_remaining": {
                      "type": "number"
                    },
                    "plan": {
                      "anyOf": [
                        {
                          "type": "string",
                          "enum": [
                            "free",
                            "starter",
                            "growth",
                            "scale",
                            "platform"
                          ]
                        },
                        {
                          "type": "null"
                        }
                      ]
                    },
                    "monthly_spend_cap": {
                      "anyOf": [
                        {
                          "type": "number"
                        },
                        {
                          "type": "null"
                        }
                      ]
                    },
                    "key_daily_cap": {
                      "anyOf": [
                        {
                          "type": "number"
                        },
                        {
                          "type": "null"
                        }
                      ]
                    },
                    "key_mode": {
                      "type": "string",
                      "enum": [
                        "live",
                        "test"
                      ]
                    },
                    "client_budget": {
                      "type": "object",
                      "properties": {
                        "monthly_budget": {
                          "type": "number"
                        },
                        "spent_this_month": {
                          "type": "number"
                        }
                      },
                      "required": [
                        "monthly_budget",
                        "spent_this_month"
                      ],
                      "description": "Only on agency client workspaces, which spend from their agency's wallet: credits_remaining is then what's left of this month's budget."
                    }
                  },
                  "required": [
                    "credits_remaining",
                    "plan",
                    "monthly_spend_cap",
                    "key_daily_cap",
                    "key_mode"
                  ]
                }
              }
            }
          },
          "401": {
            "description": "`unauthorized`: Missing or invalid API key.",
            "headers": {
              "X-Credits-Used": {
                "schema": {
                  "const": 0
                },
                "description": "Always 0: errors never charge."
              },
              "WWW-Authenticate": {
                "schema": {
                  "type": "string"
                },
                "description": "Bearer realm=\"wholayer\""
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "unauthorized",
                    "message": "Missing or invalid API key."
                  }
                }
              }
            }
          },
          "429": {
            "description": "`rate_limited`: Too many requests per second for your plan, or 2 jobs already queued or running in this workspace. Retry after the Retry-After header (seconds).",
            "headers": {
              "X-Credits-Used": {
                "schema": {
                  "const": 0
                },
                "description": "Always 0: errors never charge."
              },
              "Retry-After": {
                "schema": {
                  "type": "integer"
                },
                "description": "Seconds to wait."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "rate_limited",
                    "message": "Too many requests per second for your plan, or 2 jobs already queued or running in this workspace. Retry after the Retry-After header (seconds)."
                  }
                }
              }
            }
          },
          "500": {
            "description": "`internal_error`: Our fault. Retry with the same Idempotency-Key.",
            "headers": {
              "X-Credits-Used": {
                "schema": {
                  "const": 0
                },
                "description": "Always 0: errors never charge."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "internal_error",
                    "message": "Our fault. Retry with the same Idempotency-Key."
                  }
                }
              }
            }
          }
        }
      }
    },
    "/estimate": {
      "post": {
        "operationId": "estimateCost",
        "tags": [
          "Billing"
        ],
        "summary": "Quote the cost of a call",
        "description": "The most credits an operation could spend, with a line-by-line breakdown, before you run it. We only charge on hits, so the real cost is the same or lower. Free; never calls a data supplier; works with read-only and test keys.",
        "x-credit-cost": 0,
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "operation": {
                    "type": "string",
                    "enum": [
                      "enrich",
                      "phones",
                      "verify",
                      "company",
                      "search",
                      "bulk"
                    ]
                  },
                  "reveal": {
                    "description": "enrich and bulk: what you'll ask for. Default [\"work_email\"].",
                    "minItems": 1,
                    "type": "array",
                    "items": {
                      "type": "string",
                      "enum": [
                        "work_email",
                        "personal_email",
                        "mobile"
                      ]
                    }
                  },
                  "email_only": {
                    "description": "enrich and bulk: true if rows will carry only an email (adds the reverse lookup). Default false.",
                    "type": "boolean"
                  },
                  "rows": {
                    "description": "bulk, verify, phones, company: how many rows, addresses, or lookups. Default 1. Up to 100,000 to quote an async job (/jobs).",
                    "type": "integer",
                    "minimum": 1,
                    "maximum": 100000
                  },
                  "limit": {
                    "description": "search: results per call. Default 25.",
                    "type": "integer",
                    "minimum": 1,
                    "maximum": 100
                  }
                },
                "required": [
                  "operation"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Quote.",
            "headers": {
              "X-Credits-Used": {
                "description": "Credits this call charged (same as credits_used). Sent on every response; 0 when free, on errors, for test keys, and for idempotent replays.",
                "schema": {
                  "type": "number"
                }
              },
              "X-Credits-Remaining": {
                "description": "Credits left in the workspace wallet after this call (omitted when unknown).",
                "schema": {
                  "type": "number"
                }
              },
              "X-WhoLayer-Mode": {
                "description": "live or test, from the API key used.",
                "schema": {
                  "type": "string",
                  "enum": [
                    "live",
                    "test"
                  ]
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "operation": {
                      "type": "string"
                    },
                    "max_credits": {
                      "type": "number"
                    },
                    "breakdown": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "item": {
                            "type": "string"
                          },
                          "credits_each": {
                            "type": "number"
                          },
                          "quantity": {
                            "type": "integer"
                          },
                          "credits": {
                            "type": "number"
                          }
                        },
                        "required": [
                          "item",
                          "credits_each",
                          "quantity",
                          "credits"
                        ]
                      }
                    },
                    "within_default_ceiling": {
                      "type": "boolean",
                      "description": "True when max_credits is within the default 250-credit per-call ceiling."
                    },
                    "note": {
                      "type": "string"
                    },
                    "live_available": {
                      "type": "boolean",
                      "description": "Present and false for search while live people search is switched off."
                    },
                    "live_note": {
                      "type": "string"
                    }
                  },
                  "required": [
                    "operation",
                    "max_credits",
                    "breakdown",
                    "within_default_ceiling"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "`invalid_request`: A field is missing or malformed, the call could exceed the per-call credit ceiling, or a cursor is invalid. The message says which.",
            "headers": {
              "X-Credits-Used": {
                "schema": {
                  "const": 0
                },
                "description": "Always 0: errors never charge."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "invalid_request",
                    "message": "A field is missing or malformed, the call could exceed the per-call credit ceiling, or a cursor is invalid. The message says which."
                  }
                }
              }
            }
          },
          "401": {
            "description": "`unauthorized`: Missing or invalid API key.",
            "headers": {
              "X-Credits-Used": {
                "schema": {
                  "const": 0
                },
                "description": "Always 0: errors never charge."
              },
              "WWW-Authenticate": {
                "schema": {
                  "type": "string"
                },
                "description": "Bearer realm=\"wholayer\""
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "unauthorized",
                    "message": "Missing or invalid API key."
                  }
                }
              }
            }
          },
          "429": {
            "description": "`rate_limited`: Too many requests per second for your plan, or 2 jobs already queued or running in this workspace. Retry after the Retry-After header (seconds).",
            "headers": {
              "X-Credits-Used": {
                "schema": {
                  "const": 0
                },
                "description": "Always 0: errors never charge."
              },
              "Retry-After": {
                "schema": {
                  "type": "integer"
                },
                "description": "Seconds to wait."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "rate_limited",
                    "message": "Too many requests per second for your plan, or 2 jobs already queued or running in this workspace. Retry after the Retry-After header (seconds)."
                  }
                }
              }
            }
          },
          "500": {
            "description": "`internal_error`: Our fault. Retry with the same Idempotency-Key.",
            "headers": {
              "X-Credits-Used": {
                "schema": {
                  "const": 0
                },
                "description": "Always 0: errors never charge."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "internal_error",
                    "message": "Our fault. Retry with the same Idempotency-Key."
                  }
                }
              }
            }
          }
        }
      }
    },
    "/report": {
      "post": {
        "operationId": "reportBadData",
        "tags": [
          "Billing"
        ],
        "summary": "Report a bad result",
        "description": "Report a wrong person, a bounced email, or outdated data. Send result_id (the per_… id from /people/enrich or /phones/lookup) or identity_id (a visitor's person_id from /visitors). Credits charged for a reported result are refunded if it was charged in the last 30 days, once per result. Refunds in any rolling 30 days are capped at 10% of the credits the workspace spent in that window; reports above the cap wait for review by our team and nothing is refunded automatically. The result is flagged and never returned or delivered again. Reporting the same result twice returns the first report. Needs a key that isn't read-only.",
        "x-credit-cost": 0,
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "result_id": {
                    "description": "per_… id from /people/enrich or /phones/lookup.",
                    "anyOf": [
                      {
                        "type": "string",
                        "maxLength": 80
                      },
                      {
                        "type": "null"
                      }
                    ]
                  },
                  "identity_id": {
                    "description": "A visitor's person_id from /visitors (per_<uuid>), or the identity id.",
                    "anyOf": [
                      {
                        "type": "string",
                        "maxLength": 80
                      },
                      {
                        "type": "null"
                      }
                    ]
                  },
                  "reason": {
                    "type": "string",
                    "enum": [
                      "wrong_person",
                      "bounced",
                      "outdated"
                    ]
                  },
                  "note": {
                    "anyOf": [
                      {
                        "type": "string",
                        "maxLength": 500
                      },
                      {
                        "type": "null"
                      }
                    ]
                  }
                },
                "required": [
                  "reason"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Report recorded.",
            "headers": {
              "X-Credits-Used": {
                "description": "Credits this call charged (same as credits_used). Sent on every response; 0 when free, on errors, for test keys, and for idempotent replays.",
                "schema": {
                  "type": "number"
                }
              },
              "X-Credits-Remaining": {
                "description": "Credits left in the workspace wallet after this call (omitted when unknown).",
                "schema": {
                  "type": "number"
                }
              },
              "X-WhoLayer-Mode": {
                "description": "live or test, from the API key used.",
                "schema": {
                  "type": "string",
                  "enum": [
                    "live",
                    "test"
                  ]
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "report_id": {
                      "type": "integer"
                    },
                    "status": {
                      "type": "string",
                      "enum": [
                        "refunded",
                        "pending_review",
                        "not_charged",
                        "denied"
                      ],
                      "description": "refunded: credits are back in the wallet. pending_review: over the refund cap, our team reviews it. not_charged: nothing was charged in the window. denied: reviewed and declined."
                    },
                    "credits_charged": {
                      "type": "number",
                      "description": "Credits charged for this result in the refund window."
                    },
                    "credits_refunded": {
                      "type": "number"
                    },
                    "already_reported": {
                      "type": "boolean"
                    },
                    "message": {
                      "type": "string"
                    },
                    "credits_used": {
                      "type": "number",
                      "description": "Credits charged for this call. 0 for test keys and idempotent replays."
                    }
                  },
                  "required": [
                    "report_id",
                    "status",
                    "credits_charged",
                    "credits_refunded",
                    "already_reported",
                    "message",
                    "credits_used"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "`invalid_request`: A field is missing or malformed, the call could exceed the per-call credit ceiling, or a cursor is invalid. The message says which.",
            "headers": {
              "X-Credits-Used": {
                "schema": {
                  "const": 0
                },
                "description": "Always 0: errors never charge."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "invalid_request",
                    "message": "A field is missing or malformed, the call could exceed the per-call credit ceiling, or a cursor is invalid. The message says which."
                  }
                }
              }
            }
          },
          "401": {
            "description": "`unauthorized`: Missing or invalid API key.",
            "headers": {
              "X-Credits-Used": {
                "schema": {
                  "const": 0
                },
                "description": "Always 0: errors never charge."
              },
              "WWW-Authenticate": {
                "schema": {
                  "type": "string"
                },
                "description": "Bearer realm=\"wholayer\""
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "unauthorized",
                    "message": "Missing or invalid API key."
                  }
                }
              }
            }
          },
          "403": {
            "description": "`forbidden`: A read-only key was used on an endpoint that reveals contact details, spends credits, or changes the suppression list.",
            "headers": {
              "X-Credits-Used": {
                "schema": {
                  "const": 0
                },
                "description": "Always 0: errors never charge."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "forbidden",
                    "message": "A read-only key was used on an endpoint that reveals contact details, spends credits, or changes the suppression list."
                  }
                }
              }
            }
          },
          "404": {
            "description": "`not_found`: No company profile for the domain, unknown person_id for this workspace, or no such job (jobs are deleted after 30 days). You are not charged.",
            "headers": {
              "X-Credits-Used": {
                "schema": {
                  "const": 0
                },
                "description": "Always 0: errors never charge."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "not_found",
                    "message": "No company profile for the domain, unknown person_id for this workspace, or no such job (jobs are deleted after 30 days). You are not charged."
                  }
                }
              }
            }
          },
          "409": {
            "description": "`duplicate_request`: This Idempotency-Key was already used with a different request body. `request_in_progress`: A request with this Idempotency-Key is still running. Retry in a few seconds to get its stored result.",
            "headers": {
              "X-Credits-Used": {
                "schema": {
                  "const": 0
                },
                "description": "Always 0: errors never charge."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "duplicate_request",
                    "message": "This Idempotency-Key was already used with a different request body."
                  }
                }
              }
            }
          },
          "429": {
            "description": "`rate_limited`: Too many requests per second for your plan, or 2 jobs already queued or running in this workspace. Retry after the Retry-After header (seconds).",
            "headers": {
              "X-Credits-Used": {
                "schema": {
                  "const": 0
                },
                "description": "Always 0: errors never charge."
              },
              "Retry-After": {
                "schema": {
                  "type": "integer"
                },
                "description": "Seconds to wait."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "rate_limited",
                    "message": "Too many requests per second for your plan, or 2 jobs already queued or running in this workspace. Retry after the Retry-After header (seconds)."
                  }
                }
              }
            }
          },
          "500": {
            "description": "`internal_error`: Our fault. Retry with the same Idempotency-Key.",
            "headers": {
              "X-Credits-Used": {
                "schema": {
                  "const": 0
                },
                "description": "Always 0: errors never charge."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "internal_error",
                    "message": "Our fault. Retry with the same Idempotency-Key."
                  }
                }
              }
            }
          }
        },
        "x-reasons": [
          "wrong_person",
          "bounced",
          "outdated"
        ]
      }
    },
    "/policy": {
      "get": {
        "operationId": "getPolicy",
        "tags": [
          "Compliance"
        ],
        "summary": "Get the compliance policy",
        "description": "Machine-readable rules WhoLayer enforces: US-only person-level identification, consent outside the US, Global Privacy Control, blocked sensitive-page categories, suppression sources and reason codes, and retention periods. Free; works with read-only and test keys.",
        "x-credit-cost": 0,
        "responses": {
          "200": {
            "description": "The current policy. The example is the live policy when this spec was built.",
            "headers": {
              "X-Credits-Used": {
                "description": "Credits this call charged (same as credits_used). Sent on every response; 0 when free, on errors, for test keys, and for idempotent replays.",
                "schema": {
                  "type": "number"
                }
              },
              "X-Credits-Remaining": {
                "description": "Credits left in the workspace wallet after this call (omitted when unknown).",
                "schema": {
                  "type": "number"
                }
              },
              "X-WhoLayer-Mode": {
                "description": "live or test, from the API key used.",
                "schema": {
                  "type": "string",
                  "enum": [
                    "live",
                    "test"
                  ]
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "version": {
                      "type": "string"
                    },
                    "docs_url": {
                      "type": "string"
                    }
                  },
                  "required": [
                    "version"
                  ],
                  "additionalProperties": true,
                  "example": {
                    "version": "2026-11-04",
                    "docs_url": "https://wholayer.com/docs/api#policy",
                    "visitor_identification": {
                      "person_level_countries": [
                        "US"
                      ],
                      "person_level_outside_us": "never, even with consent",
                      "company_level_outside_us": "only after the visitor's consent is recorded (wholayer(\"consent\", true))",
                      "require_consent_everywhere_site_setting": "when a site is set to wait for consent everywhere, nothing is stored or matched for any visitor until consent is recorded",
                      "global_privacy_control": {
                        "honored": true,
                        "effect": "requests sent with Sec-GPC: 1 are dropped: nothing is stored or matched"
                      },
                      "bots": "crawler, headless-browser, and HTTP-library traffic is never stored or matched",
                      "url_redaction": "query values that look like emails, phone numbers, or tokens are replaced with \"redacted\" before storage"
                    },
                    "sensitive_pages": {
                      "person_level": "blocked",
                      "company_level": "allowed",
                      "match": "a keyword below appears as a whole path segment or word in the page path (separated by / _ . -), case-insensitive",
                      "categories": [
                        {
                          "id": "health",
                          "label": "Health and medical conditions",
                          "keywords": [
                            "health",
                            "medical",
                            "clinic",
                            "pharmacy",
                            "rx",
                            "prescription",
                            "diagnos",
                            "symptom",
                            "therapy",
                            "mental",
                            "counsel",
                            "hiv",
                            "std",
                            "sti",
                            "cancer",
                            "oncology",
                            "diabetes"
                          ]
                        },
                        {
                          "id": "reproductive_care",
                          "label": "Reproductive care",
                          "keywords": [
                            "abortion",
                            "pregnan",
                            "fertility",
                            "ivf",
                            "contracept"
                          ]
                        },
                        {
                          "id": "addiction",
                          "label": "Addiction and recovery",
                          "keywords": [
                            "rehab",
                            "addiction",
                            "recovery",
                            "alcohol",
                            "opioid"
                          ]
                        },
                        {
                          "id": "sexual_orientation_gender_identity",
                          "label": "Sexual orientation and gender identity",
                          "keywords": [
                            "lgbt",
                            "gay",
                            "lesbian",
                            "trans",
                            "transgender",
                            "queer"
                          ]
                        },
                        {
                          "id": "religion",
                          "label": "Religion",
                          "keywords": [
                            "religio",
                            "church",
                            "mosque",
                            "synagogue"
                          ]
                        },
                        {
                          "id": "immigration",
                          "label": "Immigration status",
                          "keywords": [
                            "immigra",
                            "visa",
                            "asylum"
                          ]
                        },
                        {
                          "id": "financial_hardship",
                          "label": "Financial hardship",
                          "keywords": [
                            "bankrupt",
                            "debt-relief",
                            "payday"
                          ]
                        },
                        {
                          "id": "children",
                          "label": "Children",
                          "keywords": [
                            "kid",
                            "kids",
                            "children",
                            "teen",
                            "minor",
                            "school"
                          ]
                        }
                      ]
                    },
                    "suppression": {
                      "checked_on": [
                        "website visitor identification (tag)",
                        "dashboard overview, people list, person page, CSV export, and Companies view",
                        "destination deliveries (Slack, webhooks, HubSpot, Klaviyo), visitor.high_intent signals, and their retries",
                        "digest emails",
                        "REST API: /v1/people/enrich, /v1/people/search, /v1/phones/lookup, /v1/bulk, /v1/visitors, and idempotent replays",
                        "async jobs (/v1/jobs): every row when it is processed, and again every time results are read",
                        "MCP server: every tool that returns a person"
                      ],
                      "identifiers": [
                        "email (sha256 of trimmed, lowercased address)",
                        "LinkedIn URL",
                        "mobile number (E.164, and its last 10 digits as California DROP lists use)",
                        "full name + company domain"
                      ],
                      "sources": [
                        {
                          "source": "privacy_request",
                          "scope": "global",
                          "reason": "opted_out",
                          "description": "opt-out, deletion, and limit requests made through wholayer.com/privacy-request or by email; added immediately, before verification"
                        },
                        {
                          "source": "ca_drop",
                          "scope": "global",
                          "reason": "drop_deleted",
                          "description": "California Delete Request and Opt-out Platform (DROP) email and phone deletion lists, including requests that matched nothing when imported"
                        },
                        {
                          "source": "customer_list",
                          "scope": "workspace",
                          "reason": "customer_suppressed",
                          "description": "your own list: Settings, Suppression list, POST /v1/suppression, or the add_to_suppression MCP tool (email hashes)"
                        }
                      ],
                      "effect": "the person is withheld and never charged; the Data API answers HTTP 451 with error.code person_not_available and error.reason",
                      "reasons": [
                        "opted_out",
                        "drop_deleted",
                        "customer_suppressed"
                      ],
                      "stored_identities": "people on the global list are purged from every workspace (DROP lists when imported, everyone else by a daily job); workspaces with a webhook destination that had received them get a suppression.added event",
                      "webhook_event": "suppression.added"
                    },
                    "retention": {
                      "page_events_months": 13,
                      "delivery_log_months": 13,
                      "revealed_api_people_days": 90,
                      "idempotency_cache_days": 30,
                      "async_job_results_days": 30,
                      "high_intent_signals_months": 13,
                      "coverage_counters_months": 13,
                      "digest_send_records_months": 13,
                      "privacy_request_records_min_months": 24
                    },
                    "california_drop": {
                      "lists": [
                        "Email",
                        "Phone"
                      ],
                      "access_interval_days": 45,
                      "access_interval_days_from_2027": 30
                    },
                    "customer_obligations": {
                      "phone_numbers": "Phone numbers are returned for lookup only. Under the TCPA, don't use them for calls or texts (including autodialed, prerecorded, or marketing calls and texts) without the consent the law requires.",
                      "acceptable_use_url": "https://wholayer.com/legal/acceptable-use"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "`unauthorized`: Missing or invalid API key.",
            "headers": {
              "X-Credits-Used": {
                "schema": {
                  "const": 0
                },
                "description": "Always 0: errors never charge."
              },
              "WWW-Authenticate": {
                "schema": {
                  "type": "string"
                },
                "description": "Bearer realm=\"wholayer\""
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "unauthorized",
                    "message": "Missing or invalid API key."
                  }
                }
              }
            }
          },
          "429": {
            "description": "`rate_limited`: Too many requests per second for your plan, or 2 jobs already queued or running in this workspace. Retry after the Retry-After header (seconds).",
            "headers": {
              "X-Credits-Used": {
                "schema": {
                  "const": 0
                },
                "description": "Always 0: errors never charge."
              },
              "Retry-After": {
                "schema": {
                  "type": "integer"
                },
                "description": "Seconds to wait."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "rate_limited",
                    "message": "Too many requests per second for your plan, or 2 jobs already queued or running in this workspace. Retry after the Retry-After header (seconds)."
                  }
                }
              }
            }
          },
          "500": {
            "description": "`internal_error`: Our fault. Retry with the same Idempotency-Key.",
            "headers": {
              "X-Credits-Used": {
                "schema": {
                  "const": 0
                },
                "description": "Always 0: errors never charge."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "internal_error",
                    "message": "Our fault. Retry with the same Idempotency-Key."
                  }
                }
              }
            }
          }
        }
      }
    },
    "/suppression": {
      "post": {
        "operationId": "addSuppression",
        "tags": [
          "Compliance"
        ],
        "summary": "Add people to your suppression list",
        "description": "Adds emails or SHA-256 email hashes (up to 10,000 entries per request) to this workspace's suppression list. Listed people are never returned, delivered, or charged for in this workspace; calls about them return 451 with reason customer_suppressed. Emails are trimmed, lowercased, and hashed on arrival; only hashes are stored. Entries can be removed only in the dashboard. Free; needs a key that isn't read-only.",
        "x-credit-cost": 0,
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "emails": {
                    "maxItems": 10000,
                    "type": "array",
                    "items": {
                      "type": "string",
                      "maxLength": 320
                    }
                  },
                  "email_sha256": {
                    "maxItems": 10000,
                    "type": "array",
                    "items": {
                      "type": "string",
                      "maxLength": 64
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Result.",
            "headers": {
              "X-Credits-Used": {
                "description": "Credits this call charged (same as credits_used). Sent on every response; 0 when free, on errors, for test keys, and for idempotent replays.",
                "schema": {
                  "type": "number"
                }
              },
              "X-Credits-Remaining": {
                "description": "Credits left in the workspace wallet after this call (omitted when unknown).",
                "schema": {
                  "type": "number"
                }
              },
              "X-WhoLayer-Mode": {
                "description": "live or test, from the API key used.",
                "schema": {
                  "type": "string",
                  "enum": [
                    "live",
                    "test"
                  ]
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "added": {
                      "type": "integer",
                      "description": "New hashes added."
                    },
                    "already_listed": {
                      "type": "integer",
                      "description": "Valid entries that were already on the list."
                    },
                    "invalid": {
                      "type": "integer",
                      "description": "Entries that were neither an email nor a 64-character hex SHA-256."
                    },
                    "total": {
                      "type": "integer",
                      "description": "Entries on the list after this call."
                    }
                  },
                  "required": [
                    "added",
                    "already_listed",
                    "invalid",
                    "total"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "`invalid_request`: A field is missing or malformed, the call could exceed the per-call credit ceiling, or a cursor is invalid. The message says which.",
            "headers": {
              "X-Credits-Used": {
                "schema": {
                  "const": 0
                },
                "description": "Always 0: errors never charge."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "invalid_request",
                    "message": "A field is missing or malformed, the call could exceed the per-call credit ceiling, or a cursor is invalid. The message says which."
                  }
                }
              }
            }
          },
          "401": {
            "description": "`unauthorized`: Missing or invalid API key.",
            "headers": {
              "X-Credits-Used": {
                "schema": {
                  "const": 0
                },
                "description": "Always 0: errors never charge."
              },
              "WWW-Authenticate": {
                "schema": {
                  "type": "string"
                },
                "description": "Bearer realm=\"wholayer\""
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "unauthorized",
                    "message": "Missing or invalid API key."
                  }
                }
              }
            }
          },
          "403": {
            "description": "`forbidden`: A read-only key was used on an endpoint that reveals contact details, spends credits, or changes the suppression list.",
            "headers": {
              "X-Credits-Used": {
                "schema": {
                  "const": 0
                },
                "description": "Always 0: errors never charge."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "forbidden",
                    "message": "A read-only key was used on an endpoint that reveals contact details, spends credits, or changes the suppression list."
                  }
                }
              }
            }
          },
          "409": {
            "description": "`duplicate_request`: This Idempotency-Key was already used with a different request body. `request_in_progress`: A request with this Idempotency-Key is still running. Retry in a few seconds to get its stored result.",
            "headers": {
              "X-Credits-Used": {
                "schema": {
                  "const": 0
                },
                "description": "Always 0: errors never charge."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "duplicate_request",
                    "message": "This Idempotency-Key was already used with a different request body."
                  }
                }
              }
            }
          },
          "413": {
            "description": "`payload_too_large`: Body over 4 MB (gzip /jobs bodies), more than 1,000 rows sent to /bulk or 100,000 to /jobs, or the suppression list is full.",
            "headers": {
              "X-Credits-Used": {
                "schema": {
                  "const": 0
                },
                "description": "Always 0: errors never charge."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "payload_too_large",
                    "message": "Body over 4 MB (gzip /jobs bodies), more than 1,000 rows sent to /bulk or 100,000 to /jobs, or the suppression list is full."
                  }
                }
              }
            }
          },
          "429": {
            "description": "`rate_limited`: Too many requests per second for your plan, or 2 jobs already queued or running in this workspace. Retry after the Retry-After header (seconds).",
            "headers": {
              "X-Credits-Used": {
                "schema": {
                  "const": 0
                },
                "description": "Always 0: errors never charge."
              },
              "Retry-After": {
                "schema": {
                  "type": "integer"
                },
                "description": "Seconds to wait."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "rate_limited",
                    "message": "Too many requests per second for your plan, or 2 jobs already queued or running in this workspace. Retry after the Retry-After header (seconds)."
                  }
                }
              }
            }
          },
          "500": {
            "description": "`internal_error`: Our fault. Retry with the same Idempotency-Key.",
            "headers": {
              "X-Credits-Used": {
                "schema": {
                  "const": 0
                },
                "description": "Always 0: errors never charge."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "internal_error",
                    "message": "Our fault. Retry with the same Idempotency-Key."
                  }
                }
              }
            }
          }
        }
      }
    },
    "/suppression/count": {
      "get": {
        "operationId": "countSuppression",
        "tags": [
          "Compliance"
        ],
        "summary": "Count your suppression list",
        "description": "Number of entries on this workspace's suppression list. Free; works with read-only keys.",
        "x-credit-cost": 0,
        "responses": {
          "200": {
            "description": "Count.",
            "headers": {
              "X-Credits-Used": {
                "description": "Credits this call charged (same as credits_used). Sent on every response; 0 when free, on errors, for test keys, and for idempotent replays.",
                "schema": {
                  "type": "number"
                }
              },
              "X-Credits-Remaining": {
                "description": "Credits left in the workspace wallet after this call (omitted when unknown).",
                "schema": {
                  "type": "number"
                }
              },
              "X-WhoLayer-Mode": {
                "description": "live or test, from the API key used.",
                "schema": {
                  "type": "string",
                  "enum": [
                    "live",
                    "test"
                  ]
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "count": {
                      "type": "integer"
                    }
                  },
                  "required": [
                    "count"
                  ]
                }
              }
            }
          },
          "401": {
            "description": "`unauthorized`: Missing or invalid API key.",
            "headers": {
              "X-Credits-Used": {
                "schema": {
                  "const": 0
                },
                "description": "Always 0: errors never charge."
              },
              "WWW-Authenticate": {
                "schema": {
                  "type": "string"
                },
                "description": "Bearer realm=\"wholayer\""
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "unauthorized",
                    "message": "Missing or invalid API key."
                  }
                }
              }
            }
          },
          "429": {
            "description": "`rate_limited`: Too many requests per second for your plan, or 2 jobs already queued or running in this workspace. Retry after the Retry-After header (seconds).",
            "headers": {
              "X-Credits-Used": {
                "schema": {
                  "const": 0
                },
                "description": "Always 0: errors never charge."
              },
              "Retry-After": {
                "schema": {
                  "type": "integer"
                },
                "description": "Seconds to wait."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "rate_limited",
                    "message": "Too many requests per second for your plan, or 2 jobs already queued or running in this workspace. Retry after the Retry-After header (seconds)."
                  }
                }
              }
            }
          },
          "500": {
            "description": "`internal_error`: Our fault. Retry with the same Idempotency-Key.",
            "headers": {
              "X-Credits-Used": {
                "schema": {
                  "const": 0
                },
                "description": "Always 0: errors never charge."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "internal_error",
                    "message": "Our fault. Retry with the same Idempotency-Key."
                  }
                }
              }
            }
          }
        }
      }
    },
    "/bulk": {
      "post": {
        "operationId": "bulk",
        "tags": [
          "People",
          "Emails"
        ],
        "summary": "Bulk enrich or verify",
        "description": "Up to 1,000 rows in one synchronous request. operation \"enrich\": each row is a /people/enrich body and is billed like a single call; processing stops at the first credits_exhausted row, and rows not reached within the time budget return not_processed (resend them with the same Idempotency-Key and charged rows are not charged again). Rows that reveal mobile need the Growth plan or above on live keys (403 plan_restricted for the whole request). Withheld rows return { \"ok\": false, \"error\": { \"code\": \"person_not_available\", \"reason\": … } }. operation \"verify\": rows are { \"email\": \"…\" } and the response matches /emails/verify. The 250-credit per-call ceiling applies to the whole request; raise it with X-Max-Credits.",
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          },
          {
            "$ref": "#/components/parameters/MaxCredits"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "operation": {
                    "type": "string",
                    "enum": [
                      "enrich",
                      "verify"
                    ]
                  },
                  "rows": {
                    "minItems": 1,
                    "type": "array",
                    "items": {
                      "type": "object",
                      "propertyNames": {
                        "type": "string"
                      },
                      "additionalProperties": {}
                    }
                  }
                },
                "required": [
                  "operation",
                  "rows"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Per-row results (enrich), or the /emails/verify response shape (verify).",
            "headers": {
              "X-Credits-Used": {
                "description": "Credits this call charged (same as credits_used). Sent on every response; 0 when free, on errors, for test keys, and for idempotent replays.",
                "schema": {
                  "type": "number"
                }
              },
              "X-Credits-Remaining": {
                "description": "Credits left in the workspace wallet after this call (omitted when unknown).",
                "schema": {
                  "type": "number"
                }
              },
              "X-WhoLayer-Mode": {
                "description": "live or test, from the API key used.",
                "schema": {
                  "type": "string",
                  "enum": [
                    "live",
                    "test"
                  ]
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "anyOf": [
                    {
                      "type": "object",
                      "properties": {
                        "results": {
                          "type": "array",
                          "items": {
                            "oneOf": [
                              {
                                "allOf": [
                                  {
                                    "type": "object",
                                    "properties": {
                                      "id": {
                                        "type": "string",
                                        "description": "per_… id; pass to /phones/lookup as person_id."
                                      },
                                      "name": {
                                        "anyOf": [
                                          {
                                            "type": "string"
                                          },
                                          {
                                            "type": "null"
                                          }
                                        ]
                                      },
                                      "title": {
                                        "anyOf": [
                                          {
                                            "type": "string"
                                          },
                                          {
                                            "type": "null"
                                          }
                                        ]
                                      },
                                      "seniority": {
                                        "anyOf": [
                                          {
                                            "type": "string"
                                          },
                                          {
                                            "type": "null"
                                          }
                                        ]
                                      },
                                      "department": {
                                        "anyOf": [
                                          {
                                            "type": "string"
                                          },
                                          {
                                            "type": "null"
                                          }
                                        ]
                                      },
                                      "linkedin_url": {
                                        "anyOf": [
                                          {
                                            "type": "string"
                                          },
                                          {
                                            "type": "null"
                                          }
                                        ]
                                      },
                                      "company": {
                                        "anyOf": [
                                          {
                                            "type": "object",
                                            "properties": {
                                              "name": {
                                                "type": "string"
                                              },
                                              "domain": {
                                                "type": "string"
                                              },
                                              "employees": {
                                                "anyOf": [
                                                  {
                                                    "type": "integer"
                                                  },
                                                  {
                                                    "type": "null"
                                                  }
                                                ]
                                              }
                                            },
                                            "required": [
                                              "name",
                                              "domain",
                                              "employees"
                                            ]
                                          },
                                          {
                                            "type": "null"
                                          }
                                        ]
                                      },
                                      "work_email": {
                                        "anyOf": [
                                          {
                                            "type": "object",
                                            "properties": {
                                              "address": {
                                                "type": "string"
                                              },
                                              "risk": {
                                                "type": "string",
                                                "enum": [
                                                  "A",
                                                  "B",
                                                  "C",
                                                  "D",
                                                  "F"
                                                ]
                                              },
                                              "verified_at": {
                                                "type": "string",
                                                "format": "date-time"
                                              }
                                            },
                                            "required": [
                                              "address",
                                              "risk",
                                              "verified_at"
                                            ]
                                          },
                                          {
                                            "type": "null"
                                          }
                                        ]
                                      },
                                      "personal_email": {
                                        "anyOf": [
                                          {
                                            "type": "object",
                                            "properties": {
                                              "address": {
                                                "type": "string"
                                              },
                                              "risk": {
                                                "type": "string",
                                                "enum": [
                                                  "A",
                                                  "B",
                                                  "C",
                                                  "D",
                                                  "F"
                                                ]
                                              },
                                              "verified_at": {
                                                "type": "string",
                                                "format": "date-time"
                                              }
                                            },
                                            "required": [
                                              "address",
                                              "risk",
                                              "verified_at"
                                            ]
                                          },
                                          {
                                            "type": "null"
                                          }
                                        ]
                                      },
                                      "mobile": {
                                        "anyOf": [
                                          {
                                            "type": "object",
                                            "properties": {
                                              "number": {
                                                "type": "string",
                                                "description": "E.164"
                                              },
                                              "line_type": {
                                                "type": "string"
                                              },
                                              "carrier": {
                                                "anyOf": [
                                                  {
                                                    "type": "string"
                                                  },
                                                  {
                                                    "type": "null"
                                                  }
                                                ]
                                              },
                                              "checked_at": {
                                                "type": "string",
                                                "format": "date-time"
                                              }
                                            },
                                            "required": [
                                              "number",
                                              "line_type",
                                              "carrier",
                                              "checked_at"
                                            ]
                                          },
                                          {
                                            "type": "null"
                                          }
                                        ]
                                      },
                                      "credits_used": {
                                        "type": "number",
                                        "description": "Credits charged for this call. 0 for test keys and idempotent replays."
                                      },
                                      "test_mode": {
                                        "type": "boolean",
                                        "description": "Present and true when called with a wl_test_ key (sandbox data, nothing charged)."
                                      },
                                      "idempotent_replay": {
                                        "type": "boolean",
                                        "description": "Present and true when this is a stored result replayed for a reused Idempotency-Key (credits_used is 0)."
                                      }
                                    },
                                    "required": [
                                      "id",
                                      "work_email",
                                      "personal_email",
                                      "mobile",
                                      "credits_used"
                                    ]
                                  },
                                  {
                                    "type": "object",
                                    "properties": {
                                      "ok": {
                                        "const": true
                                      }
                                    },
                                    "required": [
                                      "ok"
                                    ]
                                  }
                                ]
                              },
                              {
                                "type": "object",
                                "properties": {
                                  "ok": {
                                    "const": false
                                  },
                                  "error": {
                                    "type": "object",
                                    "properties": {
                                      "code": {
                                        "type": "string"
                                      },
                                      "message": {
                                        "type": "string"
                                      }
                                    },
                                    "required": [
                                      "code",
                                      "message"
                                    ]
                                  }
                                },
                                "required": [
                                  "ok",
                                  "error"
                                ]
                              }
                            ]
                          }
                        },
                        "credits_used": {
                          "type": "number",
                          "description": "Credits charged for this call. 0 for test keys and idempotent replays."
                        },
                        "test_mode": {
                          "type": "boolean",
                          "description": "Present and true when called with a wl_test_ key (sandbox data, nothing charged)."
                        },
                        "idempotent_replay": {
                          "type": "boolean",
                          "description": "Present and true when this is a stored result replayed for a reused Idempotency-Key (credits_used is 0)."
                        }
                      },
                      "required": [
                        "results",
                        "credits_used"
                      ]
                    },
                    {
                      "type": "object",
                      "properties": {
                        "results": {
                          "type": "array",
                          "items": {
                            "type": "object",
                            "properties": {
                              "email": {
                                "type": "string"
                              },
                              "status": {
                                "type": "string",
                                "enum": [
                                  "deliverable",
                                  "risky",
                                  "undeliverable",
                                  "unknown"
                                ]
                              },
                              "risk": {
                                "type": "string",
                                "enum": [
                                  "A",
                                  "B",
                                  "C",
                                  "D",
                                  "F"
                                ]
                              },
                              "catch_all": {
                                "type": "boolean"
                              },
                              "reason": {
                                "type": "string",
                                "enum": [
                                  "invalid_syntax",
                                  "mailbox_not_found"
                                ]
                              }
                            },
                            "required": [
                              "email",
                              "status",
                              "risk",
                              "catch_all"
                            ]
                          }
                        },
                        "credits_used": {
                          "type": "number",
                          "description": "Credits charged for this call. 0 for test keys and idempotent replays."
                        },
                        "test_mode": {
                          "type": "boolean",
                          "description": "Present and true when called with a wl_test_ key (sandbox data, nothing charged)."
                        },
                        "idempotent_replay": {
                          "type": "boolean",
                          "description": "Present and true when this is a stored result replayed for a reused Idempotency-Key (credits_used is 0)."
                        }
                      },
                      "required": [
                        "results",
                        "credits_used"
                      ]
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "description": "`invalid_request`: A field is missing or malformed, the call could exceed the per-call credit ceiling, or a cursor is invalid. The message says which.",
            "headers": {
              "X-Credits-Used": {
                "schema": {
                  "const": 0
                },
                "description": "Always 0: errors never charge."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "invalid_request",
                    "message": "A field is missing or malformed, the call could exceed the per-call credit ceiling, or a cursor is invalid. The message says which."
                  }
                }
              }
            }
          },
          "401": {
            "description": "`unauthorized`: Missing or invalid API key.",
            "headers": {
              "X-Credits-Used": {
                "schema": {
                  "const": 0
                },
                "description": "Always 0: errors never charge."
              },
              "WWW-Authenticate": {
                "schema": {
                  "type": "string"
                },
                "description": "Bearer realm=\"wholayer\""
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "unauthorized",
                    "message": "Missing or invalid API key."
                  }
                }
              }
            }
          },
          "402": {
            "description": "`credits_exhausted`: Wallet empty (Free plan), monthly spend cap reached, this key's daily cap reached, or (agency client workspaces) the client's monthly budget is used up.",
            "headers": {
              "X-Credits-Used": {
                "schema": {
                  "const": 0
                },
                "description": "Always 0: errors never charge."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "credits_exhausted",
                    "message": "Wallet empty (Free plan), monthly spend cap reached, this key's daily cap reached, or (agency client workspaces) the client's monthly budget is used up."
                  }
                }
              }
            }
          },
          "403": {
            "description": "`forbidden`: A read-only key was used on an endpoint that reveals contact details, spends credits, or changes the suppression list. `plan_restricted`: The workspace's plan doesn't include this feature (mobile numbers need Growth or above on live keys; test keys bypass plan gates). The error carries required_plan, current_plan, and upgrade_url.",
            "headers": {
              "X-Credits-Used": {
                "schema": {
                  "const": 0
                },
                "description": "Always 0: errors never charge."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "forbidden",
                    "message": "A read-only key was used on an endpoint that reveals contact details, spends credits, or changes the suppression list."
                  }
                }
              }
            }
          },
          "409": {
            "description": "`duplicate_request`: This Idempotency-Key was already used with a different request body. `request_in_progress`: A request with this Idempotency-Key is still running. Retry in a few seconds to get its stored result.",
            "headers": {
              "X-Credits-Used": {
                "schema": {
                  "const": 0
                },
                "description": "Always 0: errors never charge."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "duplicate_request",
                    "message": "This Idempotency-Key was already used with a different request body."
                  }
                }
              }
            }
          },
          "413": {
            "description": "`payload_too_large`: Body over 4 MB (gzip /jobs bodies), more than 1,000 rows sent to /bulk or 100,000 to /jobs, or the suppression list is full.",
            "headers": {
              "X-Credits-Used": {
                "schema": {
                  "const": 0
                },
                "description": "Always 0: errors never charge."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "payload_too_large",
                    "message": "Body over 4 MB (gzip /jobs bodies), more than 1,000 rows sent to /bulk or 100,000 to /jobs, or the suppression list is full."
                  }
                }
              }
            }
          },
          "429": {
            "description": "`rate_limited`: Too many requests per second for your plan, or 2 jobs already queued or running in this workspace. Retry after the Retry-After header (seconds).",
            "headers": {
              "X-Credits-Used": {
                "schema": {
                  "const": 0
                },
                "description": "Always 0: errors never charge."
              },
              "Retry-After": {
                "schema": {
                  "type": "integer"
                },
                "description": "Seconds to wait."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "rate_limited",
                    "message": "Too many requests per second for your plan, or 2 jobs already queued or running in this workspace. Retry after the Retry-After header (seconds)."
                  }
                }
              }
            }
          },
          "500": {
            "description": "`internal_error`: Our fault. Retry with the same Idempotency-Key.",
            "headers": {
              "X-Credits-Used": {
                "schema": {
                  "const": 0
                },
                "description": "Always 0: errors never charge."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "internal_error",
                    "message": "Our fault. Retry with the same Idempotency-Key."
                  }
                }
              }
            }
          },
          "503": {
            "description": "`provider_unavailable`: A data source is temporarily unavailable, or the lookup is switched off on live keys (live people search stays off until its supplier cost is verified). You are not charged. Retry later; test keys always work.",
            "headers": {
              "X-Credits-Used": {
                "schema": {
                  "const": 0
                },
                "description": "Always 0: errors never charge."
              },
              "Retry-After": {
                "schema": {
                  "type": "integer"
                },
                "description": "Seconds to wait."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "provider_unavailable",
                    "message": "A data source is temporarily unavailable, or the lookup is switched off on live keys (live people search stays off until its supplier cost is verified). You are not charged. Retry later; test keys always work."
                  }
                }
              }
            }
          }
        }
      }
    },
    "/jobs": {
      "post": {
        "operationId": "createJob",
        "tags": [
          "Jobs"
        ],
        "summary": "Start an async bulk job",
        "description": "Queue up to 100,000 enrich or verify rows; the response (202) has the job id and you poll GET /jobs/{id}, or pass webhook_url to get a signed job.completed or job.failed event (same WhoLayer-Signature headers as other webhooks). Rows are processed in the background in batches with the same logic, prices, plan gates, and suppression as /bulk: each enrich row is a /people/enrich body, each verify row is { \"email\": \"…\" } (each address is verified and charged once per job). Every row may carry external_id, returned with its result.\n\nmax_credits (body, or the X-Max-Credits header; default 250, up to 2,000,000) covers the whole job: it is refused with 400 if its worst case is above max_credits, and the worker never lets it spend more. Quote it first with /estimate (operation bulk or verify, rows).\n\nProcessing stops at the first credits_exhausted row (wallet, spend cap, key daily cap, or client budget) or if the key is revoked; the job is then failed with an error and rows not reached come back as not_processed. Up to 2 jobs can be queued or running per workspace (429 rate_limited). Bodies over 4 MB can be sent gzipped with Content-Encoding: gzip. Test keys run sandbox jobs and never spend. Jobs and results are deleted after 30 days.",
        "x-credit-cost": {
          "work_email": 1,
          "personal_email": 2,
          "mobile": 5,
          "reverse_email_lookup": 10,
          "per_verified_address": 0.25
        },
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          },
          {
            "$ref": "#/components/parameters/MaxCredits"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "operation": {
                    "type": "string",
                    "enum": [
                      "enrich",
                      "verify"
                    ]
                  },
                  "rows": {
                    "minItems": 1,
                    "maxItems": 100000,
                    "type": "array",
                    "items": {
                      "anyOf": [
                        {
                          "type": "object",
                          "properties": {
                            "linkedin_url": {
                              "anyOf": [
                                {
                                  "type": "string",
                                  "format": "uri"
                                },
                                {
                                  "type": "null"
                                }
                              ]
                            },
                            "email": {
                              "anyOf": [
                                {
                                  "type": "string",
                                  "format": "email",
                                  "pattern": "^(?:[A-Za-z0-9_'+\\-]+\\.)*[A-Za-z0-9_'+\\-]*[A-Za-z0-9_+-]@(?:[A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$"
                                },
                                {
                                  "type": "null"
                                }
                              ]
                            },
                            "name": {
                              "anyOf": [
                                {
                                  "type": "string",
                                  "minLength": 1,
                                  "maxLength": 200
                                },
                                {
                                  "type": "null"
                                }
                              ]
                            },
                            "company_domain": {
                              "anyOf": [
                                {
                                  "type": "string",
                                  "minLength": 3,
                                  "maxLength": 253
                                },
                                {
                                  "type": "null"
                                }
                              ]
                            },
                            "reveal": {
                              "default": [
                                "work_email"
                              ],
                              "minItems": 1,
                              "type": "array",
                              "items": {
                                "type": "string",
                                "enum": [
                                  "work_email",
                                  "personal_email",
                                  "mobile"
                                ]
                              }
                            },
                            "external_id": {
                              "description": "Your own id for the row (up to 200 characters). Returned with the row's result.",
                              "anyOf": [
                                {
                                  "anyOf": [
                                    {
                                      "type": "string",
                                      "maxLength": 200
                                    },
                                    {
                                      "type": "number"
                                    }
                                  ]
                                },
                                {
                                  "type": "null"
                                }
                              ]
                            }
                          }
                        },
                        {
                          "type": "object",
                          "properties": {
                            "email": {
                              "type": "string",
                              "maxLength": 320
                            },
                            "external_id": {
                              "description": "Your own id for the row (up to 200 characters). Returned with the row's result.",
                              "anyOf": [
                                {
                                  "anyOf": [
                                    {
                                      "type": "string",
                                      "maxLength": 200
                                    },
                                    {
                                      "type": "number"
                                    }
                                  ]
                                },
                                {
                                  "type": "null"
                                }
                              ]
                            }
                          },
                          "required": [
                            "email"
                          ]
                        }
                      ]
                    }
                  },
                  "webhook_url": {
                    "description": "https URL that receives a signed job.completed or job.failed event.",
                    "anyOf": [
                      {
                        "type": "string",
                        "maxLength": 2000,
                        "format": "uri"
                      },
                      {
                        "type": "null"
                      }
                    ]
                  },
                  "max_credits": {
                    "description": "Most the whole job may spend (default 250, up to 2,000,000). The job is refused if its worst case is above it.",
                    "anyOf": [
                      {
                        "type": "number",
                        "minimum": 0,
                        "maximum": 2000000
                      },
                      {
                        "type": "null"
                      }
                    ]
                  }
                },
                "required": [
                  "operation",
                  "rows"
                ]
              }
            }
          }
        },
        "responses": {
          "202": {
            "description": "Job queued. Nothing is charged until rows are processed.",
            "headers": {
              "X-Credits-Used": {
                "description": "Credits this call charged (same as credits_used). Sent on every response; 0 when free, on errors, for test keys, and for idempotent replays.",
                "schema": {
                  "type": "number"
                }
              },
              "X-Credits-Remaining": {
                "description": "Credits left in the workspace wallet after this call (omitted when unknown).",
                "schema": {
                  "type": "number"
                }
              },
              "X-WhoLayer-Mode": {
                "description": "live or test, from the API key used.",
                "schema": {
                  "type": "string",
                  "enum": [
                    "live",
                    "test"
                  ]
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "string",
                      "description": "job_…"
                    },
                    "object": {
                      "const": "job"
                    },
                    "operation": {
                      "type": "string",
                      "enum": [
                        "enrich",
                        "verify"
                      ]
                    },
                    "status": {
                      "type": "string",
                      "enum": [
                        "queued",
                        "running",
                        "completed",
                        "failed"
                      ]
                    },
                    "total_rows": {
                      "type": "integer"
                    },
                    "processed_rows": {
                      "type": "integer"
                    },
                    "succeeded_rows": {
                      "type": "integer"
                    },
                    "failed_rows": {
                      "type": "integer"
                    },
                    "progress": {
                      "type": "number",
                      "description": "processed_rows / total_rows, 0 to 1."
                    },
                    "estimate_credits": {
                      "type": "number",
                      "description": "Worst case if every row is a hit."
                    },
                    "max_credits": {
                      "type": "number"
                    },
                    "job_credits_used": {
                      "type": "number",
                      "description": "Credits charged by this job so far (0 for test keys)."
                    },
                    "error": {
                      "anyOf": [
                        {
                          "type": "object",
                          "properties": {
                            "code": {
                              "type": "string"
                            },
                            "message": {
                              "type": "string"
                            }
                          },
                          "required": [
                            "code",
                            "message"
                          ]
                        },
                        {
                          "type": "null"
                        }
                      ],
                      "description": "Why a failed job stopped: credits_exhausted, forbidden (key revoked), or max_credits_reached."
                    },
                    "webhook_url": {
                      "anyOf": [
                        {
                          "type": "string"
                        },
                        {
                          "type": "null"
                        }
                      ]
                    },
                    "webhook_status": {
                      "anyOf": [
                        {
                          "type": "string",
                          "enum": [
                            "pending",
                            "sent",
                            "failed"
                          ]
                        },
                        {
                          "type": "null"
                        }
                      ]
                    },
                    "created_at": {
                      "type": "string",
                      "format": "date-time"
                    },
                    "started_at": {
                      "anyOf": [
                        {
                          "type": "string",
                          "format": "date-time"
                        },
                        {
                          "type": "null"
                        }
                      ]
                    },
                    "finished_at": {
                      "anyOf": [
                        {
                          "type": "string",
                          "format": "date-time"
                        },
                        {
                          "type": "null"
                        }
                      ]
                    },
                    "expires_at": {
                      "type": "string",
                      "format": "date-time",
                      "description": "When the job and its results are deleted."
                    },
                    "results_url": {
                      "type": "string"
                    },
                    "test_mode": {
                      "type": "boolean",
                      "description": "Present and true when called with a wl_test_ key (sandbox data, nothing charged)."
                    },
                    "credits_used": {
                      "type": "number",
                      "description": "Always 0: creating and reading a job is free; rows are charged as they are processed (see job_credits_used)."
                    },
                    "idempotent_replay": {
                      "type": "boolean",
                      "description": "Present and true when this is a stored result replayed for a reused Idempotency-Key (credits_used is 0)."
                    }
                  },
                  "required": [
                    "id",
                    "object",
                    "operation",
                    "status",
                    "total_rows",
                    "processed_rows",
                    "succeeded_rows",
                    "failed_rows",
                    "progress",
                    "estimate_credits",
                    "max_credits",
                    "job_credits_used",
                    "error",
                    "created_at",
                    "expires_at",
                    "results_url"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "`invalid_request`: A field is missing or malformed, the call could exceed the per-call credit ceiling, or a cursor is invalid. The message says which.",
            "headers": {
              "X-Credits-Used": {
                "schema": {
                  "const": 0
                },
                "description": "Always 0: errors never charge."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "invalid_request",
                    "message": "A field is missing or malformed, the call could exceed the per-call credit ceiling, or a cursor is invalid. The message says which."
                  }
                }
              }
            }
          },
          "401": {
            "description": "`unauthorized`: Missing or invalid API key.",
            "headers": {
              "X-Credits-Used": {
                "schema": {
                  "const": 0
                },
                "description": "Always 0: errors never charge."
              },
              "WWW-Authenticate": {
                "schema": {
                  "type": "string"
                },
                "description": "Bearer realm=\"wholayer\""
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "unauthorized",
                    "message": "Missing or invalid API key."
                  }
                }
              }
            }
          },
          "402": {
            "description": "`credits_exhausted`: Wallet empty (Free plan), monthly spend cap reached, this key's daily cap reached, or (agency client workspaces) the client's monthly budget is used up.",
            "headers": {
              "X-Credits-Used": {
                "schema": {
                  "const": 0
                },
                "description": "Always 0: errors never charge."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "credits_exhausted",
                    "message": "Wallet empty (Free plan), monthly spend cap reached, this key's daily cap reached, or (agency client workspaces) the client's monthly budget is used up."
                  }
                }
              }
            }
          },
          "403": {
            "description": "`forbidden`: A read-only key was used on an endpoint that reveals contact details, spends credits, or changes the suppression list. `plan_restricted`: The workspace's plan doesn't include this feature (mobile numbers need Growth or above on live keys; test keys bypass plan gates). The error carries required_plan, current_plan, and upgrade_url.",
            "headers": {
              "X-Credits-Used": {
                "schema": {
                  "const": 0
                },
                "description": "Always 0: errors never charge."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "forbidden",
                    "message": "A read-only key was used on an endpoint that reveals contact details, spends credits, or changes the suppression list."
                  }
                }
              }
            }
          },
          "409": {
            "description": "`duplicate_request`: This Idempotency-Key was already used with a different request body. `request_in_progress`: A request with this Idempotency-Key is still running. Retry in a few seconds to get its stored result.",
            "headers": {
              "X-Credits-Used": {
                "schema": {
                  "const": 0
                },
                "description": "Always 0: errors never charge."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "duplicate_request",
                    "message": "This Idempotency-Key was already used with a different request body."
                  }
                }
              }
            }
          },
          "413": {
            "description": "`payload_too_large`: Body over 4 MB (gzip /jobs bodies), more than 1,000 rows sent to /bulk or 100,000 to /jobs, or the suppression list is full.",
            "headers": {
              "X-Credits-Used": {
                "schema": {
                  "const": 0
                },
                "description": "Always 0: errors never charge."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "payload_too_large",
                    "message": "Body over 4 MB (gzip /jobs bodies), more than 1,000 rows sent to /bulk or 100,000 to /jobs, or the suppression list is full."
                  }
                }
              }
            }
          },
          "429": {
            "description": "`rate_limited`: Too many requests per second for your plan, or 2 jobs already queued or running in this workspace. Retry after the Retry-After header (seconds).",
            "headers": {
              "X-Credits-Used": {
                "schema": {
                  "const": 0
                },
                "description": "Always 0: errors never charge."
              },
              "Retry-After": {
                "schema": {
                  "type": "integer"
                },
                "description": "Seconds to wait."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "rate_limited",
                    "message": "Too many requests per second for your plan, or 2 jobs already queued or running in this workspace. Retry after the Retry-After header (seconds)."
                  }
                }
              }
            }
          },
          "500": {
            "description": "`internal_error`: Our fault. Retry with the same Idempotency-Key.",
            "headers": {
              "X-Credits-Used": {
                "schema": {
                  "const": 0
                },
                "description": "Always 0: errors never charge."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "internal_error",
                    "message": "Our fault. Retry with the same Idempotency-Key."
                  }
                }
              }
            }
          },
          "503": {
            "description": "`provider_unavailable`: A data source is temporarily unavailable, or the lookup is switched off on live keys (live people search stays off until its supplier cost is verified). You are not charged. Retry later; test keys always work.",
            "headers": {
              "X-Credits-Used": {
                "schema": {
                  "const": 0
                },
                "description": "Always 0: errors never charge."
              },
              "Retry-After": {
                "schema": {
                  "type": "integer"
                },
                "description": "Seconds to wait."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "provider_unavailable",
                    "message": "A data source is temporarily unavailable, or the lookup is switched off on live keys (live people search stays off until its supplier cost is verified). You are not charged. Retry later; test keys always work."
                  }
                }
              }
            }
          }
        }
      }
    },
    "/jobs/{id}": {
      "get": {
        "operationId": "getJob",
        "tags": [
          "Jobs"
        ],
        "summary": "Get a job's status",
        "description": "Status, progress, counts, and credits used. Free; works with read-only keys. A job is visible to keys of the same mode (live or test) in its workspace.",
        "x-credit-cost": 0,
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "job_… id from POST /jobs."
          }
        ],
        "responses": {
          "200": {
            "description": "The job.",
            "headers": {
              "X-Credits-Used": {
                "description": "Credits this call charged (same as credits_used). Sent on every response; 0 when free, on errors, for test keys, and for idempotent replays.",
                "schema": {
                  "type": "number"
                }
              },
              "X-Credits-Remaining": {
                "description": "Credits left in the workspace wallet after this call (omitted when unknown).",
                "schema": {
                  "type": "number"
                }
              },
              "X-WhoLayer-Mode": {
                "description": "live or test, from the API key used.",
                "schema": {
                  "type": "string",
                  "enum": [
                    "live",
                    "test"
                  ]
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "string",
                      "description": "job_…"
                    },
                    "object": {
                      "const": "job"
                    },
                    "operation": {
                      "type": "string",
                      "enum": [
                        "enrich",
                        "verify"
                      ]
                    },
                    "status": {
                      "type": "string",
                      "enum": [
                        "queued",
                        "running",
                        "completed",
                        "failed"
                      ]
                    },
                    "total_rows": {
                      "type": "integer"
                    },
                    "processed_rows": {
                      "type": "integer"
                    },
                    "succeeded_rows": {
                      "type": "integer"
                    },
                    "failed_rows": {
                      "type": "integer"
                    },
                    "progress": {
                      "type": "number",
                      "description": "processed_rows / total_rows, 0 to 1."
                    },
                    "estimate_credits": {
                      "type": "number",
                      "description": "Worst case if every row is a hit."
                    },
                    "max_credits": {
                      "type": "number"
                    },
                    "job_credits_used": {
                      "type": "number",
                      "description": "Credits charged by this job so far (0 for test keys)."
                    },
                    "error": {
                      "anyOf": [
                        {
                          "type": "object",
                          "properties": {
                            "code": {
                              "type": "string"
                            },
                            "message": {
                              "type": "string"
                            }
                          },
                          "required": [
                            "code",
                            "message"
                          ]
                        },
                        {
                          "type": "null"
                        }
                      ],
                      "description": "Why a failed job stopped: credits_exhausted, forbidden (key revoked), or max_credits_reached."
                    },
                    "webhook_url": {
                      "anyOf": [
                        {
                          "type": "string"
                        },
                        {
                          "type": "null"
                        }
                      ]
                    },
                    "webhook_status": {
                      "anyOf": [
                        {
                          "type": "string",
                          "enum": [
                            "pending",
                            "sent",
                            "failed"
                          ]
                        },
                        {
                          "type": "null"
                        }
                      ]
                    },
                    "created_at": {
                      "type": "string",
                      "format": "date-time"
                    },
                    "started_at": {
                      "anyOf": [
                        {
                          "type": "string",
                          "format": "date-time"
                        },
                        {
                          "type": "null"
                        }
                      ]
                    },
                    "finished_at": {
                      "anyOf": [
                        {
                          "type": "string",
                          "format": "date-time"
                        },
                        {
                          "type": "null"
                        }
                      ]
                    },
                    "expires_at": {
                      "type": "string",
                      "format": "date-time",
                      "description": "When the job and its results are deleted."
                    },
                    "results_url": {
                      "type": "string"
                    },
                    "test_mode": {
                      "type": "boolean",
                      "description": "Present and true when called with a wl_test_ key (sandbox data, nothing charged)."
                    },
                    "credits_used": {
                      "type": "number",
                      "description": "Always 0: creating and reading a job is free; rows are charged as they are processed (see job_credits_used)."
                    }
                  },
                  "required": [
                    "id",
                    "object",
                    "operation",
                    "status",
                    "total_rows",
                    "processed_rows",
                    "succeeded_rows",
                    "failed_rows",
                    "progress",
                    "estimate_credits",
                    "max_credits",
                    "job_credits_used",
                    "error",
                    "created_at",
                    "expires_at",
                    "results_url"
                  ]
                }
              }
            }
          },
          "401": {
            "description": "`unauthorized`: Missing or invalid API key.",
            "headers": {
              "X-Credits-Used": {
                "schema": {
                  "const": 0
                },
                "description": "Always 0: errors never charge."
              },
              "WWW-Authenticate": {
                "schema": {
                  "type": "string"
                },
                "description": "Bearer realm=\"wholayer\""
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "unauthorized",
                    "message": "Missing or invalid API key."
                  }
                }
              }
            }
          },
          "404": {
            "description": "`not_found`: No company profile for the domain, unknown person_id for this workspace, or no such job (jobs are deleted after 30 days). You are not charged.",
            "headers": {
              "X-Credits-Used": {
                "schema": {
                  "const": 0
                },
                "description": "Always 0: errors never charge."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "not_found",
                    "message": "No company profile for the domain, unknown person_id for this workspace, or no such job (jobs are deleted after 30 days). You are not charged."
                  }
                }
              }
            }
          },
          "429": {
            "description": "`rate_limited`: Too many requests per second for your plan, or 2 jobs already queued or running in this workspace. Retry after the Retry-After header (seconds).",
            "headers": {
              "X-Credits-Used": {
                "schema": {
                  "const": 0
                },
                "description": "Always 0: errors never charge."
              },
              "Retry-After": {
                "schema": {
                  "type": "integer"
                },
                "description": "Seconds to wait."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "rate_limited",
                    "message": "Too many requests per second for your plan, or 2 jobs already queued or running in this workspace. Retry after the Retry-After header (seconds)."
                  }
                }
              }
            }
          },
          "500": {
            "description": "`internal_error`: Our fault. Retry with the same Idempotency-Key.",
            "headers": {
              "X-Credits-Used": {
                "schema": {
                  "const": 0
                },
                "description": "Always 0: errors never charge."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "internal_error",
                    "message": "Our fault. Retry with the same Idempotency-Key."
                  }
                }
              }
            }
          }
        }
      }
    },
    "/jobs/{id}/results": {
      "get": {
        "operationId": "getJobResults",
        "tags": [
          "Jobs"
        ],
        "summary": "Get a job's results",
        "description": "One result per row, in row order, paged with cursor (up to 1000 per page), or every row as CSV with ?format=csv. Enrich rows look like /bulk rows; verify rows look like /emails/verify results. Rows not yet processed have error.code pending (or not_processed once the job has stopped). Stored people are re-checked against every suppression list and your bad-data reports on each read: anyone withheld since comes back as { \"ok\": false, \"status\": 451, \"error\": { \"code\": \"person_not_available\", \"reason\": … } }. Free; works with read-only keys.",
        "x-credit-cost": 0,
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "job_… id from POST /jobs."
          },
          {
            "name": "format",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "json",
                "csv"
              ]
            },
            "description": "csv returns every row as a CSV download."
          },
          {
            "name": "cursor",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "next_cursor from the previous page (JSON only)."
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 1000,
              "default": 100
            },
            "description": "Rows per page (JSON only)."
          }
        ],
        "responses": {
          "200": {
            "description": "A page of results (JSON), or the whole set (CSV).",
            "headers": {
              "X-Credits-Used": {
                "description": "Credits this call charged (same as credits_used). Sent on every response; 0 when free, on errors, for test keys, and for idempotent replays.",
                "schema": {
                  "type": "number"
                }
              },
              "X-Credits-Remaining": {
                "description": "Credits left in the workspace wallet after this call (omitted when unknown).",
                "schema": {
                  "type": "number"
                }
              },
              "X-WhoLayer-Mode": {
                "description": "live or test, from the API key used.",
                "schema": {
                  "type": "string",
                  "enum": [
                    "live",
                    "test"
                  ]
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "job": {
                      "type": "object",
                      "properties": {
                        "id": {
                          "type": "string",
                          "description": "job_…"
                        },
                        "object": {
                          "const": "job"
                        },
                        "operation": {
                          "type": "string",
                          "enum": [
                            "enrich",
                            "verify"
                          ]
                        },
                        "status": {
                          "type": "string",
                          "enum": [
                            "queued",
                            "running",
                            "completed",
                            "failed"
                          ]
                        },
                        "total_rows": {
                          "type": "integer"
                        },
                        "processed_rows": {
                          "type": "integer"
                        },
                        "succeeded_rows": {
                          "type": "integer"
                        },
                        "failed_rows": {
                          "type": "integer"
                        },
                        "progress": {
                          "type": "number",
                          "description": "processed_rows / total_rows, 0 to 1."
                        },
                        "estimate_credits": {
                          "type": "number",
                          "description": "Worst case if every row is a hit."
                        },
                        "max_credits": {
                          "type": "number"
                        },
                        "job_credits_used": {
                          "type": "number",
                          "description": "Credits charged by this job so far (0 for test keys)."
                        },
                        "error": {
                          "anyOf": [
                            {
                              "type": "object",
                              "properties": {
                                "code": {
                                  "type": "string"
                                },
                                "message": {
                                  "type": "string"
                                }
                              },
                              "required": [
                                "code",
                                "message"
                              ]
                            },
                            {
                              "type": "null"
                            }
                          ],
                          "description": "Why a failed job stopped: credits_exhausted, forbidden (key revoked), or max_credits_reached."
                        },
                        "webhook_url": {
                          "anyOf": [
                            {
                              "type": "string"
                            },
                            {
                              "type": "null"
                            }
                          ]
                        },
                        "webhook_status": {
                          "anyOf": [
                            {
                              "type": "string",
                              "enum": [
                                "pending",
                                "sent",
                                "failed"
                              ]
                            },
                            {
                              "type": "null"
                            }
                          ]
                        },
                        "created_at": {
                          "type": "string",
                          "format": "date-time"
                        },
                        "started_at": {
                          "anyOf": [
                            {
                              "type": "string",
                              "format": "date-time"
                            },
                            {
                              "type": "null"
                            }
                          ]
                        },
                        "finished_at": {
                          "anyOf": [
                            {
                              "type": "string",
                              "format": "date-time"
                            },
                            {
                              "type": "null"
                            }
                          ]
                        },
                        "expires_at": {
                          "type": "string",
                          "format": "date-time",
                          "description": "When the job and its results are deleted."
                        },
                        "results_url": {
                          "type": "string"
                        },
                        "test_mode": {
                          "type": "boolean",
                          "description": "Present and true when called with a wl_test_ key (sandbox data, nothing charged)."
                        },
                        "credits_used": {
                          "type": "number",
                          "description": "Always 0: creating and reading a job is free; rows are charged as they are processed (see job_credits_used)."
                        }
                      },
                      "required": [
                        "id",
                        "object",
                        "operation",
                        "status",
                        "total_rows",
                        "processed_rows",
                        "succeeded_rows",
                        "failed_rows",
                        "progress",
                        "estimate_credits",
                        "max_credits",
                        "job_credits_used",
                        "error",
                        "created_at",
                        "expires_at",
                        "results_url"
                      ]
                    },
                    "results": {
                      "type": "array",
                      "items": {
                        "oneOf": [
                          {
                            "allOf": [
                              {
                                "type": "object",
                                "properties": {
                                  "id": {
                                    "type": "string",
                                    "description": "per_… id; pass to /phones/lookup as person_id."
                                  },
                                  "name": {
                                    "anyOf": [
                                      {
                                        "type": "string"
                                      },
                                      {
                                        "type": "null"
                                      }
                                    ]
                                  },
                                  "title": {
                                    "anyOf": [
                                      {
                                        "type": "string"
                                      },
                                      {
                                        "type": "null"
                                      }
                                    ]
                                  },
                                  "seniority": {
                                    "anyOf": [
                                      {
                                        "type": "string"
                                      },
                                      {
                                        "type": "null"
                                      }
                                    ]
                                  },
                                  "department": {
                                    "anyOf": [
                                      {
                                        "type": "string"
                                      },
                                      {
                                        "type": "null"
                                      }
                                    ]
                                  },
                                  "linkedin_url": {
                                    "anyOf": [
                                      {
                                        "type": "string"
                                      },
                                      {
                                        "type": "null"
                                      }
                                    ]
                                  },
                                  "company": {
                                    "anyOf": [
                                      {
                                        "type": "object",
                                        "properties": {
                                          "name": {
                                            "type": "string"
                                          },
                                          "domain": {
                                            "type": "string"
                                          },
                                          "employees": {
                                            "anyOf": [
                                              {
                                                "type": "integer"
                                              },
                                              {
                                                "type": "null"
                                              }
                                            ]
                                          }
                                        },
                                        "required": [
                                          "name",
                                          "domain",
                                          "employees"
                                        ]
                                      },
                                      {
                                        "type": "null"
                                      }
                                    ]
                                  },
                                  "work_email": {
                                    "anyOf": [
                                      {
                                        "type": "object",
                                        "properties": {
                                          "address": {
                                            "type": "string"
                                          },
                                          "risk": {
                                            "type": "string",
                                            "enum": [
                                              "A",
                                              "B",
                                              "C",
                                              "D",
                                              "F"
                                            ]
                                          },
                                          "verified_at": {
                                            "type": "string",
                                            "format": "date-time"
                                          }
                                        },
                                        "required": [
                                          "address",
                                          "risk",
                                          "verified_at"
                                        ]
                                      },
                                      {
                                        "type": "null"
                                      }
                                    ]
                                  },
                                  "personal_email": {
                                    "anyOf": [
                                      {
                                        "type": "object",
                                        "properties": {
                                          "address": {
                                            "type": "string"
                                          },
                                          "risk": {
                                            "type": "string",
                                            "enum": [
                                              "A",
                                              "B",
                                              "C",
                                              "D",
                                              "F"
                                            ]
                                          },
                                          "verified_at": {
                                            "type": "string",
                                            "format": "date-time"
                                          }
                                        },
                                        "required": [
                                          "address",
                                          "risk",
                                          "verified_at"
                                        ]
                                      },
                                      {
                                        "type": "null"
                                      }
                                    ]
                                  },
                                  "mobile": {
                                    "anyOf": [
                                      {
                                        "type": "object",
                                        "properties": {
                                          "number": {
                                            "type": "string",
                                            "description": "E.164"
                                          },
                                          "line_type": {
                                            "type": "string"
                                          },
                                          "carrier": {
                                            "anyOf": [
                                              {
                                                "type": "string"
                                              },
                                              {
                                                "type": "null"
                                              }
                                            ]
                                          },
                                          "checked_at": {
                                            "type": "string",
                                            "format": "date-time"
                                          }
                                        },
                                        "required": [
                                          "number",
                                          "line_type",
                                          "carrier",
                                          "checked_at"
                                        ]
                                      },
                                      {
                                        "type": "null"
                                      }
                                    ]
                                  },
                                  "credits_used": {
                                    "type": "number",
                                    "description": "Credits charged for this call. 0 for test keys and idempotent replays."
                                  },
                                  "test_mode": {
                                    "type": "boolean",
                                    "description": "Present and true when called with a wl_test_ key (sandbox data, nothing charged)."
                                  },
                                  "idempotent_replay": {
                                    "type": "boolean",
                                    "description": "Present and true when this is a stored result replayed for a reused Idempotency-Key (credits_used is 0)."
                                  }
                                },
                                "required": [
                                  "id",
                                  "work_email",
                                  "personal_email",
                                  "mobile",
                                  "credits_used"
                                ]
                              },
                              {
                                "type": "object",
                                "properties": {
                                  "index": {
                                    "type": "integer"
                                  },
                                  "external_id": {
                                    "anyOf": [
                                      {
                                        "type": "string"
                                      },
                                      {
                                        "type": "null"
                                      }
                                    ]
                                  },
                                  "ok": {
                                    "const": true
                                  }
                                },
                                "required": [
                                  "index",
                                  "external_id",
                                  "ok"
                                ]
                              }
                            ]
                          },
                          {
                            "allOf": [
                              {
                                "type": "object",
                                "properties": {
                                  "email": {
                                    "type": "string"
                                  },
                                  "status": {
                                    "type": "string",
                                    "enum": [
                                      "deliverable",
                                      "risky",
                                      "undeliverable",
                                      "unknown"
                                    ]
                                  },
                                  "risk": {
                                    "type": "string",
                                    "enum": [
                                      "A",
                                      "B",
                                      "C",
                                      "D",
                                      "F"
                                    ]
                                  },
                                  "catch_all": {
                                    "type": "boolean"
                                  },
                                  "reason": {
                                    "type": "string",
                                    "enum": [
                                      "invalid_syntax",
                                      "mailbox_not_found"
                                    ]
                                  }
                                },
                                "required": [
                                  "email",
                                  "status",
                                  "risk",
                                  "catch_all"
                                ]
                              },
                              {
                                "type": "object",
                                "properties": {
                                  "index": {
                                    "type": "integer"
                                  },
                                  "external_id": {
                                    "anyOf": [
                                      {
                                        "type": "string"
                                      },
                                      {
                                        "type": "null"
                                      }
                                    ]
                                  },
                                  "ok": {
                                    "const": true
                                  }
                                },
                                "required": [
                                  "index",
                                  "external_id",
                                  "ok"
                                ]
                              }
                            ]
                          },
                          {
                            "type": "object",
                            "properties": {
                              "index": {
                                "type": "integer"
                              },
                              "external_id": {
                                "anyOf": [
                                  {
                                    "type": "string"
                                  },
                                  {
                                    "type": "null"
                                  }
                                ]
                              },
                              "ok": {
                                "const": false
                              },
                              "status": {
                                "type": "integer",
                                "description": "HTTP status the row would have had as a single call, e.g. 404 or 451."
                              },
                              "error": {
                                "type": "object",
                                "properties": {
                                  "code": {
                                    "type": "string"
                                  },
                                  "message": {
                                    "type": "string"
                                  },
                                  "reason": {
                                    "type": "string",
                                    "enum": [
                                      "opted_out",
                                      "drop_deleted",
                                      "customer_suppressed",
                                      "non_us",
                                      "sensitive"
                                    ]
                                  }
                                },
                                "required": [
                                  "code",
                                  "message"
                                ]
                              }
                            },
                            "required": [
                              "index",
                              "external_id",
                              "ok",
                              "error"
                            ]
                          }
                        ]
                      }
                    },
                    "next_cursor": {
                      "anyOf": [
                        {
                          "type": "string"
                        },
                        {
                          "type": "null"
                        }
                      ]
                    },
                    "credits_used": {
                      "type": "number",
                      "description": "Credits charged for this call. 0 for test keys and idempotent replays."
                    }
                  },
                  "required": [
                    "job",
                    "results",
                    "next_cursor",
                    "credits_used"
                  ]
                }
              },
              "text/csv": {
                "schema": {
                  "type": "string",
                  "description": "Columns: index, external_id, result (ok or error), error_code, error_reason, error_message, the result fields, credits_used."
                }
              }
            }
          },
          "400": {
            "description": "`invalid_request`: A field is missing or malformed, the call could exceed the per-call credit ceiling, or a cursor is invalid. The message says which.",
            "headers": {
              "X-Credits-Used": {
                "schema": {
                  "const": 0
                },
                "description": "Always 0: errors never charge."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "invalid_request",
                    "message": "A field is missing or malformed, the call could exceed the per-call credit ceiling, or a cursor is invalid. The message says which."
                  }
                }
              }
            }
          },
          "401": {
            "description": "`unauthorized`: Missing or invalid API key.",
            "headers": {
              "X-Credits-Used": {
                "schema": {
                  "const": 0
                },
                "description": "Always 0: errors never charge."
              },
              "WWW-Authenticate": {
                "schema": {
                  "type": "string"
                },
                "description": "Bearer realm=\"wholayer\""
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "unauthorized",
                    "message": "Missing or invalid API key."
                  }
                }
              }
            }
          },
          "404": {
            "description": "`not_found`: No company profile for the domain, unknown person_id for this workspace, or no such job (jobs are deleted after 30 days). You are not charged.",
            "headers": {
              "X-Credits-Used": {
                "schema": {
                  "const": 0
                },
                "description": "Always 0: errors never charge."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "not_found",
                    "message": "No company profile for the domain, unknown person_id for this workspace, or no such job (jobs are deleted after 30 days). You are not charged."
                  }
                }
              }
            }
          },
          "429": {
            "description": "`rate_limited`: Too many requests per second for your plan, or 2 jobs already queued or running in this workspace. Retry after the Retry-After header (seconds).",
            "headers": {
              "X-Credits-Used": {
                "schema": {
                  "const": 0
                },
                "description": "Always 0: errors never charge."
              },
              "Retry-After": {
                "schema": {
                  "type": "integer"
                },
                "description": "Seconds to wait."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "rate_limited",
                    "message": "Too many requests per second for your plan, or 2 jobs already queued or running in this workspace. Retry after the Retry-After header (seconds)."
                  }
                }
              }
            }
          },
          "500": {
            "description": "`internal_error`: Our fault. Retry with the same Idempotency-Key.",
            "headers": {
              "X-Credits-Used": {
                "schema": {
                  "const": 0
                },
                "description": "Always 0: errors never charge."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "internal_error",
                    "message": "Our fault. Retry with the same Idempotency-Key."
                  }
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "bearerAuth": {
        "type": "http",
        "scheme": "bearer",
        "description": "Secret API key from Settings, API keys: wl_live_… (live) or wl_test_… (sandbox, never spends). Server-side only."
      }
    },
    "parameters": {
      "IdempotencyKey": {
        "name": "Idempotency-Key",
        "in": "header",
        "required": false,
        "schema": {
          "type": "string",
          "maxLength": 200
        },
        "description": "Send on POST requests. Retrying with the same key within 30 days returns the stored result and never charges twice. Reusing a key with a different body returns 409 duplicate_request."
      },
      "MaxCredits": {
        "name": "X-Max-Credits",
        "in": "header",
        "required": false,
        "schema": {
          "type": "number",
          "minimum": 0,
          "maximum": 10000,
          "default": 250
        },
        "description": "Per-call credit ceiling (default 250, capped at 10,000). A call whose worst-case cost is above it fails with 400 invalid_request before anything is charged."
      }
    },
    "schemas": {
      "Error": {
        "type": "object",
        "properties": {
          "error": {
            "type": "object",
            "properties": {
              "code": {
                "type": "string",
                "enum": [
                  "invalid_request",
                  "unauthorized",
                  "credits_exhausted",
                  "forbidden",
                  "plan_restricted",
                  "person_not_available",
                  "not_found",
                  "duplicate_request",
                  "request_in_progress",
                  "payload_too_large",
                  "rate_limited",
                  "internal_error",
                  "provider_unavailable"
                ]
              },
              "message": {
                "type": "string"
              },
              "reason": {
                "type": "string",
                "enum": [
                  "opted_out",
                  "drop_deleted",
                  "customer_suppressed",
                  "non_us",
                  "sensitive"
                ],
                "description": "On 451 person_not_available: why the person is withheld."
              },
              "required_plan": {
                "type": "string",
                "enum": [
                  "free",
                  "starter",
                  "growth",
                  "scale",
                  "platform"
                ],
                "description": "On 403 plan_restricted."
              },
              "current_plan": {
                "type": "string",
                "enum": [
                  "free",
                  "starter",
                  "growth",
                  "scale",
                  "platform"
                ],
                "description": "On 403 plan_restricted."
              },
              "upgrade_url": {
                "type": "string",
                "format": "uri",
                "description": "On 403 plan_restricted."
              }
            },
            "required": [
              "code",
              "message"
            ]
          }
        },
        "required": [
          "error"
        ]
      }
    }
  }
}