> ## Documentation Index
> Fetch the complete documentation index at: https://docs.didit.me/llms.txt
> Use this file to discover all available pages before exploring further.

# IP Risk API

> Risk-score an IP address with geolocation, ISP, VPN/Tor, datacenter, threat, and claimed-country mismatch signals.

Use `POST /v3/ip/risk/` when your backend already has an IP address and needs a server-to-server risk decision.

The response includes geolocation, network ownership, VPN/Tor and data-center flags, threat data, and `country_mismatch` when you send a claimed country.

## Interpret the response

* `status` is `In Review` when the resolved country conflicts with `claimed_country`, or when the address is associated with VPN, Tor, or data-center infrastructure. Otherwise it is `Approved`.
* `ip.ip_country_code` contains the resolved country code. Compare countries through `ip.country_mismatch` instead of comparing raw country strings yourself.
* `ip.latitude` and `ip.longitude` are approximate network geolocation, not a precise device location.
* `ip.is_vpn_or_tor`, `ip.is_data_center`, `ip.proxy_type`, and `ip.threat` carry network-risk evidence.
* `ip.asn_number`, `ip.asn_organization`, `ip.connection_type`, and `ip.carrier` describe the network the address belongs to.
* `request_id` is the API session id. Store it with `vendor_data` if you need to correlate the result with later webhooks or console activity.

<Note>
  IP enrichment is best effort. If no enrichment source returns usable data, the endpoint returns an approved response with `ip.ip_address: null`, `ip.country_mismatch: null`, and an empty `ip.threat` object. Individual enrichment fields can also be `null` when unavailable.
</Note>

## Device intelligence

The IP Risk API is a pure server-to-server endpoint. Device fingerprinting is not, because a backend cannot observe browser or device signals by itself. For hosted-flow and SDK sessions, Didit exposes the already collected device fields on the session response, including `device_fingerprint`, device and browser details, `user_agent`, raw device data, and cross-session matches.

## Sandbox behavior

Sandbox keys validate the request and return deterministic approved data. They do not contact external intelligence services, consume credits, create a session, or contribute to Networks.

## Billing

IP Risk API calls use IP Risk API pricing. A live request is billable once the lookup starts.


## OpenAPI

````yaml POST /v3/ip/risk/
openapi: 3.0.0
info:
  version: 3.0.0
  title: Didit Verification API
  description: Identity verification API. Authenticate with x-api-key header.
servers:
  - url: https://verification.didit.me
