{
  "openapi": "3.1.0",
  "info": {
    "title": "LayerCall API",
    "version": "1.0.0",
    "summary": "Trust and risk intelligence for IPs, emails, phones, domains and devices.",
    "description": "Score any IP address, email, phone number, domain or device \u2014 or a whole signup in one call \u2014 and get back a 0\u2013100 risk score plus an allow / review / block verdict.\n\nAuthenticate with an API key from https://www.layercall.com/get-key. Free tier: 1,000 lookups per month, no card required. Test-mode keys are never billed.\n\nEvery endpoint accepts an optional `strictness` parameter (`lenient`, `balanced`, `strict`) that shifts the verdict thresholds without changing the underlying signals.",
    "termsOfService": "https://www.layercall.com/terms",
    "contact": {
      "name": "LayerCall Support",
      "email": "support@layercall.com",
      "url": "https://www.layercall.com/contact"
    },
    "license": {
      "name": "Proprietary",
      "url": "https://www.layercall.com/terms"
    },
    "x-logo": {
      "url": "https://www.layercall.com/logo-icon.png",
      "altText": "LayerCall"
    }
  },
  "servers": [
    {
      "url": "https://www.layercall.com",
      "description": "Production"
    }
  ],
  "externalDocs": {
    "description": "Guides and examples",
    "url": "https://www.layercall.com/docs"
  },
  "security": [
    {
      "ApiKeyAuth": []
    },
    {
      "BearerAuth": []
    }
  ],
  "tags": [
    {
      "name": "Scoring",
      "description": "Risk scores for a single signal type."
    },
    {
      "name": "Unified",
      "description": "One call across every signal on a signup."
    },
    {
      "name": "Device",
      "description": "Fingerprint reputation and bot detection."
    }
  ],
  "paths": {
    "/v1/score/ip": {
      "get": {
        "tags": [
          "Scoring"
        ],
        "operationId": "scoreIp",
        "summary": "Score an IP address",
        "description": "Detects VPNs, proxies, Tor exit nodes and datacenter ranges, and returns geo/ASN context plus reputation from LayerCall's first-seen network.",
        "parameters": [
          {
            "name": "ip",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "IPv4 or IPv6 address. IPv4-mapped IPv6 (`::ffff:1.2.3.4`) is canonicalised.",
            "example": "185.220.101.5"
          },
          {
            "$ref": "#/components/parameters/Strictness"
          }
        ],
        "responses": {
          "200": {
            "description": "Score",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/IpScore"
                }
              }
            },
            "headers": {
              "x-ratelimit-limit": {
                "description": "Requests allowed per minute (600).",
                "schema": {
                  "type": "integer"
                }
              },
              "x-ratelimit-remaining": {
                "description": "Requests left in the current minute.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/verify/email": {
      "get": {
        "tags": [
          "Scoring"
        ],
        "operationId": "verifyEmail",
        "summary": "Verify and score an email address",
        "description": "Checks syntax, MX records and mailbox deliverability, and flags disposable, role-based, free-provider and homograph (lookalike) addresses.",
        "parameters": [
          {
            "name": "email",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string",
              "format": "email"
            },
            "example": "test@mailinator.com"
          },
          {
            "$ref": "#/components/parameters/Strictness"
          }
        ],
        "responses": {
          "200": {
            "description": "Result",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/EmailResult"
                }
              }
            },
            "headers": {
              "x-ratelimit-limit": {
                "description": "Requests allowed per minute (600).",
                "schema": {
                  "type": "integer"
                }
              },
              "x-ratelimit-remaining": {
                "description": "Requests left in the current minute.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/lookup/phone": {
      "get": {
        "tags": [
          "Scoring"
        ],
        "operationId": "lookupPhone",
        "summary": "Validate and score a phone number",
        "description": "Parses the number, identifies line type (mobile, VoIP, premium rate, toll free) and carrier, and scores fraud risk.",
        "parameters": [
          {
            "name": "phone",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "E.164 recommended. Without a leading `+`, pass `country`.",
            "example": "+14155552671"
          },
          {
            "name": "country",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "minLength": 2,
              "maxLength": 2
            },
            "description": "ISO 3166-1 alpha-2, required for national-format numbers.",
            "example": "US"
          },
          {
            "$ref": "#/components/parameters/Strictness"
          }
        ],
        "responses": {
          "200": {
            "description": "Result",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PhoneResult"
                }
              }
            },
            "headers": {
              "x-ratelimit-limit": {
                "description": "Requests allowed per minute (600).",
                "schema": {
                  "type": "integer"
                }
              },
              "x-ratelimit-remaining": {
                "description": "Requests left in the current minute.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/score/domain": {
      "get": {
        "tags": [
          "Scoring"
        ],
        "operationId": "scoreDomain",
        "summary": "Score a domain",
        "description": "Resolution, MX, SPF/DMARC posture, registration age, disposable and risky-TLD checks, plus homograph detection for lookalike domains.",
        "parameters": [
          {
            "name": "domain",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string"
            },
            "example": "example.com"
          },
          {
            "$ref": "#/components/parameters/Strictness"
          }
        ],
        "responses": {
          "200": {
            "description": "Result",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DomainResult"
                }
              }
            },
            "headers": {
              "x-ratelimit-limit": {
                "description": "Requests allowed per minute (600).",
                "schema": {
                  "type": "integer"
                }
              },
              "x-ratelimit-remaining": {
                "description": "Requests left in the current minute.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/score/user": {
      "post": {
        "tags": [
          "Unified"
        ],
        "operationId": "scoreUser",
        "summary": "Score a whole signup in one call",
        "description": "Combines every signal you supply into a single risk score and verdict, with each component returned alongside. This is the endpoint most integrations use at registration or checkout.",
        "parameters": [
          {
            "$ref": "#/components/parameters/Strictness"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "description": "Supply any combination. Every field is optional, but at least one is required.",
                "properties": {
                  "ip": {
                    "type": "string",
                    "example": "185.220.101.5"
                  },
                  "email": {
                    "type": "string",
                    "format": "email",
                    "example": "test@mailinator.com"
                  },
                  "phone": {
                    "type": "string",
                    "example": "+14155552671"
                  },
                  "phone_country": {
                    "type": "string",
                    "minLength": 2,
                    "maxLength": 2,
                    "example": "US"
                  },
                  "domain": {
                    "type": "string",
                    "example": "example.com"
                  },
                  "device_id": {
                    "type": "string",
                    "description": "Fingerprint from fp.js."
                  },
                  "device_signals": {
                    "type": "object",
                    "description": "Optional browser characteristics from fp.js."
                  },
                  "device_automation": {
                    "type": "object",
                    "description": "Optional automation markers from fp.js."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Combined score",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/UserScore"
                }
              }
            },
            "headers": {
              "x-ratelimit-limit": {
                "description": "Requests allowed per minute (600).",
                "schema": {
                  "type": "integer"
                }
              },
              "x-ratelimit-remaining": {
                "description": "Requests left in the current minute.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      },
      "get": {
        "tags": [
          "Unified"
        ],
        "operationId": "scoreUserGet",
        "summary": "Score a signup via query parameters",
        "description": "Same as the POST form, for quick testing from a browser or curl.",
        "parameters": [
          {
            "name": "ip",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "email",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "email"
            }
          },
          {
            "name": "phone",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "phone_country",
            "in": "query",
            "schema": {
              "type": "string",
              "minLength": 2,
              "maxLength": 2
            }
          },
          {
            "name": "domain",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "device_id",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "$ref": "#/components/parameters/Strictness"
          }
        ],
        "responses": {
          "200": {
            "description": "Combined score",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/UserScore"
                }
              }
            },
            "headers": {
              "x-ratelimit-limit": {
                "description": "Requests allowed per minute (600).",
                "schema": {
                  "type": "integer"
                }
              },
              "x-ratelimit-remaining": {
                "description": "Requests left in the current minute.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/score/device": {
      "post": {
        "tags": [
          "Device"
        ],
        "operationId": "scoreDevice",
        "summary": "Score a device fingerprint",
        "description": "Reputation and bot probability for a browser fingerprint produced by https://www.layercall.com/fp.js.\n\nThis is the signal that survives an attacker rotating IPs and email addresses: a new VPN exit and a fresh disposable inbox are free, a new machine is not. The `device_id` is hashed in the browser and again before storage, so the stored value cannot be reversed to a device.\n\nPOST rather than GET because a device id in a query string ends up in access logs and Referer headers.",
        "parameters": [
          {
            "$ref": "#/components/parameters/Strictness"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "device_id"
                ],
                "properties": {
                  "device_id": {
                    "type": "string",
                    "description": "Hex fingerprint from LayerCall.fingerprint().",
                    "example": "ff48aa64a243c1b9e07d5c2f8a1b4e6d9c3f7a2b5e8d1c4f7a0b3e6d9c2f5a8b"
                  },
                  "ip": {
                    "type": "string",
                    "description": "Visitor IP. Optional \u2014 enables the timezone-vs-geolocation check.",
                    "example": "185.220.101.5"
                  },
                  "signals": {
                    "type": "object",
                    "description": "Optional browser characteristics from fp.js. Used for bot scoring only, never persisted.",
                    "properties": {
                      "user_agent": {
                        "type": "string"
                      },
                      "timezone": {
                        "type": "string",
                        "example": "Europe/Berlin"
                      },
                      "timezone_offset": {
                        "type": "integer"
                      },
                      "screen": {
                        "type": "string",
                        "example": "1920x1080x24"
                      },
                      "hardware_concurrency": {
                        "type": "integer"
                      },
                      "languages": {
                        "type": "string"
                      },
                      "webgl_renderer": {
                        "type": "string"
                      }
                    }
                  },
                  "automation": {
                    "type": "object",
                    "description": "Optional automation markers from fp.js.",
                    "properties": {
                      "webdriver": {
                        "type": "boolean",
                        "description": "navigator.webdriver was true."
                      },
                      "automation_globals": {
                        "type": "boolean",
                        "description": "Selenium/Puppeteer/Playwright globals present."
                      },
                      "headless_ua": {
                        "type": "boolean"
                      },
                      "zero_screen": {
                        "type": "boolean"
                      },
                      "no_plugins": {
                        "type": "boolean"
                      },
                      "no_languages": {
                        "type": "boolean"
                      }
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Device score",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DeviceScore"
                }
              }
            },
            "headers": {
              "x-ratelimit-limit": {
                "description": "Requests allowed per minute (600).",
                "schema": {
                  "type": "integer"
                }
              },
              "x-ratelimit-remaining": {
                "description": "Requests left in the current minute.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/batch": {
      "post": {
        "tags": [
          "Batch"
        ],
        "summary": "Score up to 500 values in one request",
        "description": "Bills one lookup per item actually scored; rejected items are not billed. More than 500 items is refused, never truncated.",
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "billable_lookups": {
                      "type": "integer",
                      "example": 2
                    },
                    "count": {
                      "type": "integer",
                      "example": 2
                    },
                    "failed": {
                      "type": "integer",
                      "example": 0
                    },
                    "processing_time_sec": {
                      "type": "number",
                      "example": 4.818
                    },
                    "results": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "abuse_reports": {
                            "type": "integer",
                            "example": 0
                          },
                          "cached": {
                            "type": "boolean",
                            "example": false
                          },
                          "first_seen": {
                            "nullable": true
                          },
                          "geo": {
                            "type": "object",
                            "properties": {
                              "asn": {
                                "type": "string",
                                "example": "AS15169"
                              },
                              "city": {
                                "type": "string",
                                "example": "Mountain View"
                              },
                              "country": {
                                "type": "string",
                                "example": "US"
                              },
                              "isp": {
                                "type": "string",
                                "example": "Google LLC"
                              }
                            }
                          },
                          "hijacked_source": {
                            "nullable": true
                          },
                          "input": {
                            "type": "string",
                            "example": "8.8.8.8"
                          },
                          "ip": {
                            "type": "string",
                            "example": "8.8.8.8"
                          },
                          "request_id": {
                            "type": "string",
                            "example": "req_66397ef9-c023-494f-80d9-6672121b535b"
                          },
                          "risk_score": {
                            "type": "integer",
                            "example": 30
                          },
                          "signals": {
                            "type": "object",
                            "properties": {
                              "is_datacenter": {
                                "type": "boolean",
                                "example": true
                              },
                              "is_hijacked_netblock": {
                                "type": "boolean",
                                "example": false
                              },
                              "is_proxy": {
                                "type": "boolean",
                                "example": false
                              },
                              "is_tor": {
                                "type": "boolean",
                                "example": false
                              },
                              "is_vpn": {
                                "type": "boolean",
                                "example": false
                              },
                              "recent_abuse": {
                                "type": "boolean",
                                "example": false
                              }
                            }
                          },
                          "times_seen": {
                            "type": "integer",
                            "example": 1
                          },
                          "verdict": {
                            "type": "string",
                            "example": "allow"
                          },
                          "vpn_provider": {
                            "nullable": true
                          }
                        }
                      }
                    },
                    "succeeded": {
                      "type": "integer",
                      "example": 2
                    },
                    "type": {
                      "type": "string",
                      "example": "ip"
                    }
                  }
                },
                "example": {
                  "billable_lookups": 2,
                  "count": 2,
                  "failed": 0,
                  "processing_time_sec": 4.818,
                  "results": [
                    {
                      "abuse_reports": 0,
                      "cached": false,
                      "first_seen": null,
                      "geo": {
                        "asn": "AS15169",
                        "city": "Mountain View",
                        "country": "US",
                        "isp": "Google LLC"
                      },
                      "hijacked_source": null,
                      "input": "8.8.8.8",
                      "ip": "8.8.8.8",
                      "request_id": "req_66397ef9-c023-494f-80d9-6672121b535b",
                      "risk_score": 30,
                      "signals": {
                        "is_datacenter": true,
                        "is_hijacked_netblock": false,
                        "is_proxy": false,
                        "is_tor": false,
                        "is_vpn": false,
                        "recent_abuse": false
                      },
                      "times_seen": 1,
                      "verdict": "allow",
                      "vpn_provider": null
                    }
                  ],
                  "succeeded": 2,
                  "type": "ip"
                }
              }
            },
            "headers": {
              "x-ratelimit-limit": {
                "description": "Requests allowed per minute (600).",
                "schema": {
                  "type": "integer"
                }
              },
              "x-ratelimit-remaining": {
                "description": "Requests left in the current minute.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "400": {
            "description": "Invalid input"
          },
          "402": {
            "description": "Quota exceeded, or account paused after a failed payment"
          },
          "403": {
            "description": "Invalid or revoked API key"
          },
          "429": {
            "description": "Rate limited \u2014 honour Retry-After"
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "type",
                  "items"
                ],
                "properties": {
                  "type": {
                    "type": "string",
                    "enum": [
                      "ip",
                      "email",
                      "domain",
                      "phone"
                    ]
                  },
                  "items": {
                    "type": "array",
                    "maxItems": 500,
                    "items": {
                      "type": "string"
                    }
                  },
                  "strictness": {
                    "type": "integer",
                    "minimum": 0,
                    "maximum": 3
                  },
                  "phone_country": {
                    "type": "string"
                  }
                }
              }
            }
          }
        }
      }
    },
    "/v1/report": {
      "post": {
        "tags": [
          "Reputation"
        ],
        "summary": "Report a confirmed-fraud value",
        "description": "Feeds our own reputation network. Counts as one lookup per value. Test-mode keys are refused so fixtures never reach shared data.",
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "processing_time_sec": {
                      "type": "number",
                      "example": 0.218
                    },
                    "reported": {
                      "type": "integer",
                      "example": 1
                    },
                    "request_id": {
                      "type": "string",
                      "example": "req_1c20ed09-ae37-4432-b5ba-ee52b8580738"
                    },
                    "results": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "abuse_reports": {
                            "type": "integer",
                            "example": 1
                          },
                          "kind": {
                            "type": "string",
                            "example": "ip"
                          }
                        }
                      }
                    },
                    "skipped": {
                      "type": "array"
                    }
                  }
                },
                "example": {
                  "processing_time_sec": 0.218,
                  "reported": 1,
                  "request_id": "req_1c20ed09-ae37-4432-b5ba-ee52b8580738",
                  "results": [
                    {
                      "abuse_reports": 1,
                      "kind": "ip"
                    }
                  ],
                  "skipped": []
                }
              }
            },
            "headers": {
              "x-ratelimit-limit": {
                "description": "Requests allowed per minute (600).",
                "schema": {
                  "type": "integer"
                }
              },
              "x-ratelimit-remaining": {
                "description": "Requests left in the current minute.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "400": {
            "description": "Invalid input"
          },
          "402": {
            "description": "Quota exceeded, or account paused after a failed payment"
          },
          "403": {
            "description": "Invalid or revoked API key"
          },
          "429": {
            "description": "Rate limited \u2014 honour Retry-After"
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "kind"
                ],
                "properties": {
                  "kind": {
                    "type": "string",
                    "enum": [
                      "ip",
                      "email",
                      "domain",
                      "phone"
                    ]
                  },
                  "value": {
                    "type": "string"
                  },
                  "values": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/v1/outcome": {
      "post": {
        "tags": [
          "Reputation"
        ],
        "summary": "Label a past score as fraud or legitimate",
        "description": "Closes the feedback loop using the request_id from any scoring call. Free \u2014 not metered.",
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "not_found": {
                      "type": "integer",
                      "example": 0
                    },
                    "processing_time_sec": {
                      "type": "number",
                      "example": 0.048
                    },
                    "recorded": {
                      "type": "integer",
                      "example": 1
                    },
                    "request_id": {
                      "type": "string",
                      "example": "req_a9b7f2a2-27e2-42aa-88ec-3dcf8e969123"
                    },
                    "results": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "disagreement": {
                            "type": "boolean",
                            "example": false
                          },
                          "our_score": {
                            "type": "integer",
                            "example": 40
                          },
                          "our_verdict": {
                            "type": "string",
                            "example": "review"
                          },
                          "request_id": {
                            "type": "string",
                            "example": "req_b3b102ac-7be9-4d47-a40f-0e125957f823"
                          },
                          "status": {
                            "type": "string",
                            "example": "recorded"
                          }
                        }
                      }
                    }
                  }
                },
                "example": {
                  "not_found": 0,
                  "processing_time_sec": 0.048,
                  "recorded": 1,
                  "request_id": "req_a9b7f2a2-27e2-42aa-88ec-3dcf8e969123",
                  "results": [
                    {
                      "disagreement": false,
                      "our_score": 40,
                      "our_verdict": "review",
                      "request_id": "req_b3b102ac-7be9-4d47-a40f-0e125957f823",
                      "status": "recorded"
                    }
                  ]
                }
              }
            },
            "headers": {
              "x-ratelimit-limit": {
                "description": "Requests allowed per minute (600).",
                "schema": {
                  "type": "integer"
                }
              },
              "x-ratelimit-remaining": {
                "description": "Requests left in the current minute.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "400": {
            "description": "Invalid input"
          },
          "402": {
            "description": "Quota exceeded, or account paused after a failed payment"
          },
          "403": {
            "description": "Invalid or revoked API key"
          },
          "429": {
            "description": "Rate limited \u2014 honour Retry-After"
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "request_id",
                  "outcome"
                ],
                "properties": {
                  "request_id": {
                    "type": "string"
                  },
                  "outcome": {
                    "type": "string",
                    "enum": [
                      "fraud",
                      "legit"
                    ]
                  }
                }
              }
            }
          }
        }
      }
    },
    "/v1/verify/agent": {
      "post": {
        "tags": [
          "AI agents"
        ],
        "summary": "Verify an AI agent's HTTP Message Signature",
        "description": "RFC 9421 / Web Bot Auth. Proves which agent sent a request, rather than inferring it from a User-Agent.",
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "agent": {
                      "nullable": true
                    },
                    "docs": {
                      "type": "string",
                      "example": "https://www.layercall.com/docs/agents"
                    },
                    "expires_in": {
                      "nullable": true
                    },
                    "keyid": {
                      "type": "string",
                      "example": "probe"
                    },
                    "processing_time_sec": {
                      "type": "number",
                      "example": 0.214
                    },
                    "purpose": {
                      "nullable": true
                    },
                    "reason": {
                      "type": "string",
                      "example": "not_web_bot_auth"
                    },
                    "replay_protection": {
                      "type": "string",
                      "example": "signature-window"
                    },
                    "request_id": {
                      "type": "string",
                      "example": "req_5909ce2f-ea06-4388-b692-5abe07fc351d"
                    },
                    "verified": {
                      "type": "boolean",
                      "example": false
                    }
                  }
                },
                "example": {
                  "agent": null,
                  "docs": "https://www.layercall.com/docs/agents",
                  "expires_in": null,
                  "keyid": "probe",
                  "processing_time_sec": 0.214,
                  "purpose": null,
                  "reason": "not_web_bot_auth",
                  "replay_protection": "signature-window",
                  "request_id": "req_5909ce2f-ea06-4388-b692-5abe07fc351d",
                  "verified": false
                }
              }
            },
            "headers": {
              "x-ratelimit-limit": {
                "description": "Requests allowed per minute (600).",
                "schema": {
                  "type": "integer"
                }
              },
              "x-ratelimit-remaining": {
                "description": "Requests left in the current minute.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "400": {
            "description": "Invalid input"
          },
          "402": {
            "description": "Quota exceeded, or account paused after a failed payment"
          },
          "403": {
            "description": "Invalid or revoked API key"
          },
          "429": {
            "description": "Rate limited \u2014 honour Retry-After"
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "url",
                  "headers"
                ],
                "properties": {
                  "url": {
                    "type": "string"
                  },
                  "method": {
                    "type": "string"
                  },
                  "headers": {
                    "type": "object",
                    "additionalProperties": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/v1/agent/authorize": {
      "post": {
        "tags": [
          "AI agents"
        ],
        "summary": "Decide whether to allow an agent request",
        "description": "Applies your agent policy to a verified signature and returns allow, review or deny.",
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "agent": {
                      "type": "object",
                      "properties": {
                        "expires_in": {
                          "nullable": true
                        },
                        "host": {
                          "nullable": true
                        },
                        "name": {
                          "nullable": true
                        },
                        "publishes_card": {
                          "type": "boolean",
                          "example": false
                        },
                        "purpose": {
                          "nullable": true
                        },
                        "rate_expectation": {
                          "nullable": true
                        },
                        "signature_reason": {
                          "type": "string",
                          "example": "not_web_bot_auth"
                        },
                        "trigger": {
                          "nullable": true
                        },
                        "verified": {
                          "type": "boolean",
                          "example": false
                        }
                      }
                    },
                    "decision": {
                      "type": "string",
                      "example": "deny"
                    },
                    "docs": {
                      "type": "string",
                      "example": "https://www.layercall.com/docs/agents"
                    },
                    "matched": {
                      "type": "string",
                      "example": "default:signature_failed"
                    },
                    "processing_time_sec": {
                      "type": "number",
                      "example": 0.072
                    },
                    "reason": {
                      "type": "string",
                      "example": "A Signature-Agent header was present but did not verify. An unverifiable claim of identity is worth less than no claim at all."
                    },
                    "request_id": {
                      "type": "string",
                      "example": "req_1afc9f14-aef6-49cd-8524-f76fae58fbd2"
                    },
                    "requests_last_hour": {
                      "nullable": true
                    }
                  }
                },
                "example": {
                  "agent": {
                    "expires_in": null,
                    "host": null,
                    "name": null,
                    "publishes_card": false,
                    "purpose": null,
                    "rate_expectation": null,
                    "signature_reason": "not_web_bot_auth",
                    "trigger": null,
                    "verified": false
                  },
                  "decision": "deny",
                  "docs": "https://www.layercall.com/docs/agents",
                  "matched": "default:signature_failed",
                  "processing_time_sec": 0.072,
                  "reason": "A Signature-Agent header was present but did not verify. An unverifiable claim of identity is worth less than no claim at all.",
                  "request_id": "req_1afc9f14-aef6-49cd-8524-f76fae58fbd2",
                  "requests_last_hour": null
                }
              }
            },
            "headers": {
              "x-ratelimit-limit": {
                "description": "Requests allowed per minute (600).",
                "schema": {
                  "type": "integer"
                }
              },
              "x-ratelimit-remaining": {
                "description": "Requests left in the current minute.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "400": {
            "description": "Invalid input"
          },
          "402": {
            "description": "Quota exceeded, or account paused after a failed payment"
          },
          "403": {
            "description": "Invalid or revoked API key"
          },
          "429": {
            "description": "Rate limited \u2014 honour Retry-After"
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "url",
                  "headers"
                ],
                "properties": {
                  "url": {
                    "type": "string"
                  },
                  "method": {
                    "type": "string"
                  },
                  "headers": {
                    "type": "object",
                    "additionalProperties": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/v1/agent/policy": {
      "get": {
        "tags": [
          "AI agents"
        ],
        "summary": "Read your agent policy",
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "defaults_apply_when_no_rule_matches": {
                      "type": "boolean",
                      "example": true
                    },
                    "docs": {
                      "type": "string",
                      "example": "https://www.layercall.com/docs/agents"
                    },
                    "processing_time_sec": {
                      "type": "number",
                      "example": 0.331
                    },
                    "request_id": {
                      "type": "string",
                      "example": "req_5d422a53-dd81-471f-9a22-56f646234324"
                    },
                    "rules": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "action": {
                            "type": "string",
                            "example": "deny"
                          },
                          "methods": {
                            "type": "array",
                            "items": {
                              "type": "string",
                              "example": "POST"
                            }
                          },
                          "reason": {
                            "type": "string",
                            "example": "Write on nobody's behalf"
                          },
                          "trigger": {
                            "type": "string",
                            "example": "crawler"
                          }
                        }
                      }
                    },
                    "updated_at": {
                      "type": "string",
                      "example": "2026-08-06T23:44:16.908+00:00"
                    }
                  }
                },
                "example": {
                  "defaults_apply_when_no_rule_matches": true,
                  "docs": "https://www.layercall.com/docs/agents",
                  "processing_time_sec": 0.331,
                  "request_id": "req_5d422a53-dd81-471f-9a22-56f646234324",
                  "rules": [
                    {
                      "action": "deny",
                      "methods": [
                        "POST"
                      ],
                      "reason": "Write on nobody's behalf",
                      "trigger": "crawler"
                    },
                    {
                      "action": "allow",
                      "reason": "A person asked for this",
                      "trigger": "fetcher"
                    }
                  ],
                  "updated_at": "2026-08-06T23:44:16.908+00:00"
                }
              }
            },
            "headers": {
              "x-ratelimit-limit": {
                "description": "Requests allowed per minute (600).",
                "schema": {
                  "type": "integer"
                }
              },
              "x-ratelimit-remaining": {
                "description": "Requests left in the current minute.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          }
        }
      },
      "put": {
        "tags": [
          "AI agents"
        ],
        "summary": "Replace your agent policy",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "rules"
                ],
                "properties": {
                  "rules": {
                    "type": "array",
                    "items": {
                      "type": "object"
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "note": {
                      "type": "string",
                      "example": "Rules are evaluated in order; the first match wins. Defaults apply when none match."
                    },
                    "rules": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "action": {
                            "type": "string",
                            "example": "deny"
                          },
                          "methods": {
                            "type": "array",
                            "items": {
                              "type": "string",
                              "example": "POST"
                            }
                          },
                          "reason": {
                            "type": "string",
                            "example": "Write on nobody's behalf"
                          },
                          "trigger": {
                            "type": "string",
                            "example": "crawler"
                          }
                        }
                      }
                    },
                    "saved": {
                      "type": "integer",
                      "example": 2
                    }
                  }
                },
                "example": {
                  "note": "Rules are evaluated in order; the first match wins. Defaults apply when none match.",
                  "rules": [
                    {
                      "action": "deny",
                      "methods": [
                        "POST"
                      ],
                      "reason": "Write on nobody's behalf",
                      "trigger": "crawler"
                    }
                  ],
                  "saved": 2
                }
              }
            },
            "headers": {
              "x-ratelimit-limit": {
                "description": "Requests allowed per minute (600).",
                "schema": {
                  "type": "integer"
                }
              },
              "x-ratelimit-remaining": {
                "description": "Requests left in the current minute.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          }
        }
      }
    },
    "/v1/rules": {
      "get": {
        "tags": [
          "Rules"
        ],
        "summary": "List your allow/block rules",
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "count": {
                      "type": "integer",
                      "example": 1
                    },
                    "processing_time_sec": {
                      "type": "number",
                      "example": 0.183
                    },
                    "request_id": {
                      "type": "string",
                      "example": "req_f0beb003-675e-46b1-aae9-9ee1a02bd2c6"
                    },
                    "rules": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "action": {
                            "type": "string",
                            "example": "block"
                          },
                          "created_at": {
                            "type": "string",
                            "example": "2026-08-06T23:43:32.323+00:00"
                          },
                          "id": {
                            "type": "string",
                            "example": "11111111-2222-3333-4444-555555555555"
                          },
                          "kind": {
                            "type": "string",
                            "example": "domain"
                          },
                          "value": {
                            "type": "string",
                            "example": "mailinator.com"
                          }
                        }
                      }
                    }
                  }
                },
                "example": {
                  "count": 1,
                  "processing_time_sec": 0.183,
                  "request_id": "req_f0beb003-675e-46b1-aae9-9ee1a02bd2c6",
                  "rules": [
                    {
                      "action": "block",
                      "created_at": "2026-08-06T23:43:32.323+00:00",
                      "id": "11111111-2222-3333-4444-555555555555",
                      "kind": "domain",
                      "value": "mailinator.com"
                    }
                  ]
                }
              }
            },
            "headers": {
              "x-ratelimit-limit": {
                "description": "Requests allowed per minute (600).",
                "schema": {
                  "type": "integer"
                }
              },
              "x-ratelimit-remaining": {
                "description": "Requests left in the current minute.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          }
        }
      },
      "post": {
        "tags": [
          "Rules"
        ],
        "summary": "Create a rule",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "added": {
                      "type": "integer",
                      "example": 1
                    },
                    "rules": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "action": {
                            "type": "string",
                            "example": "block"
                          },
                          "created_at": {
                            "type": "string",
                            "example": "2026-08-06T23:43:32.323+00:00"
                          },
                          "id": {
                            "type": "string",
                            "example": "11111111-2222-3333-4444-555555555555"
                          },
                          "kind": {
                            "type": "string",
                            "example": "domain"
                          },
                          "value": {
                            "type": "string",
                            "example": "mailinator.com"
                          }
                        }
                      }
                    },
                    "skipped": {
                      "type": "array"
                    }
                  }
                },
                "example": {
                  "added": 1,
                  "rules": [
                    {
                      "action": "block",
                      "created_at": "2026-08-06T23:43:32.323+00:00",
                      "id": "11111111-2222-3333-4444-555555555555",
                      "kind": "domain",
                      "value": "mailinator.com"
                    }
                  ],
                  "skipped": []
                }
              }
            },
            "headers": {
              "x-ratelimit-limit": {
                "description": "Requests allowed per minute (600).",
                "schema": {
                  "type": "integer"
                }
              },
              "x-ratelimit-remaining": {
                "description": "Requests left in the current minute.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          }
        }
      },
      "delete": {
        "tags": [
          "Rules"
        ],
        "summary": "Delete every rule",
        "description": "Requires ?confirm=true.",
        "parameters": [
          {
            "name": "confirm",
            "in": "query",
            "required": true,
            "schema": {
              "type": "boolean"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "deleted": {
                      "type": "integer",
                      "example": 11
                    }
                  }
                },
                "example": {
                  "deleted": 11
                }
              }
            },
            "headers": {
              "x-ratelimit-limit": {
                "description": "Requests allowed per minute (600).",
                "schema": {
                  "type": "integer"
                }
              },
              "x-ratelimit-remaining": {
                "description": "Requests left in the current minute.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          }
        }
      }
    },
    "/v1/rules/{id}": {
      "delete": {
        "tags": [
          "Rules"
        ],
        "summary": "Delete one rule",
        "description": "Removes a single allow/block rule by id. The coarse operation \u2014 DELETE /v1/rules?confirm=true, which removes EVERY rule \u2014 was in this spec and this precise one was not, so a generated client got clearRules and no deleteRule. Returns 404 when the id is a well-formed uuid that belongs to no rule on your account, and 400 when it is not a uuid at all.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The rule id, as returned by GET /v1/rules.",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The rule was deleted.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "deleted": {
                      "type": "boolean",
                      "example": true
                    },
                    "id": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "request_id": {
                      "type": "string",
                      "example": "req_8f2c1e90"
                    }
                  }
                },
                "example": {
                  "deleted": true,
                  "id": "11111111-2222-3333-4444-555555555555",
                  "request_id": "req_8f2c1e90"
                }
              }
            },
            "headers": {
              "x-ratelimit-limit": {
                "description": "Requests allowed per minute (600).",
                "schema": {
                  "type": "integer"
                }
              },
              "x-ratelimit-remaining": {
                "description": "Requests left in the current minute.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "400": {
            "description": "The id is not a uuid.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No rule with that id on this account.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/rules/import": {
      "post": {
        "tags": [
          "Rules"
        ],
        "summary": "Bulk-import rules",
        "description": "Up to 10,000 rules in one request.",
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "action": {
                      "type": "string",
                      "example": "merge"
                    },
                    "by_kind": {
                      "type": "object",
                      "properties": {
                        "domain": {
                          "type": "integer",
                          "example": 3
                        }
                      }
                    },
                    "duplicates": {
                      "type": "integer",
                      "example": 0
                    },
                    "duplicates_in_file": {
                      "type": "integer",
                      "example": 0
                    },
                    "imported": {
                      "type": "integer",
                      "example": 3
                    },
                    "skipped": {
                      "type": "array"
                    }
                  }
                },
                "example": {
                  "action": "merge",
                  "by_kind": {
                    "domain": 3
                  },
                  "duplicates": 0,
                  "duplicates_in_file": 0,
                  "imported": 3,
                  "skipped": []
                }
              }
            },
            "headers": {
              "x-ratelimit-limit": {
                "description": "Requests allowed per minute (600).",
                "schema": {
                  "type": "integer"
                }
              },
              "x-ratelimit-remaining": {
                "description": "Requests left in the current minute.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "400": {
            "description": "Invalid input"
          },
          "402": {
            "description": "Quota exceeded, or account paused after a failed payment"
          },
          "403": {
            "description": "Invalid or revoked API key"
          },
          "429": {
            "description": "Rate limited \u2014 honour Retry-After"
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "rules"
                ],
                "properties": {
                  "rules": {
                    "type": "array",
                    "items": {
                      "type": "object"
                    }
                  }
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "ApiKeyAuth": {
        "type": "apiKey",
        "in": "header",
        "name": "x-api-key",
        "description": "Your API key from https://www.layercall.com/get-key"
      },
      "BearerAuth": {
        "type": "http",
        "scheme": "bearer",
        "description": "Same key, sent as `Authorization: Bearer <key>`."
      }
    },
    "parameters": {
      "Strictness": {
        "name": "strictness",
        "in": "query",
        "required": false,
        "schema": {
          "type": "string",
          "enum": [
            "lenient",
            "balanced",
            "strict"
          ],
          "default": "balanced"
        },
        "description": "Shifts the allow/review/block thresholds. Signals and risk_score are unchanged."
      }
    },
    "responses": {
      "BadRequest": {
        "description": "Missing or malformed input.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "Unauthorized": {
        "description": "Missing, invalid, or revoked API key.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "RateLimited": {
        "description": "Monthly quota or spend cap reached, or the 600 requests-per-minute rate limit exceeded.",
        "headers": {
          "Retry-After": {
            "description": "Seconds to wait before retrying.",
            "schema": {
              "type": "integer"
            }
          },
          "x-ratelimit-limit": {
            "description": "Requests allowed per minute (600).",
            "schema": {
              "type": "integer"
            }
          },
          "x-ratelimit-remaining": {
            "description": "Requests left in the current minute.",
            "schema": {
              "type": "integer"
            }
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      }
    },
    "schemas": {
      "Verdict": {
        "type": "string",
        "enum": [
          "allow",
          "review",
          "block"
        ],
        "description": "Recommended action. Thresholds move with `strictness`."
      },
      "RiskScore": {
        "type": "integer",
        "minimum": 0,
        "maximum": 100,
        "description": "0 = clean, 100 = certain fraud."
      },
      "Error": {
        "type": "object",
        "required": [
          "error"
        ],
        "properties": {
          "error": {
            "type": "string",
            "description": "Human-readable message, safe to log."
          }
        }
      },
      "IpScore": {
        "type": "object",
        "required": [
          "ip",
          "risk_score",
          "verdict",
          "signals",
          "geo",
          "cached",
          "request_id"
        ],
        "properties": {
          "ip": {
            "type": "string"
          },
          "risk_score": {
            "$ref": "#/components/schemas/RiskScore"
          },
          "verdict": {
            "$ref": "#/components/schemas/Verdict"
          },
          "signals": {
            "type": "object",
            "properties": {
              "is_vpn": {
                "type": "boolean"
              },
              "is_proxy": {
                "type": "boolean"
              },
              "is_datacenter": {
                "type": "boolean"
              },
              "is_tor": {
                "type": "boolean"
              },
              "recent_abuse": {
                "type": "boolean"
              },
              "is_private_or_reserved": {
                "type": "boolean",
                "description": "RFC1918 / loopback / reserved. Cannot be meaningfully scored."
              }
            }
          },
          "geo": {
            "type": "object",
            "properties": {
              "country": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "ISO 3166-1 alpha-2."
              },
              "city": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "asn": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "isp": {
                "type": [
                  "string",
                  "null"
                ]
              }
            }
          },
          "first_seen": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "First time this value was seen across the LayerCall network."
          },
          "times_seen": {
            "type": "integer"
          },
          "abuse_reports": {
            "type": "integer",
            "description": "Confirmed-fraud reports from distinct accounts."
          },
          "cached": {
            "type": "boolean",
            "description": "Served from cache. Cached lookups are never billed."
          },
          "request_id": {
            "type": "string"
          },
          "warning": {
            "type": "string",
            "description": "Present only when the input needs attention."
          },
          "vpn_provider": {
            "type": [
              "string",
              "null"
            ],
            "description": "Name of the VPN operating this exit node (e.g. \"NordVPN\", \"Mullvad\", \"Surfshark\"), taken from the operator's own published server list. null when is_vpn was inferred from an ASN or third-party feed rather than confirmed by the operator \u2014 absence is not evidence the IP is not a VPN.",
            "example": "NordVPN"
          },
          "signals_unavailable": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Signals that could not be measured on this request \u2014 e.g. [\"tor\", \"vpn_providers\"]. Absent entirely when every feed answered, so the happy-path shape is unchanged. Present means the score was built on incomplete data: a \"clean\" answer and a \"we could not check\" answer are otherwise identical."
          },
          "hijacked_source": {
            "nullable": true
          },
          "processing_time_sec": {
            "type": "number",
            "example": 0.261
          },
          "billable_lookups": {
            "type": "integer",
            "example": 1
          }
        }
      },
      "EmailResult": {
        "type": "object",
        "required": [
          "email",
          "risk_score",
          "verdict"
        ],
        "properties": {
          "email": {
            "type": "string"
          },
          "normalized_email": {
            "type": "string"
          },
          "risk_score": {
            "$ref": "#/components/schemas/RiskScore"
          },
          "verdict": {
            "$ref": "#/components/schemas/Verdict"
          },
          "status": {
            "type": "string",
            "description": "e.g. valid, invalid, do_not_mail, unknown."
          },
          "sub_status": {
            "type": [
              "string",
              "null"
            ],
            "description": "e.g. disposable, role_based, mailbox_not_found."
          },
          "deliverability_score": {
            "type": "integer",
            "minimum": 0,
            "maximum": 100
          },
          "did_you_mean": {
            "type": [
              "string",
              "null"
            ],
            "description": "Suggested correction for a likely typo."
          },
          "signals": {
            "type": "object",
            "properties": {
              "syntax_valid": {
                "type": "boolean"
              },
              "mx_found": {
                "type": "boolean"
              },
              "is_disposable": {
                "type": "boolean"
              },
              "is_homograph": {
                "type": "boolean",
                "description": "Lookalike domain \u2014 mixed script or a spelling that folds onto a real ASCII domain."
              },
              "is_role_account": {
                "type": "boolean"
              },
              "is_free_provider": {
                "type": "boolean"
              }
            }
          },
          "cached": {
            "type": "boolean"
          },
          "request_id": {
            "type": "string"
          },
          "signals_unavailable": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Signals that could not be measured on this request \u2014 e.g. [\"tor\", \"vpn_providers\"]. Absent entirely when every feed answered, so the happy-path shape is unchanged. Present means the score was built on incomplete data: a \"clean\" answer and a \"we could not check\" answer are otherwise identical."
          },
          "domain": {
            "type": "string",
            "example": "gmail.com"
          },
          "domain_age_days": {
            "nullable": true
          },
          "mx_provider": {
            "type": "string",
            "example": "Google Workspace"
          },
          "mx_records": {
            "type": "array",
            "items": {
              "type": "string",
              "example": "gmail-smtp-in.l.google.com."
            }
          },
          "first_seen": {
            "nullable": true
          },
          "times_seen": {
            "type": "integer",
            "example": 1
          },
          "abuse_reports": {
            "type": "integer",
            "example": 0
          },
          "digital_footprint": {
            "type": "object",
            "properties": {
              "has_gravatar": {
                "type": [
                  "boolean",
                  "null"
                ],
                "example": false,
                "description": "Whether the address has a public Gravatar. null means the check did not run \u2014 the request's time budget was exhausted \u2014 which is not the same as no avatar."
              },
              "gravatar_profile_url": {
                "nullable": true
              },
              "breach_count": {
                "nullable": true
              },
              "seen_in_breach": {
                "nullable": true
              }
            }
          },
          "processing_time_sec": {
            "type": "number",
            "example": 0.097
          },
          "billable_lookups": {
            "type": "integer",
            "example": 1
          }
        }
      },
      "PhoneResult": {
        "type": "object",
        "required": [
          "phone",
          "risk_score",
          "verdict"
        ],
        "properties": {
          "phone": {
            "type": "string"
          },
          "risk_score": {
            "$ref": "#/components/schemas/RiskScore"
          },
          "verdict": {
            "$ref": "#/components/schemas/Verdict"
          },
          "parse_status": {
            "type": "string",
            "enum": [
              "ok",
              "country_required",
              "impossible",
              "invalid_pattern"
            ],
            "description": "Distinguishes 'you omitted country' from 'this number is bad'."
          },
          "signals": {
            "type": "object",
            "properties": {
              "syntax_valid": {
                "type": "boolean"
              },
              "is_possible": {
                "type": "boolean"
              },
              "is_voip": {
                "type": [
                  "boolean",
                  "null"
                ],
                "description": "In a range the national plan reserves for VoIP. null where the plan has no VoIP range (+1 US, India, Germany and others) or the number is only fixed-line-or-mobile: unknown, not no."
              },
              "is_premium_rate": {
                "type": "boolean"
              },
              "is_toll_free": {
                "type": "boolean"
              },
              "assigned_area_code": {
                "type": [
                  "boolean",
                  "null"
                ],
                "description": "NANP only. null when not applicable."
              }
            }
          },
          "number": {
            "type": "object",
            "properties": {
              "e164": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "country": {
                "type": [
                  "string",
                  "null"
                ]
              }
            }
          },
          "cached": {
            "type": "boolean"
          },
          "request_id": {
            "type": "string"
          },
          "processing_time_sec": {
            "type": "number",
            "example": 0.116
          },
          "first_seen": {
            "nullable": true
          },
          "times_seen": {
            "type": "integer",
            "example": 1
          },
          "abuse_reports": {
            "type": "integer",
            "example": 0
          },
          "billable_lookups": {
            "type": "integer",
            "example": 1
          }
        }
      },
      "DomainResult": {
        "type": "object",
        "required": [
          "domain",
          "risk_score",
          "verdict"
        ],
        "properties": {
          "domain": {
            "type": "string"
          },
          "risk_score": {
            "$ref": "#/components/schemas/RiskScore"
          },
          "verdict": {
            "$ref": "#/components/schemas/Verdict"
          },
          "signals": {
            "type": "object",
            "properties": {
              "resolves": {
                "type": "boolean"
              },
              "mx_found": {
                "type": "boolean"
              },
              "has_spf": {
                "type": "boolean"
              },
              "has_dmarc": {
                "type": "boolean"
              },
              "is_disposable": {
                "type": "boolean"
              },
              "is_homograph": {
                "type": "boolean"
              },
              "is_free_provider": {
                "type": "boolean"
              },
              "is_risky_tld": {
                "type": "boolean"
              },
              "newly_registered": {
                "type": [
                  "boolean",
                  "null"
                ],
                "description": "Registered less than 30 days ago. null means the registration date could not be determined (RDAP unreachable, or the TLD publishes no RDAP) \u2014 it does NOT mean the domain is established. Treat null as unknown, not as a negative finding."
              }
            }
          },
          "registration": {
            "type": "object",
            "properties": {
              "created_at": {
                "type": [
                  "string",
                  "null"
                ],
                "format": "date-time"
              }
            }
          },
          "cached": {
            "type": "boolean"
          },
          "request_id": {
            "type": "string"
          },
          "signals_unavailable": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Signals that could not be measured on this request \u2014 e.g. [\"tor\", \"vpn_providers\"]. Absent entirely when every feed answered, so the happy-path shape is unchanged. Present means the score was built on incomplete data: a \"clean\" answer and a \"we could not check\" answer are otherwise identical."
          },
          "processing_time_sec": {
            "type": "number",
            "example": 0.094
          },
          "first_seen": {
            "nullable": true
          },
          "times_seen": {
            "type": "integer",
            "example": 1
          },
          "abuse_reports": {
            "type": "integer",
            "example": 0
          },
          "billable_lookups": {
            "type": "integer",
            "example": 1
          }
        }
      },
      "UserScore": {
        "type": "object",
        "required": [
          "risk_score",
          "verdict",
          "components"
        ],
        "properties": {
          "risk_score": {
            "$ref": "#/components/schemas/RiskScore"
          },
          "verdict": {
            "$ref": "#/components/schemas/Verdict"
          },
          "components": {
            "type": "object",
            "description": "Each supplied signal, scored independently. Absent keys were not supplied.",
            "properties": {
              "ip": {
                "$ref": "#/components/schemas/IpScore"
              },
              "email": {
                "$ref": "#/components/schemas/EmailResult"
              },
              "phone": {
                "$ref": "#/components/schemas/PhoneResult"
              },
              "domain": {
                "$ref": "#/components/schemas/DomainResult"
              },
              "device": {
                "$ref": "#/components/schemas/DeviceScore"
              }
            }
          },
          "request_id": {
            "type": "string"
          },
          "signals_unavailable": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Signals that could not be measured on this request \u2014 e.g. [\"tor\", \"vpn_providers\"]. Absent entirely when every feed answered, so the happy-path shape is unchanged. Present means the score was built on incomplete data: a \"clean\" answer and a \"we could not check\" answer are otherwise identical."
          },
          "summary": {
            "type": "string",
            "example": "Allowed (34/100) \u2014 Datacenter ASN and machine-generated handle, not enough to hold it."
          },
          "components_checked": {
            "type": "array",
            "items": {
              "type": "string",
              "example": "email"
            }
          },
          "linkage": {
            "type": "object",
            "properties": {
              "device_email_count": {
                "nullable": true
              },
              "email_device_count": {
                "type": "integer",
                "example": 0
              },
              "email_ip_count": {
                "type": "integer",
                "example": 1
              },
              "subnet_rate_1h": {
                "type": "integer",
                "example": 17
              },
              "domain_rate_1h": {
                "type": "integer",
                "example": 14
              }
            }
          },
          "actor": {
            "type": "object",
            "properties": {
              "type": {
                "type": "string",
                "example": "unknown"
              },
              "proven": {
                "type": "boolean",
                "example": false
              },
              "basis": {
                "type": "string",
                "example": "none"
              },
              "operator": {
                "nullable": true
              },
              "trigger": {
                "nullable": true
              },
              "detail": {
                "type": "string",
                "example": "No signature and no device fingerprint. Drop fp.js on the page, or pass the agent's signed request, to get an answer here."
              }
            }
          },
          "top_signals": {
            "type": "array",
            "items": {
              "type": "string",
              "example": "ip: datacenter asn"
            }
          },
          "processing_time_sec": {
            "type": "number",
            "example": 0.033
          },
          "billable_lookups": {
            "type": "integer",
            "example": 0
          }
        }
      },
      "DeviceScore": {
        "type": "object",
        "required": [
          "device_id",
          "risk_score",
          "verdict",
          "bot_probability",
          "signals"
        ],
        "properties": {
          "device_id": {
            "type": "string"
          },
          "risk_score": {
            "$ref": "#/components/schemas/RiskScore"
          },
          "verdict": {
            "$ref": "#/components/schemas/Verdict"
          },
          "bot_probability": {
            "type": "integer",
            "minimum": 0,
            "maximum": 100,
            "description": "Reported separately from risk: plenty of automated traffic is legitimate."
          },
          "signals": {
            "type": "object",
            "properties": {
              "is_bot": {
                "type": "boolean",
                "description": "bot_probability >= 60."
              },
              "is_automated": {
                "type": "boolean"
              },
              "is_headless": {
                "type": "boolean"
              },
              "timezone_mismatch": {
                "type": [
                  "boolean",
                  "null"
                ],
                "description": "Browser timezone country differs from the IP's. Silent (false) when either is genuinely unknown; null when the country could NOT be resolved \u2014 a GeoIP outage \u2014 so no comparison was made."
              },
              "repeat_device": {
                "type": "boolean",
                "description": "Seen before across the LayerCall network."
              }
            }
          },
          "first_seen": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "times_seen": {
            "type": "integer"
          },
          "abuse_reports": {
            "type": "integer"
          },
          "request_id": {
            "type": "string",
            "example": "req_9319c108-cc66-4cbf-af64-37729a17c855"
          },
          "processing_time_sec": {
            "type": "number",
            "example": 0.031
          },
          "billable_lookups": {
            "type": "integer",
            "example": 1
          }
        }
      }
    }
  }
}
