Every API here is available over the APIs.io API and to AI agents over MCP.
MCP server
One button, every client — Claude, Cursor, VS Code and the rest.
https://apis.io/mcp
Tools for apis
7 MCP tools reach this
find_apisBrowse and filter every API in the catalog.
get_api_artifactsOne API's artifacts, grouped by type.
get_openapiThe primary OpenAPI for this API.
find_similar_apisAPIs that look like this one.
apis_io_searchSTART HERE — APIs, providers and tags for one query, each with its total.
resolveTurn a domain, URL or GitHub org into the provider it belongs to.
find_cohortsEvery scored population of providers in the catalog.
All 92 tools →
Call it yourself
curl for this page
This API
curl "https://apis.io/api/v1/apis/hootlens-com:hootlens-com-sites-api"
All apis
curl "https://apis.io/api/v1/apis?limit=25"
Calls without X-API-Key need no key. Calls with it need a paid plan — get a key.
Get an API key
Free tier, no form to fill in. Signing in shares your email address with us — we
store it to create your key and to recognise you if you sign in with another
provider. See our Privacy Policy and
Terms.
A second provider on the same verified email joins the account you already have.
openapi: 3.2.0
info:
title: Hoot Lens Sites API
version: v1
summary: Read website interaction evidence and log changes.
description: 'The Hoot Lens REST API records website interaction evidence for the sites you add and returns it as visits, recordings, heatmaps, goals, funnels and change impact. This document describes the endpoints a person or an agent uses to read that evidence and to log changes. Everything returned is observed behavior with its evidence; the API makes no claims about visitors'' emotions or intent.
Authentication. Send `Authorization: Bearer `. The credential is either a Firebase ID token (a signed-in person, whose role on each site decides what they may do) or a personal access token (`hl_pat_...`) made in the dashboard. A personal access token carries the scopes `read`, `replay` and `annotate`, belongs to one site, expires in 30, 60 or 90 days, and can do no more than its creator''s current role allows. Operations marked `x-hootlens-sign-in-only` need a signed-in person and answer 403 to a token.
OAuth. The hosted MCP endpoint (`/mcp`, Streamable HTTP) signs assistants in with OAuth 2.1 authorization code and PKCE (S256), with discovery at `/.well-known/oauth-protected-resource` and `/.well-known/oauth-authorization-server`. Its access tokens (`hl_oat_...`) are bound to the MCP resource and are accepted only there; sending one to any route in this document answers 401. Use a personal access token or a Firebase ID token for these routes.
Not described here. Collector intake, public tracker configuration, billing, OAuth, sharing links, issues and feedback, invitations, members, access tokens, notifications and the audit log also exist; see docs/API.md for them.
Site (project) IDs are public. Authorization is enforced by the API on every request. Rate limits answer 429. A caller who is not a member of a site receives 404, so a site''s existence is not revealed.'
contact:
name: Parallel Platforms
url: https://hootlens.com
servers:
- url: https://hootlens-api-368877148687.us-east1.run.app
description: Production
security:
- bearerAuth: []
tags:
- name: Sites
paths:
/v1/projects:
get:
tags:
- Sites
summary: List sites
description: 'Sites (projects in the API) the caller owns or belongs to, each with its role and the permissions it gives there. A personal access token sees only its own site.
Required token scopes are listed in x-hootlens-scopes. A signed-in person needs the matching permission on the site instead (the viewer and editor roles are presets of the permissions).'
operationId: listProjects
responses:
'200':
description: The sites.
content:
application/json:
schema:
type: object
properties:
projects:
type: array
items:
$ref: '#/components/schemas/ProjectWithRole'
required:
- projects
additionalProperties: true
'401':
$ref: '#/components/responses/Unauthorized'
'403':
$ref: '#/components/responses/Forbidden'
'404':
$ref: '#/components/responses/NotFound'
'429':
$ref: '#/components/responses/TooManyRequests'
security:
- bearerAuth: []
x-hootlens-scopes:
- read
post:
tags:
- Sites
summary: Create a site
description: 'Adds a website to the authenticated account, within its current site allowance. A verified signed-in account owner may create directly. An OAuth agent must have explicitly approved sites:create and all-project access; site-bound personal access tokens and demo readers receive 403. Third-party OAuth tokens call through the hosted MCP endpoint, while first-party local MCP/CLI sign-in can call REST directly. Agents start with balanced privacy (strict is also allowed) and required consent. Agents cannot request full privacy or implied consent. Origins reject credentials, query parameters and fragments. Account IDs and owner IDs are not accepted in the body. Idempotency-Key is required for OAuth agents and optional for people. For 24 hours the same account, key and normalized settings return the existing site without consuming another slot; different settings return 409. Expired keys may create a new site. Revocation and current email verification are checked before a replay. Idempotency-Replayed is true on replay and false on new creation. Site creation produces configuration and does not prove a tracking tag has been installed.
Required token scopes are listed in x-hootlens-scopes. A signed-in person needs the matching permission on the site instead (the viewer and editor roles are presets of the permissions).'
operationId: createProject
parameters:
- name: Idempotency-Key
in: header
required: false
description: Required for OAuth agents. Reuse the same key and settings when retrying within 24 hours.
schema:
type: string
minLength: 8
maxLength: 128
pattern: ^[!-~]{8,128}$
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/ProjectCreate'
responses:
'200':
description: The existing site for this account and request key.
content:
application/json:
schema:
type: object
properties:
project:
$ref: '#/components/schemas/ProjectWithRole'
required:
- project
additionalProperties: true
'201':
description: The new site.
content:
application/json:
schema:
type: object
properties:
project:
$ref: '#/components/schemas/ProjectWithRole'
required:
- project
additionalProperties: true
'400':
$ref: '#/components/responses/BadRequest'
'401':
$ref: '#/components/responses/Unauthorized'
'402':
$ref: '#/components/responses/PaymentRequired'
'403':
$ref: '#/components/responses/Forbidden'
'404':
$ref: '#/components/responses/NotFound'
'409':
$ref: '#/components/responses/Conflict'
'429':
$ref: '#/components/responses/TooManyRequests'
security:
- bearerAuth: []
x-hootlens-scopes:
- sites:create
/v1/projects/{projectId}:
patch:
tags:
- Sites
summary: Update site settings
description: 'Changes the name, allowed origins, preview origin patterns, privacy policy, consent mode, sample rate, page targeting, per-path privacy presets (pathPresets), page groups, grouping, retention, AI explanations, JavaScript error recording or internal traffic setting. The owner may change every field; an editor may change only pageGroups and grouping. Personal access tokens and connected apps receive 403, whatever their scopes: agents use the settings changes below, which can only make privacy stricter.
Required token scopes are listed in x-hootlens-scopes. A signed-in person needs the matching permission on the site instead (the viewer and editor roles are presets of the permissions).'
operationId: updateProject
parameters:
- $ref: '#/components/parameters/projectId'
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/ProjectUpdate'
responses:
'200':
description: The updated site.
content:
application/json:
schema:
type: object
properties:
project:
$ref: '#/components/schemas/ProjectWithRole'
required:
- project
additionalProperties: true
'400':
$ref: '#/components/responses/BadRequest'
'401':
$ref: '#/components/responses/Unauthorized'
'402':
$ref: '#/components/responses/PaymentRequired'
'403':
$ref: '#/components/responses/Forbidden'
'404':
$ref: '#/components/responses/NotFound'
'429':
$ref: '#/components/responses/TooManyRequests'
security:
- bearerAuth: []
x-hootlens-scopes: []
x-hootlens-sign-in-only: true
components:
schemas:
Goal:
allOf:
- oneOf:
- oneOf:
- type: object
properties:
kind:
type: string
const: click
name:
type: string
minLength: 1
maxLength: 60
by:
type: string
const: selector
match:
type: string
minLength: 1
maxLength: 200
required:
- kind
- name
- by
- match
additionalProperties: false
- type: object
properties:
kind:
type: string
const: click
name:
type: string
minLength: 1
maxLength: 60
by:
type: string
const: hootId
match:
type: string
pattern: ^[a-zA-Z0-9_-]{1,80}$
required:
- kind
- name
- by
- match
additionalProperties: false
- type: object
properties:
kind:
type: string
const: click
name:
type: string
minLength: 1
maxLength: 60
by:
type: string
const: label
match:
type: string
minLength: 2
maxLength: 200
required:
- kind
- name
- by
- match
additionalProperties: false
- type: object
properties:
kind:
type: string
const: page
name:
type: string
minLength: 1
maxLength: 60
match:
type: string
required:
- kind
- name
- match
additionalProperties: false
- type: object
properties:
kind:
type: string
const: event
name:
type: string
minLength: 1
maxLength: 60
match:
type: string
pattern: ^[a-z][a-z0-9_]{0,63}$
confirmation:
type: string
enum:
- browser
- server
required:
- kind
- name
- match
- confirmation
additionalProperties: false
- type: object
properties:
id:
type: string
pattern: ^[a-zA-Z0-9_-]{1,80}$
since:
type: integer
minimum: 0
maximum: 9007199254740991
required:
- id
- since
additionalProperties: false
Error:
type: object
properties:
error:
type: string
description: A short message safe to show to a person.
code:
type: string
description: A stable machine-readable code, on some errors.
required:
- error
additionalProperties: true
ProjectCreate:
type: object
properties:
name:
type: string
minLength: 1
maxLength: 80
origins:
minItems: 1
maxItems: 20
type: array
items:
type: string
format: uri
privacy:
type: object
properties:
preset:
type: string
enum:
- full
- balanced
- strict
required:
- preset
consent:
type: string
enum:
- implied
- required
replayAssets:
type: object
properties:
preserve:
default: true
type: boolean
origins:
default: []
maxItems: 20
type: array
items:
type: string
maxLength: 300
additionalProperties: false
required:
- name
- origins
additionalProperties: false
Funnel:
type: object
properties:
name:
type: string
minLength: 1
maxLength: 60
steps:
minItems: 2
maxItems: 8
type: array
items:
type: object
properties:
goalId:
type: string
pattern: ^[a-zA-Z0-9_-]{1,80}$
required:
- goalId
additionalProperties: false
id:
type: string
pattern: ^[a-zA-Z0-9_-]{1,80}$
createdAt:
type: integer
minimum: 0
maximum: 9007199254740991
required:
- name
- steps
- id
- createdAt
additionalProperties: false
PathPreset:
type: object
description: safe-form records clicks and steps but never what a visitor typed, even on the full preset. balanced and strict apply at least that preset on matching pages.
properties:
match:
type: string
description: A page path pattern in the page targeting syntax.
preset:
type: string
enum:
- safe-form
- balanced
- strict
required:
- match
- preset
additionalProperties: true
ElementDisplayName:
type: object
properties:
key:
type: string
description: The element's selector, as findings and heatmaps list it.
name:
type: string
description: What the team calls the element.
updatedAt:
type: integer
description: Epoch milliseconds.
minimum: 0
updatedBy:
type: string
description: The user who last set it.
required:
- key
- name
- updatedAt
additionalProperties: true
ProjectUpdate:
type: object
properties:
name:
type: string
minLength: 1
maxLength: 80
origins:
minItems: 1
maxItems: 20
type: array
items:
type: string
format: uri
previewOrigins:
maxItems: 10
type: array
items:
type: string
maxLength: 300
privacy:
type: object
properties:
preset:
type: string
enum:
- full
- balanced
- strict
maskTextSelectors:
default: []
maxItems: 50
type: array
items:
type: string
minLength: 1
maxLength: 200
blockSelectors:
default: []
maxItems: 50
type: array
items:
type: string
minLength: 1
maxLength: 200
unmaskInputSelectors:
default: []
maxItems: 20
type: array
items:
type: string
minLength: 1
maxLength: 200
captureQueryStrings:
default: false
type: boolean
keepCampaignDigits:
type: boolean
required:
- preset
replayAssets:
type: object
properties:
preserve:
default: true
type: boolean
origins:
default: []
maxItems: 20
type: array
items:
type: string
maxLength: 300
additionalProperties: false
consent:
type: string
enum:
- implied
- required
- regional
consentRegions:
maxItems: 60
type: array
items:
type: string
maxLength: 8
sampleRate:
type: number
minimum: 0
maximum: 1
targeting:
type: object
properties:
include:
default: []
maxItems: 50
type: array
items:
type: string
exclude:
default: []
maxItems: 50
type: array
items:
type: string
pathPresets:
maxItems: 20
type: array
items:
type: object
properties:
match:
type: string
preset:
type: string
enum:
- safe-form
- balanced
- strict
required:
- match
- preset
pageGroups:
maxItems: 50
type: array
items:
type: object
properties:
id:
type: string
pattern: ^[a-z0-9][a-z0-9-]{0,39}$
name:
type: string
minLength: 1
maxLength: 60
match:
minItems: 1
maxItems: 20
type: array
items:
type: string
required:
- id
- name
- match
grouping:
type: object
properties:
localePrefixes:
maxItems: 300
type: array
items:
type: string
pattern: ^[a-z]{2,3}(?:-[a-z]{2,4})?$
variantParams:
maxItems: 20
type: array
items:
type: string
pattern: ^[A-Za-z0-9_.-]{1,40}$
pageVersionMode:
type: string
enum:
- release
- content
retentionDays:
type: integer
minimum: 1
maximum: 90
ai:
type: object
properties:
explanations:
type: boolean
required:
- explanations
internalTraffic:
type: object
properties:
browser:
type: boolean
ips:
default: []
maxItems: 30
type: array
items:
type: string
maxLength: 60
required:
- browser
errorCapture:
type: boolean
Id:
type: string
description: 'An identifier: 1 to 80 letters, digits, underscores or hyphens.'
pattern: ^[a-zA-Z0-9_-]{1,80}$
ProjectWithRole:
allOf:
- $ref: '#/components/schemas/Project'
- type: object
properties:
role:
type: string
enum:
- owner
- editor
- viewer
- custom
permissions:
type: array
items:
type: string
enum:
- read
- replay
- annotate
- configure
- tokens
- member
- invite
- conversions:write
- manage
required:
- role
- permissions
additionalProperties: true
Project:
type: object
properties:
id:
$ref: '#/components/schemas/Id'
name:
type: string
origins:
type: array
items:
type: string
format: uri
ownerId:
type: string
createdAt:
type: integer
description: Epoch milliseconds.
minimum: 0
retentionDays:
type: integer
privacy:
type: object
description: The privacy policy. Absent on sites created before configurable privacy, which are strict.
properties:
preset:
type: string
enum:
- full
- balanced
- strict
keepCampaignDigits:
type: boolean
description: Keep digits in utm_ values. Off when absent.
required:
- preset
additionalProperties: true
internalTraffic:
type: object
description: How the site's own team is told apart from visitors. Absent when nobody is.
properties:
browser:
type: boolean
description: Visits from browsers that opened the internal link are internal.
ips:
type: array
items:
type: string
description: Listed IP addresses or ranges. Only the owner sees them.
required:
- browser
- ips
additionalProperties: true
consent:
type: string
enum:
- implied
- required
- regional
description: Absent on sites created before configurable consent, which require consent.
previewOrigins:
type: array
items:
type: string
description: Host patterns for pull request previews, such as https://deploy-preview-*--mysite.netlify.app. They may send test visits only. Absent means none.
consentRegions:
type: array
items:
type: string
sampleRate:
type: number
description: 0 to 1.
ai:
type: object
properties:
explanations:
type: boolean
required:
- explanations
additionalProperties: true
errorCapture:
type: boolean
description: Whether the site records JavaScript errors. On for sites created since the setting was added; absent (off) on earlier sites until the owner turns it on.
replayAssets:
type: object
description: Public asset preservation for new recordings. Existing sites without this field use live assets until an owner enables preservation.
properties:
preserve:
type: boolean
origins:
type: array
items:
type: string
required:
- preserve
- origins
additionalProperties: true
targeting:
type: object
description: Page paths to record. Absent records every path.
properties:
include:
type: array
items:
type: string
exclude:
type: array
items:
type: string
required:
- include
- exclude
additionalProperties: true
pathPresets:
type: array
items:
$ref: '#/components/schemas/PathPreset'
description: Per-path privacy presets. They only ever make capture stricter than the site preset. Absent means none.
pageGroups:
type: array
items:
type: object
properties:
id:
type: string
name:
type: string
match:
type: array
items:
type: string
additionalProperties: true
grouping:
type: object
description: Locale prefix, experiment parameter and page version mode overrides.
properties: {}
additionalProperties: true
goals:
type: array
items:
$ref: '#/components/schemas/Goal'
funnels:
type: array
items:
$ref: '#/components/schemas/Funnel'
savedSegments:
type: array
items:
type: object
properties:
id:
type: string
name:
type: string
additionalProperties: true
elementNames:
type: array
items:
$ref: '#/components/schemas/ElementDisplayName'
updatedAt:
type: integer
description: Epoch milliseconds.
minimum: 0
required:
- id
- name
- origins
- createdAt
- retentionDays
additionalProperties: true
responses:
Unauthorized:
description: The credential is missing, expired or revoked.
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
PaymentRequired:
description: The site (project) or account plan does not allow this.
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
Conflict:
description: The request conflicts with the current state of the record.
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
NotFound:
description: The site, or the record inside it, does not exist or the caller is not a member (a non-member cannot tell the two apart).
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
TooManyRequests:
description: A rate limit or a per-site limit was reached.
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
Forbidden:
description: The credential or the person's permissions on the site do not permit this action.
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
BadRequest:
description: The request fields are invalid.
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
parameters:
projectId:
name: projectId
in: path
required: true
description: The project ID. Project IDs are public identifiers, not secrets.
schema:
$ref: '#/components/schemas/Id'
securitySchemes:
bearerAuth:
type: http
scheme: bearer
description: A Firebase ID token, or a personal access token (hl_pat_...) with the scopes read, replay, annotate, configure, conversions:write. OAuth access tokens (hl_oat_...) are for the hosted MCP resource only.
externalDocs:
description: Full API reference, including endpoints not described here
url: https://hootlens.com/docs/API.md