security: []
tags: []
paths:
  /v3/ip/risk/:
    post:
      tags:
        - Standalone APIs
      summary: Risk-score an IP address
      description: >-
        Return geolocation, network ownership, VPN/Tor and data-center flags,
        threat data, and `country_mismatch` for a supplied IP address. The
        endpoint creates an API session and returns its id as `request_id`.


        **Decision.** `status` is `In Review` when the resolved country
        conflicts with `claimed_country`, or when the address is associated with
        VPN, Tor, or data-center infrastructure. Otherwise it is `Approved`.


        **Best effort.** IP enrichment is best effort. If no enrichment source
        returns usable data, the endpoint returns an approved response with
        `ip.ip_address: null`, `ip.country_mismatch: null`, and an empty
        `ip.threat` object.


        **Billing.** Live calls use IP Risk API pricing. The request is billable
        once the lookup starts.


        **Sandbox.** Sandbox keys validate the request and return deterministic
        approved data without consuming credits, creating a session, or
        contributing to Networks.


        **Authentication.** Send your application's API key in `x-api-key`.
        Missing or invalid credentials return `403` - this API never returns
        `401`.


        **Rate limit.** Shared write budget of 300 requests per minute per API
        key across all POST/PATCH/DELETE endpoints.
      operationId: post_v3ip_risk
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/IPRiskRequest'
            examples:
              Minimal:
                summary: Score an address with defaults
                value:
                  ip_address: 8.8.8.8
              With claimed country:
                summary: Score with a claimed country for mismatch detection
                value:
                  ip_address: 8.8.8.8
                  claimed_country: ESP
                  user_agent: >-
                    Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7)
                    AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.4
                    Safari/605.1.15
                  vendor_data: lead-123
      responses:
        '200':
          description: >-
            The risk evaluation completed. Inspect `status` for the decision;
            HTTP is always `200` for completed lookups.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/IPRiskResponse'
              examples:
                Clean residential IP:
                  summary: Residential address, claimed country matches
                  value:
                    request_id: 9a96ed2a-721e-4b86-8cbf-d61eda529279
                    status: Approved
                    ip:
                      status: Approved
                      ip_address: 192.0.2.10
                      ip_country: Spain
                      ip_country_code: ES
                      ip_state: Madrid
                      ip_city: Madrid
                      latitude: 40.4168
                      longitude: -3.7038
                      isp: Example Telecom
                      organization: Example Telecom SA
                      is_vpn_or_tor: false
                      is_data_center: false
                      time_zone: Europe/Madrid
                      time_zone_offset: '+0200'
                      asn_number: 3352
                      asn_organization: EXAMPLE-TELECOM
                      connection_type: broadband
                      carrier: Example Telecom
                      proxy_type: null
                      claimed_country: ESP
                      country_mismatch: false
                      threat: {}
                    vendor_data: lead-123
                    metadata: null
                    created_at: '2026-09-01T10:30:00.123456+00:00'
                Datacenter mismatch:
                  summary: Data-center address conflicting with the claimed country
                  value:
                    request_id: 5b1d4e9a-3c7b-4f8e-9a2d-1b0c9d8e7f6a
                    status: In Review
                    ip:
                      status: In Review
                      ip_address: 8.8.8.8
                      ip_country: United States
                      ip_country_code: US
                      ip_state: California
                      ip_city: Mountain View
                      latitude: 37.386
                      longitude: -122.0838
                      isp: Google
                      organization: Google LLC
                      is_vpn_or_tor: true
                      is_data_center: true
                      time_zone: America/Los_Angeles
                      time_zone_offset: '-0700'
                      asn_number: 15169
                      asn_organization: GOOGLE, US
                      connection_type: hosting
                      carrier: null
                      proxy_type: VPN
                      claimed_country: ESP
                      country_mismatch: true
                      threat:
                        is_proxy: true
                        is_data_center: true
                        is_tor: false
                    vendor_data: lead-126
                    metadata: null
                    created_at: '2026-09-01T10:30:00.123456+00:00'
        '400':
          $ref: '#/components/responses/IPRiskValidationError'
        '403':
          $ref: '#/components/responses/RiskPermissionDenied'
        '429':
          $ref: '#/components/responses/RateLimitedWrite'
      security:
        - ApiKeyAuth: []
      x-codeSamples:
        - lang: curl
          label: curl
          source: |-
            curl -X POST https://verification.didit.me/v3/ip/risk/ \
              -H 'x-api-key: YOUR_API_KEY' \
              -H 'Content-Type: application/json' \
              -d '{
                "ip_address": "8.8.8.8",
                "claimed_country": "ESP",
                "user_agent": "Mozilla/5.0",
                "vendor_data": "lead-123"
              }'
        - lang: python
          label: Python
          source: >-
            import os, requests


            resp = requests.post(
                "https://verification.didit.me/v3/ip/risk/",
                headers={"x-api-key": os.environ["DIDIT_API_KEY"], "Content-Type": "application/json"},
                json={
                    "ip_address": "8.8.8.8",
                    "claimed_country": "ESP",
                    "user_agent": "Mozilla/5.0",
                    "vendor_data": "lead-123",
                },
                timeout=15,
            )

            resp.raise_for_status()

            result = resp.json()

            print(result["status"])  # Approved / In Review

            print(result["ip"]["ip_country_code"],
            result["ip"]["country_mismatch"])
        - lang: javascript
          label: JavaScript
          source: >-
            const res = await fetch('https://verification.didit.me/v3/ip/risk/',
            {
              method: 'POST',
              headers: {
                'x-api-key': 'YOUR_API_KEY',
                'Content-Type': 'application/json',
              },
              body: JSON.stringify({
                ip_address: '8.8.8.8',
                claimed_country: 'ESP',
                user_agent: 'Mozilla/5.0',
                vendor_data: 'lead-123',
              }),
            });

            const data = await res.json();

            console.log(data.status, data.ip.country_mismatch);
