{
  "openapi": "3.1.0",
  "info": {
    "title": "fingerprint.dev API",
    "version": "1.0.0",
    "summary": "Hosted cookieless visitor identification API.",
    "description": "Use the fingerprint.dev API to identify returning visitors, retrieve visitor records, and review identify events."
  },
  "servers": [
    {
      "url": "https://api.fingerprint.dev",
      "description": "Production API"
    }
  ],
  "security": [
    {
      "ApiKeyAuth": []
    }
  ],
  "paths": {
    "/health": {
      "get": {
        "summary": "Check API health",
        "description": "Returns basic operational status for uptime checks.",
        "operationId": "getHealth",
        "security": [],
        "responses": {
          "200": {
            "description": "The API is reachable.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HealthResponse"
                }
              }
            }
          }
        }
      }
    },
    "/v1/identify": {
      "post": {
        "summary": "Identify a visitor",
        "description": "Returns a `visitor_id`, confidence score, request id, and the fingerprint signals observed for this request.",
        "operationId": "identifyVisitor",
        "parameters": [
          {
            "name": "detail",
            "in": "query",
            "required": false,
            "description": "When true, include signal weights and first/last seen timestamps.",
            "schema": {
              "type": "boolean",
              "default": false
            }
          }
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/IdentifyRequest"
              },
              "examples": {
                "browserSignals": {
                  "summary": "Browser-assisted identify request",
                  "value": {
                    "signals": {
                      "canvas": "6f3a19c84b21",
                      "webgl": null,
                      "audio": "39aef102cc83",
                      "screen": "e47bb910aa08",
                      "fonts": null,
                      "navigator": "44df70dc57a1"
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Visitor identification result.",
            "content": {
              "application/json": {
                "schema": {
                  "oneOf": [
                    {
                      "$ref": "#/components/schemas/IdentifyResponse"
                    },
                    {
                      "$ref": "#/components/schemas/DetailedIdentifyResponse"
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/visitors": {
      "get": {
        "summary": "List visitors",
        "description": "Returns visitor records for the authenticated account.",
        "operationId": "listVisitors",
        "parameters": [
          {
            "name": "q",
            "in": "query",
            "required": false,
            "description": "Optional search text.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "device_class",
            "in": "query",
            "required": false,
            "description": "Optional device class filter.",
            "schema": {
              "type": "string",
              "examples": ["desktop", "mobile", "tablet"]
            }
          },
          {
            "name": "page",
            "in": "query",
            "required": false,
            "description": "Page number.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "default": 1
            }
          },
          {
            "name": "per_page",
            "in": "query",
            "required": false,
            "description": "Records per page, clamped to 1..100.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 20
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Visitor page.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/VisitorListResponse"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/visitors/{id}": {
      "get": {
        "summary": "Get a visitor",
        "description": "Returns one visitor record for the authenticated account.",
        "operationId": "getVisitor",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Visitor UUID.",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Visitor record.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Visitor"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/events": {
      "get": {
        "summary": "List identify events",
        "description": "Returns identify events for the authenticated account.",
        "operationId": "listEvents",
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Records to return, clamped to 1..100.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 50
            }
          },
          {
            "name": "offset",
            "in": "query",
            "required": false,
            "description": "Pagination offset.",
            "schema": {
              "type": "integer",
              "minimum": 0,
              "default": 0
            }
          },
          {
            "name": "visitor_id",
            "in": "query",
            "required": false,
            "description": "Optional visitor UUID filter.",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Identify event page.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/EventListResponse"
                }
              }
            }
          },
          "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": "Dashboard API key, such as `fp_live_...`."
      }
    },
    "responses": {
      "BadRequest": {
        "description": "Invalid request body or parameter.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorResponse"
            }
          }
        }
      },
      "Unauthorized": {
        "description": "Missing, invalid, or expired credential.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorResponse"
            }
          }
        }
      },
      "PaymentRequired": {
        "description": "Payment is required for this request.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorResponse"
            }
          }
        }
      },
      "Forbidden": {
        "description": "The key is not allowed for the request origin.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorResponse"
            }
          }
        }
      },
      "NotFound": {
        "description": "The requested resource was not found.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorResponse"
            }
          }
        }
      },
      "RateLimited": {
        "description": "Rate limit exceeded.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorResponse"
            }
          }
        }
      }
    },
    "schemas": {
      "HealthResponse": {
        "type": "object",
        "additionalProperties": true,
        "properties": {
          "status": {
            "type": "string",
            "examples": ["ok"]
          },
          "db_ok": {
            "type": "boolean"
          },
          "version": {
            "type": "string",
            "examples": ["0.1.12"]
          },
          "build_tag": {
            "type": "string",
            "examples": ["v0.1.12"]
          },
          "build_sha": {
            "type": "string",
            "examples": ["4e570fdc6bd2f8f9f3e8d0629f7d95b9e11c0f64"]
          }
        }
      },
      "IdentifyRequest": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "signals": {
            "$ref": "#/components/schemas/BrowserSignals"
          }
        }
      },
      "BrowserSignals": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "canvas": {
            "$ref": "#/components/schemas/BrowserSignalHash"
          },
          "webgl": {
            "$ref": "#/components/schemas/BrowserSignalHash"
          },
          "audio": {
            "$ref": "#/components/schemas/BrowserSignalHash"
          },
          "screen": {
            "$ref": "#/components/schemas/BrowserSignalHash"
          },
          "fonts": {
            "$ref": "#/components/schemas/BrowserSignalHash"
          },
          "navigator": {
            "$ref": "#/components/schemas/BrowserSignalHash"
          }
        }
      },
      "BrowserSignalHash": {
        "type": ["string", "null"],
        "pattern": "^[0-9a-f]{12}$",
        "examples": ["6f3a19c84b21"]
      },
      "IdentifyResponse": {
        "type": "object",
        "required": ["visitor_id", "confidence", "kind", "request_id", "signals"],
        "properties": {
          "visitor_id": {
            "type": ["string", "null"],
            "format": "uuid"
          },
          "confidence": {
            "type": ["number", "null"],
            "minimum": 0,
            "maximum": 1
          },
          "kind": {
            "type": "string",
            "enum": ["new", "match"]
          },
          "request_id": {
            "type": "string",
            "examples": ["req_550e8400-e29b-41d4-a716-446655440000"]
          },
          "signals": {
            "$ref": "#/components/schemas/IdentifySignals"
          }
        }
      },
      "DetailedIdentifyResponse": {
        "allOf": [
          {
            "$ref": "#/components/schemas/IdentifyResponse"
          },
          {
            "type": "object",
            "required": ["first_seen", "last_seen"],
            "properties": {
              "signals": {
                "$ref": "#/components/schemas/DetailedSignals"
              },
              "first_seen": {
                "type": ["string", "null"],
                "format": "date-time"
              },
              "last_seen": {
                "type": ["string", "null"],
                "format": "date-time"
              }
            }
          }
        ]
      },
      "IdentifySignals": {
        "type": "object",
        "required": ["ja4", "ja4t", "ja4x", "ja4h", "js"],
        "properties": {
          "ja4": {
            "type": ["string", "null"]
          },
          "ja4t": {
            "type": ["string", "null"]
          },
          "ja4x": {
            "type": ["string", "null"]
          },
          "ja4h": {
            "type": ["string", "null"]
          },
          "js": {
            "type": ["string", "null"],
            "pattern": "^[0-9a-f]{12}$"
          }
        }
      },
      "DetailedSignals": {
        "type": "object",
        "required": ["ja4", "ja4t", "ja4x", "ja4h", "js"],
        "properties": {
          "ja4": {
            "$ref": "#/components/schemas/SignalDetail"
          },
          "ja4t": {
            "$ref": "#/components/schemas/SignalDetail"
          },
          "ja4x": {
            "$ref": "#/components/schemas/SignalDetail"
          },
          "ja4h": {
            "$ref": "#/components/schemas/SignalDetail"
          },
          "js": {
            "$ref": "#/components/schemas/SignalDetail"
          }
        }
      },
      "SignalDetail": {
        "type": "object",
        "required": ["hash", "weight"],
        "properties": {
          "hash": {
            "type": ["string", "null"]
          },
          "weight": {
            "type": "number",
            "minimum": 0
          }
        }
      },
      "VisitorListResponse": {
        "type": "object",
        "required": ["visitors", "total", "page", "per_page"],
        "properties": {
          "visitors": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Visitor"
            }
          },
          "total": {
            "type": "integer",
            "minimum": 0
          },
          "page": {
            "type": "integer",
            "minimum": 1
          },
          "per_page": {
            "type": "integer",
            "minimum": 1,
            "maximum": 100
          }
        }
      },
      "Visitor": {
        "type": "object",
        "required": ["id", "device_class", "fingerprint", "confidence", "first_seen", "last_seen", "request_count"],
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "device_class": {
            "type": "string",
            "examples": ["desktop"]
          },
          "fingerprint": {
            "$ref": "#/components/schemas/Fingerprint"
          },
          "confidence": {
            "type": "number",
            "minimum": 0,
            "maximum": 1
          },
          "first_seen": {
            "type": "string",
            "format": "date-time"
          },
          "last_seen": {
            "type": "string",
            "format": "date-time"
          },
          "request_count": {
            "type": "integer",
            "minimum": 0
          }
        }
      },
      "EventListResponse": {
        "type": "object",
        "required": ["events", "pagination"],
        "properties": {
          "events": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Event"
            }
          },
          "pagination": {
            "$ref": "#/components/schemas/Pagination"
          }
        }
      },
      "Event": {
        "type": "object",
        "required": ["id", "visitor_id", "fingerprint", "confidence", "cache_hit", "created_at"],
        "properties": {
          "id": {
            "type": "string",
            "examples": ["req_550e8400-e29b-41d4-a716-446655440000"]
          },
          "visitor_id": {
            "type": ["string", "null"],
            "format": "uuid"
          },
          "fingerprint": {
            "$ref": "#/components/schemas/Fingerprint"
          },
          "confidence": {
            "type": "number",
            "minimum": 0,
            "maximum": 1
          },
          "cache_hit": {
            "type": "boolean"
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "Fingerprint": {
        "type": "object",
        "required": ["ja4", "ja4t", "ja4h", "ja4x", "js_signals", "browser"],
        "properties": {
          "ja4": {
            "type": ["string", "null"]
          },
          "ja4t": {
            "type": ["string", "null"]
          },
          "ja4h": {
            "type": ["string", "null"]
          },
          "ja4x": {
            "type": ["string", "null"]
          },
          "js_signals": {
            "type": ["string", "null"],
            "pattern": "^[0-9a-f]{12}$"
          },
          "browser": {
            "$ref": "#/components/schemas/BrowserSignals"
          }
        }
      },
      "Pagination": {
        "type": "object",
        "required": ["limit", "offset", "total"],
        "properties": {
          "limit": {
            "type": "integer",
            "minimum": 1,
            "maximum": 100
          },
          "offset": {
            "type": "integer",
            "minimum": 0
          },
          "total": {
            "type": "integer",
            "minimum": 0
          }
        }
      },
      "ErrorResponse": {
        "type": "object",
        "required": ["error"],
        "properties": {
          "error": {
            "type": "string"
          }
        }
      }
    }
  }
}
