{
  "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.",
    "contact": {
      "name": "LayerCall Support",
      "email": "support@layercall.com",
      "url": "https://www.layercall.com/contact"
    },
    "license": {
      "name": "Proprietary",
      "url": "https://www.layercall.com/terms"
    }
  },
  "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"
                }
              }
            }
          },
          "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"
                }
              }
            }
          },
          "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"
                }
              }
            }
          },
          "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"
                }
              }
            }
          },
          "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"
                }
              }
            }
          },
          "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"
                }
              }
            }
          },
          "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"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    }
  },
  "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 rate limit exceeded.",
        "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"
          }
        }
      },
      "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"
          }
        }
      },
      "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"
              },
              "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"
          }
        }
      },
      "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"
          }
        }
      },
      "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"
          }
        }
      },
      "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",
                "description": "Browser timezone country differs from the IP's. Silent when either is unknown."
              },
              "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"
          }
        }
      }
    }
  }
}
