{
  "openapi": "3.1.0",
  "info": {
    "title": "TraceTail API",
    "version": "3.1.2",
    "summary": "Browser fingerprinting: a stable visitor ID for each browser, without cookies.",
    "description": "TraceTail is a browser fingerprinting API for recognizing returning visitors without cookies. Its JavaScript SDK (a script tag or the npm package @tracetail/js) turns 40+ browser signals into a stable visitor ID with 99.6% accuracy, the same in normal and incognito windows. It works without an API key for a quick start; with a key, every identification is registered with the API, checked for automation and shown in a dashboard. 1,000 identification requests are free every month, then $0.10 per 1,000.\n\nThe browser SDK calls the identification endpoints; customers' servers verify what the browser sent with the Server API (secret server keys). Rate limits: 100 requests a second per API key, 600 a minute per IP address and 600 a minute per server key.",
    "termsOfService": "https://tracetail.io/terms-and-conditions",
    "contact": {
      "name": "TraceTail support",
      "email": "support@tracetail.io",
      "url": "https://tracetail.io/contact"
    }
  },
  "externalDocs": {
    "description": "Documentation (also as Markdown: /docs.md)",
    "url": "https://tracetail.io/docs"
  },
  "servers": [
    {
      "url": "https://tracetail.io"
    }
  ],
  "tags": [
    {
      "name": "Identification",
      "description": "What the browser SDK calls."
    },
    {
      "name": "Server API",
      "description": "What a customer's backend calls to verify identifications, with a secret server key."
    },
    {
      "name": "Setup links",
      "description": "How an AI agent gets API keys for the person it works for: a link they open to sign in and add a card or continue without a card."
    },
    {
      "name": "Service",
      "description": "Health and first-party proxy checks."
    }
  ],
  "paths": {
    "/api/id": {
      "post": {
        "operationId": "identify",
        "tags": [
          "Identification"
        ],
        "summary": "Register an identification",
        "description": "Called by the TraceTail browser SDK after it computes the visitor ID, from a page on the API key's domain: the browser's Origin (or Referer) header must match the domain the key was created for. Non-browser clients can forge these headers. Each successful call counts toward usage. Integrate with the SDK rather than calling it directly.",
        "security": [
          {
            "publicKey": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/IdentificationRequest"
              },
              "example": {
                "visitorId": "fp3_e8fc80d60f8c3571",
                "components": {
                  "basic": {
                    "browser": "chrome",
                    "os": "macos",
                    "language": "en-US",
                    "platform": "MacIntel",
                    "cookieEnabled": true,
                    "screen": [
                      1440,
                      900
                    ],
                    "colorDepth": 30,
                    "timezone": "Europe/Berlin"
                  },
                  "hardware": {
                    "hardwareConcurrency": 8,
                    "maxTouchPoints": 0,
                    "deviceMemory": 8
                  },
                  "canvas": {
                    "hash": "example",
                    "samples": [
                      12480,
                      9216,
                      14111,
                      7340
                    ]
                  },
                  "webgl": {
                    "vendor": "WebKit",
                    "renderer": "WebKit WebGL",
                    "version": "WebGL 1.0",
                    "unmaskedVendor": "Apple Inc.",
                    "unmaskedRenderer": "Apple GPU"
                  },
                  "fonts": {
                    "detected": [
                      "Arial",
                      "Helvetica"
                    ],
                    "count": 2,
                    "hash": "example"
                  },
                  "automation": {
                    "webdriver": false
                  }
                },
                "confidence": 1
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Registered",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Identification"
                }
              }
            }
          },
          "400": {
            "description": "Invalid request body or ID/component mismatch",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "VALIDATION_ERROR": {
                    "value": {
                      "error": "Invalid request body or ID/component mismatch",
                      "code": "VALIDATION_ERROR"
                    }
                  },
                  "ID_COMPONENT_MISMATCH": {
                    "value": {
                      "error": "Invalid request body or ID/component mismatch",
                      "code": "ID_COMPONENT_MISMATCH"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Invalid key or wrong domain",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "INVALID_KEY": {
                    "value": {
                      "error": "Invalid key or wrong domain",
                      "code": "INVALID_KEY"
                    }
                  },
                  "DOMAIN_MISMATCH": {
                    "value": {
                      "error": "Invalid key or wrong domain",
                      "code": "DOMAIN_MISMATCH"
                    }
                  }
                }
              }
            }
          },
          "402": {
            "description": "Subscription unpaid",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "PAYMENT_REQUIRED": {
                    "value": {
                      "error": "Subscription unpaid",
                      "code": "PAYMENT_REQUIRED"
                    }
                  }
                }
              }
            }
          },
          "409": {
            "description": "Request token reused with different data",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "IDEMPOTENCY_CONFLICT": {
                    "value": {
                      "error": "Request token reused with different data",
                      "code": "IDEMPOTENCY_CONFLICT"
                    }
                  }
                }
              }
            }
          },
          "413": {
            "description": "Signals too large",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "PAYLOAD_TOO_LARGE": {
                    "value": {
                      "error": "Signals too large",
                      "code": "PAYLOAD_TOO_LARGE"
                    }
                  }
                }
              }
            }
          },
          "429": {
            "description": "Rate limited, or the free allowance is used up",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "RATE_LIMITED": {
                    "value": {
                      "error": "Rate limited, or the free allowance is used up",
                      "code": "RATE_LIMITED"
                    }
                  },
                  "QUOTA_EXCEEDED": {
                    "value": {
                      "error": "Rate limited, or the free allowance is used up",
                      "code": "QUOTA_EXCEEDED"
                    }
                  }
                }
              }
            },
            "headers": {
              "Retry-After": {
                "description": "Seconds to wait before retrying.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          }
        }
      }
    },
    "/api/id/detail": {
      "post": {
        "operationId": "identifyDetailed",
        "tags": [
          "Identification"
        ],
        "summary": "Register an identification, with the risk assessment",
        "description": "Called by the TraceTail browser SDK after it computes the visitor ID, from a page on the API key's domain: the browser's Origin (or Referer) header must match the domain the key was created for. Non-browser clients can forge these headers. Each successful call counts toward usage. Integrate with the SDK rather than calling it directly.",
        "security": [
          {
            "publicKey": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/IdentificationRequest"
              },
              "example": {
                "visitorId": "fp3_e8fc80d60f8c3571",
                "components": {
                  "basic": {
                    "browser": "chrome",
                    "os": "macos",
                    "language": "en-US",
                    "platform": "MacIntel",
                    "cookieEnabled": true,
                    "screen": [
                      1440,
                      900
                    ],
                    "colorDepth": 30,
                    "timezone": "Europe/Berlin"
                  },
                  "hardware": {
                    "hardwareConcurrency": 8,
                    "maxTouchPoints": 0,
                    "deviceMemory": 8
                  },
                  "canvas": {
                    "hash": "example",
                    "samples": [
                      12480,
                      9216,
                      14111,
                      7340
                    ]
                  },
                  "webgl": {
                    "vendor": "WebKit",
                    "renderer": "WebKit WebGL",
                    "version": "WebGL 1.0",
                    "unmaskedVendor": "Apple Inc.",
                    "unmaskedRenderer": "Apple GPU"
                  },
                  "fonts": {
                    "detected": [
                      "Arial",
                      "Helvetica"
                    ],
                    "count": 2,
                    "hash": "example"
                  },
                  "automation": {
                    "webdriver": false
                  }
                },
                "confidence": 1
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Registered",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DetailedIdentification"
                }
              }
            }
          },
          "400": {
            "description": "Invalid request body or ID/component mismatch",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "VALIDATION_ERROR": {
                    "value": {
                      "error": "Invalid request body or ID/component mismatch",
                      "code": "VALIDATION_ERROR"
                    }
                  },
                  "ID_COMPONENT_MISMATCH": {
                    "value": {
                      "error": "Invalid request body or ID/component mismatch",
                      "code": "ID_COMPONENT_MISMATCH"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Invalid key or wrong domain",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "INVALID_KEY": {
                    "value": {
                      "error": "Invalid key or wrong domain",
                      "code": "INVALID_KEY"
                    }
                  },
                  "DOMAIN_MISMATCH": {
                    "value": {
                      "error": "Invalid key or wrong domain",
                      "code": "DOMAIN_MISMATCH"
                    }
                  }
                }
              }
            }
          },
          "402": {
            "description": "Subscription unpaid",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "PAYMENT_REQUIRED": {
                    "value": {
                      "error": "Subscription unpaid",
                      "code": "PAYMENT_REQUIRED"
                    }
                  }
                }
              }
            }
          },
          "409": {
            "description": "Request token reused with different data",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "IDEMPOTENCY_CONFLICT": {
                    "value": {
                      "error": "Request token reused with different data",
                      "code": "IDEMPOTENCY_CONFLICT"
                    }
                  }
                }
              }
            }
          },
          "413": {
            "description": "Signals too large",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "PAYLOAD_TOO_LARGE": {
                    "value": {
                      "error": "Signals too large",
                      "code": "PAYLOAD_TOO_LARGE"
                    }
                  }
                }
              }
            }
          },
          "429": {
            "description": "Rate limited, or the free allowance is used up",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "RATE_LIMITED": {
                    "value": {
                      "error": "Rate limited, or the free allowance is used up",
                      "code": "RATE_LIMITED"
                    }
                  },
                  "QUOTA_EXCEEDED": {
                    "value": {
                      "error": "Rate limited, or the free allowance is used up",
                      "code": "QUOTA_EXCEEDED"
                    }
                  }
                }
              }
            },
            "headers": {
              "Retry-After": {
                "description": "Seconds to wait before retrying.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/identifications/{requestId}": {
      "get": {
        "operationId": "getIdentification",
        "tags": [
          "Server API"
        ],
        "summary": "Look up an identification",
        "description": "Before trusting a visitor ID that the browser sent with a sign-up, login or payment, look up the requestId it came with: check that visitorId matches, createdAt is recent and domain is yours. Only successful identifications of the key's account are returned, for 90 days.",
        "security": [
          {
            "serverKey": []
          }
        ],
        "parameters": [
          {
            "name": "requestId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid",
              "description": "The ID of one registered identification.",
              "examples": [
                "3f8c2a1e-6b4d-4c7a-9e21-5d0f7b8a9c13"
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The identification",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ServerIdentification"
                }
              }
            }
          },
          "400": {
            "description": "Malformed ID",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "VALIDATION_ERROR": {
                    "value": {
                      "error": "Malformed ID",
                      "code": "VALIDATION_ERROR"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Missing, unknown or revoked server key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "INVALID_SERVER_KEY": {
                    "value": {
                      "error": "Missing, unknown or revoked server key",
                      "code": "INVALID_SERVER_KEY"
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "Called from a browser",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "BROWSER_REQUEST": {
                    "value": {
                      "error": "Called from a browser",
                      "code": "BROWSER_REQUEST"
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "Not in this account",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "NOT_FOUND": {
                    "value": {
                      "error": "Not in this account",
                      "code": "NOT_FOUND"
                    }
                  }
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "RATE_LIMITED": {
                    "value": {
                      "error": "Rate limited",
                      "code": "RATE_LIMITED"
                    }
                  }
                }
              }
            },
            "headers": {
              "Retry-After": {
                "description": "Seconds to wait before retrying.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/visitors/{visitorId}": {
      "get": {
        "operationId": "getVisitor",
        "tags": [
          "Server API"
        ],
        "summary": "Look up a visitor",
        "description": "When the account first and last saw a visitor, how often, and its latest 20 identifications.",
        "security": [
          {
            "serverKey": []
          }
        ],
        "parameters": [
          {
            "name": "visitorId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "pattern": "^(?:fp3_|(?:free_)?fp2_)[0-9a-f]{16}$",
              "description": "`fp3_` followed by 16 hex characters, computed by the TraceTail SDK (IDs from 2.x SDKs start with `fp2_`).",
              "examples": [
                "fp3_8f14e45fceea167a"
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The visitor",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Visitor"
                }
              }
            }
          },
          "400": {
            "description": "Malformed ID",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "VALIDATION_ERROR": {
                    "value": {
                      "error": "Malformed ID",
                      "code": "VALIDATION_ERROR"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Missing, unknown or revoked server key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "INVALID_SERVER_KEY": {
                    "value": {
                      "error": "Missing, unknown or revoked server key",
                      "code": "INVALID_SERVER_KEY"
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "Called from a browser",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "BROWSER_REQUEST": {
                    "value": {
                      "error": "Called from a browser",
                      "code": "BROWSER_REQUEST"
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "Not in this account",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "NOT_FOUND": {
                    "value": {
                      "error": "Not in this account",
                      "code": "NOT_FOUND"
                    }
                  }
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "RATE_LIMITED": {
                    "value": {
                      "error": "Rate limited",
                      "code": "RATE_LIMITED"
                    }
                  }
                }
              }
            },
            "headers": {
              "Retry-After": {
                "description": "Seconds to wait before retrying.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/setup-links": {
      "post": {
        "operationId": "createSetupLink",
        "tags": [
          "Setup links"
        ],
        "summary": "Create a setup link",
        "description": "Give the returned setupUrl to the person you work for: they sign in with an emailed code, then add a card or continue without a card. Then check the link with its token every few seconds; the first check after they finish returns the keys, once. Links work for 24 hours. No authentication; refused from browsers.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SetupLinkRequest"
              },
              "example": {
                "domains": [
                  "example.com",
                  "localhost"
                ],
                "serverKey": true
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The link, and the token to check it with (shown only now)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CreatedSetupLink"
                }
              }
            }
          },
          "400": {
            "description": "Invalid request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "VALIDATION_ERROR": {
                    "value": {
                      "error": "Invalid request",
                      "code": "VALIDATION_ERROR"
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "Called from a browser",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "BROWSER_REQUEST": {
                    "value": {
                      "error": "Called from a browser",
                      "code": "BROWSER_REQUEST"
                    }
                  }
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "RATE_LIMITED": {
                    "value": {
                      "error": "Rate limited",
                      "code": "RATE_LIMITED"
                    }
                  }
                }
              }
            },
            "headers": {
              "Retry-After": {
                "description": "Seconds to wait before retrying.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/setup-links/{setupLinkId}": {
      "get": {
        "operationId": "checkSetupLink",
        "tags": [
          "Setup links"
        ],
        "summary": "Check a setup link",
        "description": "Where the link stands. The first check after the person finishes returns an API key for each domain (null where the account already had one) and the server key if requested; later checks do not repeat them.",
        "security": [
          {
            "setupToken": []
          }
        ],
        "parameters": [
          {
            "name": "setupLinkId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "pattern": "^sl_[0-9a-f]{32}$",
              "description": "The setup link's id."
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The link",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SetupLink"
                }
              }
            }
          },
          "401": {
            "description": "Missing token",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "INVALID_TOKEN": {
                    "value": {
                      "error": "Missing token",
                      "code": "INVALID_TOKEN"
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "Called from a browser",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "BROWSER_REQUEST": {
                    "value": {
                      "error": "Called from a browser",
                      "code": "BROWSER_REQUEST"
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "Unknown link, or not its token",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "NOT_FOUND": {
                    "value": {
                      "error": "Unknown link, or not its token",
                      "code": "NOT_FOUND"
                    }
                  }
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "RATE_LIMITED": {
                    "value": {
                      "error": "Rate limited",
                      "code": "RATE_LIMITED"
                    }
                  }
                }
              }
            },
            "headers": {
              "Retry-After": {
                "description": "Seconds to wait before retrying.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          }
        }
      }
    },
    "/api/proxy/status": {
      "get": {
        "operationId": "getProxyStatus",
        "tags": [
          "Service"
        ],
        "summary": "Check a first-party proxy",
        "description": "What a first-party proxy's `<path>/health` answers: whether TraceTail accepts the proxy secret it sends.",
        "responses": {
          "200": {
            "description": "Proxy status",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProxyStatus"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "RATE_LIMITED": {
                    "value": {
                      "error": "Rate limited",
                      "code": "RATE_LIMITED"
                    }
                  }
                }
              }
            },
            "headers": {
              "Retry-After": {
                "description": "Seconds to wait before retrying.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          }
        }
      }
    },
    "/api/health": {
      "get": {
        "operationId": "getHealth",
        "tags": [
          "Service"
        ],
        "summary": "API health",
        "responses": {
          "200": {
            "description": "Healthy",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Health"
                }
              }
            }
          },
          "503": {
            "description": "Degraded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Health"
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "publicKey": {
        "type": "apiKey",
        "in": "header",
        "name": "X-API-Key",
        "description": "Public API key (`tt_` followed by 64 hex characters). It ships in page HTML and only works from pages on its domain. Also accepted as `Authorization: Bearer`."
      },
      "serverKey": {
        "type": "http",
        "scheme": "bearer",
        "description": "Secret server key (`tts_` followed by 64 hex characters) from Settings → API keys. Server-side only: requests with an Origin header are refused."
      },
      "setupToken": {
        "type": "http",
        "scheme": "bearer",
        "description": "The token creating the setup link returned (`tt_setup_` followed by 64 hex characters). Keep it to the agent: it collects the keys."
      }
    },
    "schemas": {
      "IdentificationRequest": {
        "type": "object",
        "required": [
          "visitorId",
          "components"
        ],
        "properties": {
          "visitorId": {
            "type": "string",
            "pattern": "^(?:fp3_|(?:free_)?fp2_)[0-9a-f]{16}$",
            "description": "`fp3_` followed by 16 hex characters, computed by the TraceTail SDK (IDs from 2.x SDKs start with `fp2_`).",
            "examples": [
              "fp3_8f14e45fceea167a"
            ]
          },
          "components": {
            "type": "object",
            "required": [
              "basic",
              "hardware",
              "canvas",
              "webgl",
              "fonts",
              "automation"
            ],
            "additionalProperties": true,
            "description": "Complete SDK v3 signals (at most 32 KB as UTF-8 JSON). Generate with the SDK; the server recomputes visitorId. Legacy v2 requests are accepted without verification."
          },
          "confidence": {
            "type": "number",
            "minimum": 0,
            "maximum": 1,
            "deprecated": true,
            "description": "Ignored; the server computes signal quality."
          },
          "requestToken": {
            "type": "string",
            "format": "uuid",
            "description": "SDK-generated retry token. Reuse only for the same logical request. Receipts last for the API log retention period."
          }
        }
      },
      "Identification": {
        "type": "object",
        "required": [
          "requestId",
          "visitorId",
          "confidence",
          "quality",
          "verification",
          "isBot"
        ],
        "properties": {
          "requestId": {
            "type": "string",
            "format": "uuid",
            "description": "The ID of one registered identification.",
            "examples": [
              "3f8c2a1e-6b4d-4c7a-9e21-5d0f7b8a9c13"
            ]
          },
          "visitorId": {
            "type": "string",
            "pattern": "^(?:fp3_|(?:free_)?fp2_)[0-9a-f]{16}$",
            "description": "`fp3_` followed by 16 hex characters, computed by the TraceTail SDK (IDs from 2.x SDKs start with `fp2_`).",
            "examples": [
              "fp3_8f14e45fceea167a"
            ]
          },
          "confidence": {
            "type": "number",
            "minimum": 0,
            "maximum": 1,
            "deprecated": true,
            "description": "Alias of quality.score, not match probability."
          },
          "quality": {
            "type": "object",
            "required": [
              "score",
              "reasons"
            ],
            "description": "Signal completeness, not match probability or physical-device uniqueness.",
            "properties": {
              "score": {
                "type": "number",
                "minimum": 0,
                "maximum": 1
              },
              "reasons": {
                "type": "array",
                "items": {
                  "type": "string"
                }
              }
            }
          },
          "verification": {
            "type": "string",
            "enum": [
              "consistent",
              "unverified"
            ],
            "description": "consistent means the server recomputed the ID from the signals. It does not authenticate a device. Legacy requests are unverified."
          },
          "isBot": {
            "type": "boolean",
            "description": "True for automated browsers: a headless user agent or navigator.webdriver."
          }
        }
      },
      "DetailedIdentification": {
        "allOf": [
          {
            "$ref": "#/components/schemas/Identification"
          },
          {
            "type": "object",
            "required": [
              "riskAssessment",
              "botDetection"
            ],
            "properties": {
              "riskAssessment": {
                "type": "object",
                "required": [
                  "level",
                  "score",
                  "signals"
                ],
                "properties": {
                  "level": {
                    "type": "string",
                    "enum": [
                      "low",
                      "medium",
                      "high"
                    ]
                  },
                  "score": {
                    "type": "number",
                    "minimum": 0,
                    "maximum": 1
                  },
                  "signals": {
                    "type": "array",
                    "items": {
                      "type": "object",
                      "required": [
                        "type",
                        "detail"
                      ],
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "headless_browser",
                            "webdriver",
                            "user_agent_mismatch",
                            "platform_mismatch",
                            "software_renderer",
                            "virtual_machine",
                            "insufficient_signals"
                          ]
                        },
                        "detail": {
                          "type": "string"
                        }
                      }
                    }
                  }
                }
              },
              "botDetection": {
                "type": "object",
                "required": [
                  "isBot",
                  "verdict",
                  "automationTools"
                ],
                "properties": {
                  "isBot": {
                    "type": "boolean"
                  },
                  "verdict": {
                    "type": "string",
                    "enum": [
                      "detected",
                      "not_detected",
                      "unknown"
                    ]
                  },
                  "automationTools": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          }
        ]
      },
      "ServerIdentification": {
        "type": "object",
        "required": [
          "visitorId",
          "requestId",
          "domain",
          "endpoint",
          "createdAt",
          "ipAddress",
          "userAgent",
          "browser",
          "os",
          "confidence",
          "riskScore",
          "quality",
          "verification",
          "isBot"
        ],
        "properties": {
          "visitorId": {
            "type": "string",
            "pattern": "^(?:fp3_|(?:free_)?fp2_)[0-9a-f]{16}$",
            "description": "`fp3_` followed by 16 hex characters, computed by the TraceTail SDK (IDs from 2.x SDKs start with `fp2_`).",
            "examples": [
              "fp3_8f14e45fceea167a"
            ]
          },
          "requestId": {
            "type": "string",
            "format": "uuid",
            "description": "The ID of one registered identification.",
            "examples": [
              "3f8c2a1e-6b4d-4c7a-9e21-5d0f7b8a9c13"
            ]
          },
          "domain": {
            "type": "string",
            "description": "The page host the identification came from."
          },
          "endpoint": {
            "type": "string",
            "enum": [
              "/api/id",
              "/api/id/detail"
            ]
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "ipAddress": {
            "type": [
              "string",
              "null"
            ]
          },
          "userAgent": {
            "type": [
              "string",
              "null"
            ]
          },
          "browser": {
            "type": [
              "string",
              "null"
            ],
            "description": "From the visitor's latest record."
          },
          "os": {
            "type": [
              "string",
              "null"
            ],
            "description": "From the visitor's latest record."
          },
          "confidence": {
            "type": [
              "number",
              "null"
            ],
            "description": "This request’s signal completeness. Null for historical events without an assessment."
          },
          "riskScore": {
            "type": [
              "number",
              "null"
            ],
            "description": "This request’s risk assessment. Null for historical events without an assessment."
          },
          "quality": {
            "anyOf": [
              {
                "type": "object",
                "required": [
                  "score",
                  "reasons"
                ],
                "description": "Signal completeness, not match probability or physical-device uniqueness.",
                "properties": {
                  "score": {
                    "type": "number",
                    "minimum": 0,
                    "maximum": 1
                  },
                  "reasons": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    }
                  }
                }
              },
              {
                "type": "null"
              }
            ]
          },
          "verification": {
            "type": "string",
            "enum": [
              "consistent",
              "unverified"
            ],
            "description": "consistent means the server recomputed the ID from the signals. It does not authenticate a device. Legacy requests are unverified."
          },
          "isBot": {
            "type": [
              "boolean",
              "null"
            ]
          }
        }
      },
      "Visitor": {
        "type": "object",
        "required": [
          "visitorId",
          "firstSeenAt",
          "lastSeenAt",
          "visits",
          "browser",
          "os",
          "confidence",
          "riskScore",
          "recentIdentifications"
        ],
        "properties": {
          "visitorId": {
            "type": "string",
            "pattern": "^(?:fp3_|(?:free_)?fp2_)[0-9a-f]{16}$",
            "description": "`fp3_` followed by 16 hex characters, computed by the TraceTail SDK (IDs from 2.x SDKs start with `fp2_`).",
            "examples": [
              "fp3_8f14e45fceea167a"
            ]
          },
          "firstSeenAt": {
            "type": "string",
            "format": "date-time"
          },
          "lastSeenAt": {
            "type": "string",
            "format": "date-time"
          },
          "visits": {
            "type": "integer",
            "minimum": 0,
            "description": "Successful identifications in the last 90 days."
          },
          "browser": {
            "type": [
              "string",
              "null"
            ],
            "description": "From the visitor's latest record."
          },
          "os": {
            "type": [
              "string",
              "null"
            ],
            "description": "From the visitor's latest record."
          },
          "confidence": {
            "type": [
              "number",
              "null"
            ]
          },
          "riskScore": {
            "type": [
              "number",
              "null"
            ]
          },
          "recentIdentifications": {
            "type": "array",
            "maxItems": 20,
            "description": "Newest first.",
            "items": {
              "type": "object",
              "required": [
                "requestId",
                "domain",
                "endpoint",
                "createdAt",
                "ipAddress",
                "userAgent"
              ],
              "properties": {
                "requestId": {
                  "type": "string",
                  "format": "uuid",
                  "description": "The ID of one registered identification.",
                  "examples": [
                    "3f8c2a1e-6b4d-4c7a-9e21-5d0f7b8a9c13"
                  ]
                },
                "domain": {
                  "type": "string",
                  "description": "The page host the identification came from."
                },
                "endpoint": {
                  "type": "string",
                  "enum": [
                    "/api/id",
                    "/api/id/detail"
                  ]
                },
                "createdAt": {
                  "type": "string",
                  "format": "date-time"
                },
                "ipAddress": {
                  "type": [
                    "string",
                    "null"
                  ]
                },
                "userAgent": {
                  "type": [
                    "string",
                    "null"
                  ]
                }
              }
            }
          }
        }
      },
      "ProxyStatus": {
        "oneOf": [
          {
            "type": "object",
            "required": [
              "proxy",
              "domain",
              "visitorIp"
            ],
            "properties": {
              "proxy": {
                "const": "trusted"
              },
              "domain": {
                "type": "string"
              },
              "visitorIp": {
                "type": [
                  "string",
                  "null"
                ]
              }
            }
          },
          {
            "type": "object",
            "required": [
              "proxy",
              "reason"
            ],
            "properties": {
              "proxy": {
                "const": "untrusted"
              },
              "reason": {
                "type": "string",
                "enum": [
                  "no-secret",
                  "unknown-secret"
                ]
              }
            }
          }
        ]
      },
      "Health": {
        "type": "object",
        "required": [
          "status",
          "timestamp",
          "version",
          "checks"
        ],
        "properties": {
          "status": {
            "type": "string",
            "enum": [
              "healthy",
              "degraded"
            ]
          },
          "timestamp": {
            "type": "string",
            "format": "date-time"
          },
          "version": {
            "type": "string"
          },
          "checks": {
            "type": "object",
            "additionalProperties": {
              "type": "string"
            }
          }
        }
      },
      "SetupLinkRequest": {
        "type": "object",
        "required": [
          "domains"
        ],
        "additionalProperties": false,
        "properties": {
          "domains": {
            "type": "array",
            "minItems": 1,
            "maxItems": 5,
            "items": {
              "type": "string",
              "examples": [
                "example.com",
                "localhost"
              ]
            },
            "description": "The sites to create API keys for. URLs are reduced to their domain; add localhost for local development."
          },
          "email": {
            "type": "string",
            "format": "email",
            "description": "The person's email, if known: it prefills the page."
          },
          "serverKey": {
            "type": "boolean",
            "description": "Also create a server key, so the backend can verify identifications."
          }
        }
      },
      "SetupLink": {
        "type": "object",
        "required": [
          "id",
          "status",
          "setupUrl",
          "domains",
          "expiresAt",
          "next"
        ],
        "properties": {
          "id": {
            "type": "string",
            "pattern": "^sl_[0-9a-f]{32}$",
            "description": "The setup link's id."
          },
          "status": {
            "type": "string",
            "enum": [
              "pending",
              "completed",
              "delivered",
              "expired"
            ],
            "description": "delivered: the keys were handed over (in this response, or an earlier one)."
          },
          "setupUrl": {
            "type": "string",
            "format": "uri",
            "description": "The page to give the person."
          },
          "domains": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "expiresAt": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "When the person, then the agent, runs out of time."
          },
          "next": {
            "type": "string",
            "description": "What to do next, in words."
          },
          "account": {
            "type": "object",
            "required": [
              "email",
              "paidUsage"
            ],
            "properties": {
              "email": {
                "type": "string",
                "description": "Masked (j***@example.com), for the person to check the account is theirs."
              },
              "paidUsage": {
                "type": "boolean",
                "description": "Metered usage billing is active."
              },
              "billingExempt": {
                "type": "boolean",
                "description": "When true, identification requests are unlimited and free."
              }
            },
            "description": "With the keys: the account they belong to."
          },
          "apiKeys": {
            "type": "array",
            "description": "With the keys, once: one per domain. apiKey is null where the account already had a key for it.",
            "items": {
              "type": "object",
              "required": [
                "domain",
                "apiKey"
              ],
              "properties": {
                "domain": {
                  "type": "string"
                },
                "apiKey": {
                  "type": [
                    "string",
                    "null"
                  ],
                  "pattern": "^tt_[0-9a-f]{64}$"
                }
              }
            }
          },
          "serverKey": {
            "type": [
              "string",
              "null"
            ],
            "pattern": "^tts_[0-9a-f]{64}$",
            "description": "With the keys, once, if requested and the account was created through the link (an existing account creates its own)."
          }
        }
      },
      "CreatedSetupLink": {
        "allOf": [
          {
            "$ref": "#/components/schemas/SetupLink"
          },
          {
            "type": "object",
            "required": [
              "token"
            ],
            "properties": {
              "token": {
                "type": "string",
                "pattern": "^tt_setup_[0-9a-f]{64}$",
                "description": "Checks the link. Shown only now."
              }
            }
          }
        ]
      },
      "Error": {
        "type": "object",
        "required": [
          "error",
          "code"
        ],
        "properties": {
          "error": {
            "type": "string",
            "description": "What went wrong, for people."
          },
          "code": {
            "type": "string",
            "description": "What went wrong, for code."
          }
        }
      }
    }
  }
}
