---
title: "Score a whole signup in one call"
method: POST
path: "/v1/score/user"
tags: ["Unified"]
---

# Score a whole signup in one call

`POST /v1/score/user`

## Request

- Base URL: `https://www.layercall.com`
- URL: `https://www.layercall.com/v1/score/user`
- Auth: one of:
  - API key in header `x-api-key`
  - HTTP bearer

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.

## Query parameters

- `strictness` 'lenient' | 'balanced' | 'strict'

## Request body

- object: Supply any combination. Every field is optional, but at least one is required.
  - `ip` string
  - `email` string, email
  - `phone` string
  - `phone_country` string
  - `domain` string
  - `device_id` string: Fingerprint from fp.js.
  - `device_signals` object: Optional browser characteristics from fp.js.
  - `device_automation` object: Optional automation markers from fp.js.

## Response `200`

Combined score

- UserScore
  - `risk_score` integer, required: 0 = clean, 100 = certain fraud.
  - `verdict` 'allow' | 'review' | 'block', required: Recommended action. Thresholds move with `strictness`.
  - `components` object, required: Each supplied signal, scored independently. Absent keys were not supplied.
    - `ip` IpScore
      - `ip` string, required
      - `risk_score` integer, required: 0 = clean, 100 = certain fraud.
      - `verdict` 'allow' | 'review' | 'block', required: Recommended action. Thresholds move with `strictness`.
      - `signals` object, required
        - `is_vpn` boolean
        - `is_proxy` boolean
        - `is_datacenter` boolean
        - `is_tor` boolean
        - `recent_abuse` boolean
        - `is_private_or_reserved` boolean: RFC1918 / loopback / reserved. Cannot be meaningfully scored.
      - `geo` object, required
        - `country` string, nullable: ISO 3166-1 alpha-2.
        - `city` string, nullable
        - `asn` string, nullable
        - `isp` string, nullable
      - `first_seen` string, date-time, nullable: First time this value was seen across the LayerCall network.
      - `times_seen` integer
      - `abuse_reports` integer: Confirmed-fraud reports from distinct accounts.
      - `cached` boolean, required: Served from cache. Cached lookups are never billed.
      - `request_id` string, required
      - `warning` string: Present only when the input needs attention.
      - `vpn_provider` string, nullable: 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 — absence is not evidence the IP is not a VPN.
      - `signals_unavailable` string[]: Signals that could not be measured on this request — 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` unknown
      - `processing_time_sec` number
      - `billable_lookups` integer
    - `email` EmailResult
      - `email` string, required
      - `normalized_email` string
      - `risk_score` integer, required: 0 = clean, 100 = certain fraud.
      - `verdict` 'allow' | 'review' | 'block', required: Recommended action. Thresholds move with `strictness`.
      - `status` string: e.g. valid, invalid, do_not_mail, unknown.
      - `sub_status` string, nullable: e.g. disposable, role_based, mailbox_not_found.
      - `deliverability_score` integer
      - `did_you_mean` string, nullable: Suggested correction for a likely typo.
      - `signals` object
        - `syntax_valid` boolean
        - `mx_found` boolean
        - `is_disposable` boolean
        - `is_homograph` boolean: Lookalike domain — mixed script or a spelling that folds onto a real ASCII domain.
        - `is_role_account` boolean
        - `is_free_provider` boolean
      - `cached` boolean
      - `request_id` string
      - `signals_unavailable` string[]: Signals that could not be measured on this request — 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` string
      - `domain_age_days` unknown
      - `mx_provider` string
      - `mx_records` string[]
      - `first_seen` unknown
      - `times_seen` integer
      - `abuse_reports` integer
      - `digital_footprint` object
        - `has_gravatar` boolean, nullable: Whether the address has a public Gravatar. null means the check did not run — the request's time budget was exhausted — which is not the same as no avatar.
        - `gravatar_profile_url` unknown
        - `breach_count` unknown
        - `seen_in_breach` unknown
      - `processing_time_sec` number
      - `billable_lookups` integer
    - `phone` PhoneResult
      - `phone` string, required
      - `risk_score` integer, required: 0 = clean, 100 = certain fraud.
      - `verdict` 'allow' | 'review' | 'block', required: Recommended action. Thresholds move with `strictness`.
      - `parse_status` 'ok' | 'country_required' | 'impossible' | 'invalid_pattern': Distinguishes 'you omitted country' from 'this number is bad'.
      - `signals` object
        - `syntax_valid` boolean
        - `is_possible` boolean
        - `is_voip` boolean, nullable: 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` boolean
        - `is_toll_free` boolean
        - `assigned_area_code` boolean, nullable: NANP only. null when not applicable.
      - `number` object
        - `e164` string, nullable
        - `country` string, nullable
      - `cached` boolean
      - `request_id` string
      - `processing_time_sec` number
      - `first_seen` unknown
      - `times_seen` integer
      - `abuse_reports` integer
      - `billable_lookups` integer
    - `domain` DomainResult
      - `domain` string, required
      - `risk_score` integer, required: 0 = clean, 100 = certain fraud.
      - `verdict` 'allow' | 'review' | 'block', required: Recommended action. Thresholds move with `strictness`.
      - `signals` object
        - `resolves` boolean
        - `mx_found` boolean
        - `has_spf` boolean
        - `has_dmarc` boolean
        - `is_disposable` boolean
        - `is_homograph` boolean
        - `is_free_provider` boolean
        - `is_risky_tld` boolean
        - `newly_registered` boolean, nullable: Registered less than 30 days ago. null means the registration date could not be determined (RDAP unreachable, or the TLD publishes no RDAP) — it does NOT mean the domain is established. Treat null as unknown, not as a negative finding.
      - `registration` object
        - `created_at` string, date-time, nullable
      - `cached` boolean
      - `request_id` string
      - `signals_unavailable` string[]: Signals that could not be measured on this request — 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` number
      - `first_seen` unknown
      - `times_seen` integer
      - `abuse_reports` integer
      - `billable_lookups` integer
    - `device` DeviceScore
      - `device_id` string, required
      - `risk_score` integer, required: 0 = clean, 100 = certain fraud.
      - `verdict` 'allow' | 'review' | 'block', required: Recommended action. Thresholds move with `strictness`.
      - `bot_probability` integer, required: Reported separately from risk: plenty of automated traffic is legitimate.
      - `signals` object, required
        - `is_bot` boolean: bot_probability >= 60.
        - `is_automated` boolean
        - `is_headless` boolean
        - `timezone_mismatch` boolean, nullable: Browser timezone country differs from the IP's. Silent (false) when either is genuinely unknown; null when the country could NOT be resolved — a GeoIP outage — so no comparison was made.
        - `repeat_device` boolean: Seen before across the LayerCall network.
      - `first_seen` string, date-time, nullable
      - `times_seen` integer
      - `abuse_reports` integer
      - `request_id` string
      - `processing_time_sec` number
      - `billable_lookups` integer
  - `request_id` string
  - `signals_unavailable` string[]: Signals that could not be measured on this request — 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` string
  - `components_checked` string[]
  - `linkage` object
    - `device_email_count` unknown
    - `email_device_count` integer
    - `email_ip_count` integer
    - `subnet_rate_1h` integer
    - `domain_rate_1h` integer
  - `actor` object
    - `type` string
    - `proven` boolean
    - `basis` string
    - `operator` unknown
    - `trigger` unknown
    - `detail` string
  - `top_signals` string[]
  - `processing_time_sec` number
  - `billable_lookups` integer

## Other responses

- `400`: Missing or malformed input.
- `401`: Missing, invalid, or revoked API key.
- `429`: Monthly quota or spend cap reached, or the 600 requests-per-minute rate limit exceeded.

---

[API](https://skmtc.dev/layercall/apis/layercall-api.md) · [All operations](https://skmtc.dev/layercall/apis/layercall-api/llms.txt) · [OpenAPI document](https://skmtc.dev/layercall/apis/layercall-api/revisions/b1258307dc91?raw)
