{
  "openapi": "3.1.1",
  "info": {
    "title": "NoVPN detection API",
    "description": "Tells you whether an IP address is a VPN, proxy, Tor exit, hosting/datacenter address or residential proxy.\n\nEvery successful check consumes one request from the account's monthly allowance, cache hits included. Errors and addresses the database has never seen are not billed on paid plans. `GET /v1/me` reports what is left and is always free.\n\nErrors are `application/problem+json` with a stable `code` field. Branch on `code`, never on `title` or `detail`: those are prose and may be reworded.",
    "version": "1.0"
  },
  "servers": [
    {
      "url": "https://novpn.fr"
    }
  ],
  "paths": {
    "/v1/check/{ip}": {
      "get": {
        "tags": [
          "Gateway"
        ],
        "summary": "Check one IP address",
        "description": "Costs one request from the monthly allowance, cache hits included. An address the detection database has never seen answers 404 ip_not_found rather than a 200 with empty fields, and is not billed on plans with free errors.",
        "operationId": "Check",
        "parameters": [
          {
            "name": "ip",
            "in": "path",
            "description": "An IPv4 or IPv6 address. Any form the platform parses is accepted; the response echoes the canonical one, not the string that was sent.",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The verdict. Every field is populated; read risk for a decision or score for a threshold of your own.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CheckResponse"
                }
              }
            }
          },
          "400": {
            "description": "invalid_ip",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/GatewayProblem"
                }
              }
            }
          },
          "401": {
            "description": "missing_credentials, invalid_key, key_revoked",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/GatewayProblem"
                }
              }
            }
          },
          "403": {
            "description": "ip_not_allowed, account_suspended",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/GatewayProblem"
                }
              }
            }
          },
          "404": {
            "description": "ip_not_found",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/GatewayProblem"
                }
              }
            }
          },
          "429": {
            "description": "rate_limited, quota_exceeded",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/GatewayProblem"
                }
              }
            }
          },
          "502": {
            "description": "upstream_error",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/GatewayProblem"
                }
              }
            }
          },
          "504": {
            "description": "upstream_timeout",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/GatewayProblem"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerApiKey": [ ]
          },
          {
            "headerApiKey": [ ]
          }
        ]
      }
    },
    "/v1/me": {
      "get": {
        "tags": [
          "Gateway"
        ],
        "summary": "Read the key's plan and remaining allowance",
        "description": "Never billable, and still answers once the allowance is exhausted. Read quota_remaining and burst_per_minute before sending a large batch: both are ceilings a batch can hit, and only one of them is fixed by upgrading.",
        "operationId": "Me",
        "responses": {
          "200": {
            "description": "The key's plan and where it stands against both of its ceilings.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/MeResponse"
                }
              }
            }
          },
          "401": {
            "description": "missing_credentials, invalid_key, key_revoked",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/GatewayProblem"
                }
              }
            }
          },
          "403": {
            "description": "ip_not_allowed, account_suspended",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/GatewayProblem"
                }
              }
            }
          },
          "429": {
            "description": "rate_limited",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/GatewayProblem"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerApiKey": [ ]
          },
          {
            "headerApiKey": [ ]
          }
        ]
      }
    },
    "/v1/check": {
      "post": {
        "tags": [
          "Gateway"
        ],
        "summary": "Check up to 100 IP addresses in one call",
        "description": "Entries come back in the order the addresses were sent, and each carries either a result or the reason there is none. A batch spends one request from the monthly allowance and one per-minute permit per address, so an allowance that runs out partway through is reported per entry rather than failing the whole call.",
        "operationId": "CheckBatch",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "oneOf": [
                  {
                    "type": "null"
                  },
                  {
                    "$ref": "#/components/schemas/BatchCheckRequest"
                  }
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "One entry per address sent, in the same order. billable says how many of them were charged.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/BatchCheckResponse"
                }
              }
            }
          },
          "400": {
            "description": "invalid_batch, batch_too_large",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/GatewayProblem"
                }
              }
            }
          },
          "401": {
            "description": "missing_credentials, invalid_key, key_revoked",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/GatewayProblem"
                }
              }
            }
          },
          "403": {
            "description": "ip_not_allowed, account_suspended",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/GatewayProblem"
                }
              }
            }
          },
          "429": {
            "description": "rate_limited, quota_exceeded",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/GatewayProblem"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerApiKey": [ ]
          },
          {
            "headerApiKey": [ ]
          }
        ]
      }
    }
  },
  "components": {
    "schemas": {
      "BatchCheckError": {
        "required": [
          "code",
          "detail"
        ],
        "type": "object",
        "properties": {
          "code": {
            "type": "string"
          },
          "detail": {
            "type": "string"
          }
        }
      },
      "BatchCheckItem": {
        "required": [
          "ip"
        ],
        "type": "object",
        "properties": {
          "ip": {
            "type": "string"
          },
          "check": {
            "oneOf": [
              {
                "type": "null"
              },
              {
                "$ref": "#/components/schemas/CheckResponse"
              }
            ]
          },
          "error": {
            "oneOf": [
              {
                "type": "null"
              },
              {
                "$ref": "#/components/schemas/BatchCheckError"
              }
            ]
          }
        }
      },
      "BatchCheckRequest": {
        "type": "object",
        "properties": {
          "ips": {
            "type": [
              "null",
              "array"
            ],
            "items": {
              "type": "string"
            }
          }
        }
      },
      "BatchCheckResponse": {
        "required": [
          "results",
          "billable",
          "request_id"
        ],
        "type": "object",
        "properties": {
          "results": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/BatchCheckItem"
            }
          },
          "billable": {
            "pattern": "^-?(?:0|[1-9]\\d*)$",
            "type": [
              "integer",
              "string"
            ],
            "format": "int32"
          },
          "request_id": {
            "type": "string"
          }
        }
      },
      "CheckResponse": {
        "required": [
          "ip",
          "score",
          "risk",
          "detections",
          "network",
          "location",
          "request_id"
        ],
        "type": "object",
        "properties": {
          "ip": {
            "type": "string"
          },
          "score": {
            "pattern": "^-?(?:0|[1-9]\\d*)(?:\\.\\d+)?(?:[eE][+-]?\\d+)?$",
            "type": [
              "number",
              "string"
            ],
            "format": "float"
          },
          "risk": {
            "type": "string"
          },
          "proxy": {
            "type": "boolean"
          },
          "vpn": {
            "type": "boolean"
          },
          "tor": {
            "type": "boolean"
          },
          "hosting": {
            "type": "boolean"
          },
          "residential_proxy": {
            "type": "boolean"
          },
          "detections": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "network": {
            "$ref": "#/components/schemas/NetworkInfo"
          },
          "location": {
            "$ref": "#/components/schemas/LocationInfo"
          },
          "cached": {
            "type": "boolean"
          },
          "request_id": {
            "type": "string"
          }
        }
      },
      "GatewayProblem": {
        "required": [
          "type",
          "title",
          "status",
          "code",
          "request_id"
        ],
        "type": "object",
        "properties": {
          "type": {
            "type": "string"
          },
          "title": {
            "type": "string"
          },
          "status": {
            "pattern": "^-?(?:0|[1-9]\\d*)$",
            "type": [
              "integer",
              "string"
            ],
            "format": "int32"
          },
          "code": {
            "type": "string"
          },
          "detail": {
            "type": [
              "null",
              "string"
            ]
          },
          "request_id": {
            "type": "string"
          }
        }
      },
      "LocationInfo": {
        "required": [
          "country",
          "country_code",
          "continent",
          "continent_code"
        ],
        "type": "object",
        "properties": {
          "country": {
            "type": "string"
          },
          "country_code": {
            "type": "string"
          },
          "continent": {
            "type": "string"
          },
          "continent_code": {
            "type": "string"
          }
        }
      },
      "MeResponse": {
        "required": [
          "plan",
          "quota_limit",
          "quota_used",
          "quota_remaining",
          "quota_resets_at",
          "burst_per_minute",
          "cache_enabled",
          "free_errors_enabled",
          "request_id"
        ],
        "type": "object",
        "properties": {
          "plan": {
            "type": "string"
          },
          "quota_limit": {
            "pattern": "^-?(?:0|[1-9]\\d*)$",
            "type": [
              "integer",
              "string"
            ],
            "format": "int64"
          },
          "quota_used": {
            "pattern": "^-?(?:0|[1-9]\\d*)$",
            "type": [
              "integer",
              "string"
            ],
            "format": "int64"
          },
          "quota_remaining": {
            "pattern": "^-?(?:0|[1-9]\\d*)$",
            "type": [
              "integer",
              "string"
            ],
            "format": "int64"
          },
          "quota_resets_at": {
            "type": "string",
            "format": "date-time"
          },
          "burst_per_minute": {
            "pattern": "^-?(?:0|[1-9]\\d*)$",
            "type": [
              "integer",
              "string"
            ],
            "format": "int32"
          },
          "cache_enabled": {
            "type": "boolean"
          },
          "free_errors_enabled": {
            "type": "boolean"
          },
          "request_id": {
            "type": "string"
          }
        }
      },
      "NetworkInfo": {
        "required": [
          "asn",
          "provider",
          "hostname",
          "cloud"
        ],
        "type": "object",
        "properties": {
          "asn": {
            "type": "string"
          },
          "provider": {
            "type": "string"
          },
          "hostname": {
            "type": [
              "null",
              "string"
            ]
          },
          "cloud": {
            "type": [
              "null",
              "string"
            ]
          }
        }
      }
    },
    "securitySchemes": {
      "bearerApiKey": {
        "type": "http",
        "description": "The API key as `Authorization: Bearer nv_live_...`.",
        "scheme": "bearer"
      },
      "headerApiKey": {
        "type": "apiKey",
        "description": "The same key as `X-API-Key`, for clients that cannot set Authorization.",
        "name": "X-API-Key",
        "in": "header"
      }
    }
  },
  "tags": [
    {
      "name": "Gateway"
    }
  ]
}