components:
  schemas:
    IPRiskRequest:
      type: object
      required:
        - ip_address
      properties:
        ip_address:
          type: string
          format: ip
          description: IPv4 or IPv6 address to score.
          example: 8.8.8.8
        claimed_country:
          type: string
          maxLength: 3
          description: >-
            Optional ISO 3166-1 alpha-2 or alpha-3 country claimed by the user
            or session. Used to calculate `country_mismatch`.
          example: ESP
        user_agent:
          type: string
          maxLength: 512
          description: >-
            User agent observed by your backend. Stored as request context; it
            does not provide device fingerprinting.
        vendor_data:
          type: string
          description: >-
            Your stable reference for this request. Stored on the API session
            and echoed in the response.
        metadata:
          type: object
          nullable: true
          additionalProperties: true
          description: >-
            Your non-sensitive JSON metadata. Stored on the API session and
            echoed in the response.
    IPRiskResponse:
      type: object
      required:
        - request_id
        - status
        - created_at
      properties:
        request_id:
          type: string
          format: uuid
          description: >-
            Id of the API session created for this request. Store it with
            `vendor_data` if you need to correlate the result with later
            webhooks or console activity.
        status:
          type: string
          enum:
            - Approved
            - In Review
          description: >-
            Decision for this API session: `In Review` on network or
            country-mismatch risk, otherwise `Approved`.
        vendor_data:
          type: string
          nullable: true
          description: >-
            Echo of the `vendor_data` you sent, `null` when you did not send
            one.
        metadata:
          type: object
          nullable: true
          additionalProperties: true
          description: Echo of the `metadata` you sent, `null` when you did not send one.
        created_at:
          type: string
          format: date-time
          description: Timestamp of this response.
        ip:
          type: object
          description: >-
            IP risk result. Individual enrichment fields can be `null` when the
            lookup returned no usable data.
          required:
            - status
            - threat
          properties:
            status:
              type: string
              enum:
                - Approved
                - In Review
              description: >-
                `In Review` when the resolved country conflicts with
                `claimed_country`, or when the address is associated with VPN,
                Tor, or data-center infrastructure. Otherwise `Approved`.
            ip_address:
              type: string
              nullable: true
              description: >-
                The scored address. `null` when no enrichment source returned
                usable data.
            ip_country:
              type: string
              nullable: true
              description: Resolved country name.
            ip_country_code:
              type: string
              nullable: true
              description: >-
                Resolved country code. Compare countries through
                `country_mismatch` instead of comparing raw country strings
                yourself.
            ip_state:
              type: string
              nullable: true
              description: Resolved state or region.
            ip_city:
              type: string
              nullable: true
              description: Resolved city.
            latitude:
              type: number
              nullable: true
              description: Approximate network geolocation, not a precise device location.
            longitude:
              type: number
              nullable: true
              description: Approximate network geolocation, not a precise device location.
            isp:
              type: string
              nullable: true
              description: Internet service provider serving the address.
            organization:
              type: string
              nullable: true
              description: Organization owning the address block.
            is_vpn_or_tor:
              type: boolean
              nullable: true
              description: >-
                `true` when the address is associated with VPN or Tor
                infrastructure.
            is_data_center:
              type: boolean
              nullable: true
              description: >-
                `true` when the address belongs to a hosting or data-center
                network.
            time_zone:
              type: string
              nullable: true
              description: Time zone of the resolved location.
            time_zone_offset:
              type: string
              nullable: true
              description: UTC offset of the resolved location.
            asn_number:
              type: number
              nullable: true
              description: Autonomous system number.
            asn_organization:
              type: string
              nullable: true
              description: Organization operating the autonomous system.
            connection_type:
              type: string
              nullable: true
              description: Connection classification (e.g., `cellular`, `hosting`).
            carrier:
              type: string
              nullable: true
              description: Mobile carrier when the address is on a mobile network.
            proxy_type:
              type: string
              nullable: true
              description: Proxy classification when the address is behind a proxy.
            claimed_country:
              type: string
              nullable: true
              description: Echo of the `claimed_country` you sent.
            country_mismatch:
              type: boolean
              nullable: true
              description: >-
                `true` when the resolved country differs from `claimed_country`.
                `null` when no enrichment data or no claimed country is
                available.
            threat:
              type: object
              additionalProperties: true
              description: >-
                Free-form network-threat evidence from the enrichment source
                (e.g., `is_proxy`, `is_tor`). Empty object when no enrichment
                data is available. Treat as diagnostic detail, not a stable
                contract.
  responses:
    IPRiskValidationError:
      description: Request validation failed.
      content:
        application/json:
          examples:
            Missing IP address:
              summary: Missing IP address
              value:
                ip_address:
                  - This field is required.
            Invalid IP address:
              summary: Invalid IP address
              value:
                ip_address:
                  - Enter a valid IPv4 or IPv6 address.
            Country hint too long:
              summary: Country hint too long
              value:
                claimed_country:
                  - Ensure this field has no more than 3 characters.
    RiskPermissionDenied:
      description: >-
        The API key is missing or invalid, or the organization cannot cover the
        live lookup. This API never returns `401`.
      content:
        application/json:
          examples:
            Missing or invalid API key:
              summary: No `x-api-key` header, or the key is invalid or revoked
              value:
                detail: You do not have permission to perform this action.
            Not enough credits:
              summary: Organization balance cannot cover the lookup price
              value:
                error: You don't have enough credits to perform this request.
    RateLimitedWrite:
      description: >-
        The shared write limit of 300 requests per minute per API key was
        exceeded. Honor `Retry-After` before retrying.
      content:
        application/json:
          schema:
            type: object
            required:
              - detail
            properties:
              detail:
                type: string
          example:
            detail: >-
              Write request rate limit exceeded. You can make up to 300 requests
              per minute.
  securitySchemes:
    ApiKeyAuth:
      type: apiKey
      in: header
      name: x-api-key
      description: >-
        Your application's API key, from Developers -> API keys in the Business
        Console. The primary key has full access. A named key can be scoped:
        none, read or write per resource, limited to some workflows or to
        approved sessions, to a list of IP addresses, and to an expiry date. 401
        means the key is missing, wrong, revoked or expired; 403 means the key
        has no access to this resource or action, or the request came from an
        address outside its IP list; 404 on a session route means the session is
        outside the key's workflows or statuses. A key without media access
        receives image, video and PDF URLs as null, and a key without sessions
        write receives session links and tokens as null. See
        https://docs.didit.me/console/api-keys.

````

This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.