openapi: 3.2.0 info: title: Ironfang Analytics Sessions API version: 0.1.0 summary: Sites, origins, public keys and capture settings for session replay and web analytics. description: 'Ironfang Analytics follows each visit to your website as a session: the pages a visitor saw, in every tab, with how long they stayed, the errors and failed requests they met, the events your pages tracked, and the recordings that let you replay it, live or afterwards.' contact: name: Ironfang email: hello@ironfang.uk url: https://ironfang.com/contact termsOfService: https://ironfang.com/legal/terms license: name: Proprietary - use governed by the Ironfang terms of service url: https://ironfang.com/legal/terms servers: - url: https://api.ironfang.com/analytics security: - apiKey: [] - bearerAuth: [] tags: - name: Sessions description: 'One browser''s visit to one site: every tab, from the first page until thirty minutes pass without the visitor doing anything, or twelve hours from its start. A session holds its pages, its events and its recordings, and stays readable when recording is off or its recordings have expired. Sessions keep the visitor''s IP address, user agent and visitor id for the site''s recording retention, then lose them, and the rest for its session retention.' paths: /v1/sessions: get: tags: - Sessions operationId: listSessions summary: Search sessions description: Returns the organisation's sessions across its sites, newest first by when they started, as a bounded keyset page. Every filter is optional and they combine. Needs analytics.sessions.read. parameters: - $ref: '#/components/parameters/Cursor' - $ref: '#/components/parameters/Limit' - name: site_id in: query required: false schema: type: string format: uuid - name: from in: query required: false description: Started at or after this time. schema: type: string format: date-time - name: to in: query required: false description: Started before this time. schema: type: string format: date-time - name: state in: query required: false description: live (heard from in the last 90 seconds), active (not ended) or ended. schema: type: string enum: - live - active - ended - name: country in: query required: false description: Two-letter country code, upper case. schema: type: string pattern: ^[A-Z]{2}$ - name: device in: query required: false schema: type: string example: mobile - name: browser in: query required: false schema: type: string example: chrome - name: os in: query required: false schema: type: string example: windows - name: ip in: query required: false description: One address, or a network in CIDR notation. schema: type: string example: 203.0.113.0/24 - name: entry_path in: query required: false description: Entry paths starting with this. schema: type: string example: /apply - name: visited_path in: query required: false description: Sessions that visited this page, by its path without a query string or fragment. schema: type: string example: /pricing - name: visitor_id in: query required: false description: One browser's sessions. schema: type: string format: uuid - name: recording in: query required: false description: with, sessions with a recording that can be played; without, the rest. schema: type: string enum: - with - without - name: diagnostics in: query required: false description: Sessions with errors (uncaught, unhandled rejections or console errors), with failed requests (an error answer, no answer, a timeout or a resource that did not load; not an aborted one), or with either (any). schema: type: string enum: - errors - requests - any responses: '200': description: A page of sessions content: application/json: schema: type: object required: - sessions - next_cursor properties: sessions: type: array items: $ref: '#/components/schemas/SessionRow' next_cursor: type: string description: Opaque cursor for the next page; empty on the last page. '400': $ref: '#/components/responses/InvalidQuery' '401': $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/Forbidden' /v1/sessions/{sessionId}: parameters: - $ref: '#/components/parameters/SessionId' get: tags: - Sessions operationId: getSession summary: Get a session description: One session with what its browser reported, its journey (every page in the order the visitor saw them, with how long they stayed) and its recordings, each page placed in the recording that saw it. Needs analytics.sessions.read. responses: '200': description: The session content: application/json: schema: $ref: '#/components/schemas/SessionDetail' '401': $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/Forbidden' '404': $ref: '#/components/responses/NotFound' delete: tags: - Sessions operationId: deleteSession summary: Delete a session description: 'Erases the session: its recordings'' stored batches, then the session with its pages, events, recordings, IP address, user agent and every other detail. Recordings already counted stay counted. Needs analytics.sessions.delete.' responses: '204': description: Session deleted '401': $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/Forbidden' '404': $ref: '#/components/responses/NotFound' '503': description: This deployment cannot delete sessions yet (`deletion_unavailable`). content: application/json: schema: $ref: '#/components/schemas/Error' /v1/sessions/{sessionId}/events: parameters: - $ref: '#/components/parameters/SessionId' get: tags: - Sessions operationId: getSessionEvents summary: Get a session's timeline description: 'Everything that happened in the session: its pages, events, and recordings starting and ending, in the order they happened by the browser''s own clock (which every tab of a session shares), then each tab''s own order; never in the order they arrived. A session holds at most 300 pages and 1,000 events, so the timeline comes whole. Needs analytics.sessions.read.' parameters: - name: type in: query required: false description: A comma list of page, visibility, error, request, custom and recording. schema: type: string example: error,request responses: '200': description: The timeline content: application/json: schema: type: object required: - events properties: events: type: array items: $ref: '#/components/schemas/TimelineItem' '400': $ref: '#/components/responses/InvalidQuery' '401': $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/Forbidden' '404': $ref: '#/components/responses/NotFound' /v1/sessions/{sessionId}/heatmap: parameters: - $ref: '#/components/parameters/SessionId' get: tags: - Sessions operationId: getSessionHeatmap summary: Get a session's heatmap description: Attention, scroll reach, clicks and movement on one page from this session's visits alone, every tab's, drawn over its own recording; the same figures as a site's heatmap. Heatmaps come from recordings, extracted once each ends, so a page the session did not record, or whose recording is still open, expired or deleted, has none. Needs analytics.sessions.read. parameters: - name: path in: query required: false description: The page; without one, the first page the session came to. schema: type: string example: /pricing - name: bucket in: query required: false description: The device; without one, the one the session saw the page on most. schema: $ref: '#/components/schemas/HeatmapBucket' responses: '200': description: The session's heatmap content: application/json: schema: $ref: '#/components/schemas/SessionHeatmap' '400': $ref: '#/components/responses/InvalidQuery' '401': $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/Forbidden' '404': $ref: '#/components/responses/NotFound' components: parameters: Cursor: name: cursor in: query required: false description: The next_cursor of the previous page. schema: type: string Limit: name: limit in: query required: false schema: type: integer minimum: 1 maximum: 100 default: 50 SessionId: name: sessionId in: path required: true schema: type: string format: uuid responses: Forbidden: description: The credential lacks the permission or scope, names another organisation, or belongs to a partner integration that is not active (`forbidden`, `insufficient_scope`, `integration_inactive`). content: application/json: schema: $ref: '#/components/schemas/Error' InvalidQuery: description: An unknown, repeated or malformed query parameter (`invalid_query`). content: application/json: schema: $ref: '#/components/schemas/Error' Unauthorized: description: No credential, an invalid one, an expired partner integration key, or a delegated token where none is accepted (`unauthorized`, `invalid_api_key`, `api_key_expired`, `delegated_not_allowed`). content: application/json: schema: $ref: '#/components/schemas/Error' NotFound: description: No such object in this organisation (`not_found`). content: application/json: schema: $ref: '#/components/schemas/Error' schemas: Heatmap: type: object required: - path - bucket - from - to - visits - sampled - small_sample - visibility_known - seconds_on_screen - layouts - background - backgrounds - bands - clicks - moves properties: path: type: string bucket: $ref: '#/components/schemas/HeatmapBucket' from: type: string format: date-time to: type: string format: date-time visits: type: integer description: Page visits in the period. sampled: type: integer description: The visits the totals come from, at most 20000. small_sample: type: boolean description: Fewer than 30 visits. visibility_known: type: number description: The share of visits whose time excludes a hidden tab (SDK 0.5.0). seconds_on_screen: type: object required: - mean - median properties: mean: type: number median: type: number layouts: type: array description: The layouts seen, most common first. items: type: object required: - hash - visits properties: hash: type: - string - 'null' visits: type: integer background: description: The visit whose page at load the heatmap is drawn over; play it through the playback routes. anyOf: - $ref: '#/components/schemas/HeatmapBackground' - type: 'null' backgrounds: type: array description: 'The visits the heatmap can be drawn over, background first: of the most common layout, at a width near the period''s median, and of those the ones that scrolled furthest. At most 5.' items: $ref: '#/components/schemas/HeatmapBackground' bands: type: object required: - px - attention_share - ms_per_visit - reach properties: px: type: integer description: The height of a band. attention_share: type: array items: type: number description: Per band from the top, the share of a visit's time it was on screen. ms_per_visit: type: array items: type: number reach: type: array items: type: number description: Per band, the share of visits that scrolled to its bottom. clicks: type: object required: - per_visit - fixed_per_visit - cell_px - cells - fixed_cells - elements properties: per_visit: type: number fixed_per_visit: type: number description: Clicks on fixed or sticky elements, which belong to the screen rather than a place on the page. cell_px: type: integer cells: type: array items: $ref: '#/components/schemas/HeatmapCell' fixed_cells: type: array items: $ref: '#/components/schemas/HeatmapCell' elements: type: array description: The targets most clicked, named without text. items: type: object required: - selector - clicks - per_visit - fixed - located - offsets - cells properties: selector: type: string clicks: type: integer per_visit: type: number fixed: type: boolean description: Whether most of its clicks landed on it while fixed or sticky. located, offsets and cells cover those clicks only. located: type: integer description: How many of its clicks carry where within the target they landed (SDK 0.5.0). offsets: type: array description: A sample of the located clicks, where within the target each landed, 0 to 10000 across and down. items: type: array items: type: integer minItems: 2 maxItems: 2 cells: type: array description: The located clicks in the page grid (the fixed grid when fixed), per visit. Drawing them inside the target's box instead, take these out of cells or fixed_cells so they are not counted twice. items: $ref: '#/components/schemas/HeatmapCell' moves: type: object required: - cell_px - cells properties: cell_px: type: integer cells: type: array items: $ref: '#/components/schemas/HeatmapCell' description: Milliseconds per visit the pointer rested in each cell. SessionRecording: type: object required: - id - tab - state - end_reason - playable - live - status - discarded - created_at - ended_at - from - until - duration_ms - expires_at - entry_path - pages properties: id: type: string format: uuid tab: type: integer description: 0 when the recorder did not name its tab (before SDK 0.6.0). state: type: string enum: - pending - recording - ready - partial - unplayable end_reason: type: - string - 'null' enum: - finished - idle_timeout - duration_cap - byte_cap - site_disabled - null playable: type: boolean live: type: boolean status: type: string enum: - available - recording - expired - deleted - discarded - unplayable discarded: type: - string - 'null' enum: - empty - no_interaction - too_short - null description: Why it was not kept. created_at: type: string format: date-time ended_at: type: - string - 'null' format: date-time from: type: - string - 'null' format: date-time description: When its first event happened. until: type: - string - 'null' format: date-time description: When its latest event happened. duration_ms: type: integer expires_at: type: string format: date-time entry_path: type: - string - 'null' pages: type: integer description: Pages of the journey it saw. TimelineItem: allOf: - $ref: '#/components/schemas/ReplayPoint' - type: object required: - id - type - at - tab - page_id - path - kind - name - message - url - status - duration_ms - line - column - properties - outcome - stack - repeats properties: id: type: string type: type: string enum: - page - visibility - error - request - custom - recording_started - recording_ended at: type: string format: date-time tab: type: integer page_id: type: - string - 'null' format: uuid path: type: - string - 'null' description: The page it happened on. kind: type: - string - 'null' description: A page's kind; hidden or visible; an error's kind (error, rejection, console); a request's initiator (fetch, xhr, script, img, link, other); or why a recording ended. name: type: - string - 'null' description: An error's name, a request's method or a custom event's name. message: type: - string - 'null' description: An error's message, scrubbed of email addresses and long numbers. url: type: - string - 'null' description: A request's address or an error's source, under the site's query rules. status: type: - integer - 'null' description: 'A request''s status, when the browser exposed one: only for success and http_error. Null otherwise; never a made-up 0.' duration_ms: type: - integer - 'null' description: A request's time from its start, which is its at. outcome: type: - string - 'null' enum: - success - http_error - network_error - timeout - aborted - opaque - load_failed - null description: 'A request''s outcome. network_error: no answer, and the browser does not say why (offline, DNS, refused, CORS). opaque: a cross-origin answer whose status the browser hides. load_failed: a script, image or stylesheet that did not load; the browser does not say whether a server answered. aborted: the page stopped it, which is not a failure.' stack: type: - string - 'null' description: 'An error''s stack: at most 12 frames, addresses without their query, scrubbed as messages are. Cleared with messages at the recording retention.' repeats: type: - integer - 'null' description: How many times an error happened on its page. line: type: - integer - 'null' column: type: - integer - 'null' properties: type: - object - 'null' description: A custom event's properties. additionalProperties: type: - string - number - boolean SessionRow: type: object required: - id - site_id - site_name - state - live - end_reason - source - started_at - last_activity_at - ended_at - expires_at - duration_ms - entry_path - exit_path - origin - referrer - browser - os - device - country - ip - visitor_id - anonymised - counts - recording properties: id: type: string format: uuid site_id: type: string format: uuid site_name: type: string state: type: string enum: - active - ended live: type: boolean description: Active and heard from in the last 90 seconds. end_reason: type: - string - 'null' enum: - idle_timeout - duration_cap - null description: idle_timeout, 30 minutes without the visitor doing anything; duration_cap, 12 hours from its start. source: type: string enum: - sdk - recording description: sdk, reported page by page by the SDK (0.6.0 and later); recording, known only from its recordings (older SDKs, and visits recorded before sessions), so only recorded pages are in it. started_at: type: string format: date-time last_activity_at: type: string format: date-time description: The visitor's last activity in any tab. ended_at: type: - string - 'null' format: date-time expires_at: type: string format: date-time duration_ms: type: integer description: From the start to the last activity. entry_path: type: - string - 'null' exit_path: type: - string - 'null' origin: type: string referrer: type: - string - 'null' description: Origin and path only. browser: type: - string - 'null' os: type: - string - 'null' device: type: - string - 'null' enum: - desktop - mobile - tablet - null country: type: - string - 'null' ip: type: - string - 'null' description: Null once the session is anonymised. visitor_id: type: - string - 'null' format: uuid description: Null once the session is anonymised. anonymised: type: boolean description: Its IP address, user agent, visitor id, error messages and event properties were cleared at the recording retention. counts: type: object required: - pages - events - errors - console_errors - requests - failed_requests - custom - tabs - dropped - errors_not_kept - requests_not_kept - requests_sampled_out - request_sample_percent - replayable_pages properties: pages: type: integer events: type: integer description: Every event, of every type. errors: type: integer description: Uncaught errors and unhandled rejections. console_errors: type: integer description: console.error calls (SDK 0.7.0). requests: type: integer description: 'Requests kept: every outcome before SDK 0.7.0 only failed or slow ones.' failed_requests: type: integer description: 'Requests that failed: an error answer, no answer, a timeout or a resource that did not load.' custom: type: integer description: Events the page tracked. tabs: type: integer dropped: type: integer description: Pages and other events beyond the session's limits, counted and not kept. errors_not_kept: type: integer description: Errors and console errors past the per-page or per-session limits, counted and not kept. requests_not_kept: type: integer description: Requests past the per-page or per-session limits, counted and not kept. requests_sampled_out: type: integer description: Successful requests sampling left out. request_sample_percent: type: - integer - 'null' description: The smallest share of successful requests kept while the session was reported; null when none were sampled. replayable_pages: type: integer description: Pages a recording that can still be played saw. recording: type: object required: - status - total - available - open_recording_id - live properties: status: type: string enum: - none - recording - available - partial - expired - deleted - unavailable description: none, no recording; recording, one is still open; available, every page can be replayed; partial, some can; expired, deleted or unavailable, recordings exist and none can be played. total: type: integer available: type: integer description: Recordings that can be played. open_recording_id: type: - string - 'null' format: uuid description: The recording still open, to watch live. live: type: boolean description: The open recording was heard from in the last 90 seconds. HeatmapCell: type: array description: '[x, y, value]: x cells from the centre of the viewport (negative to the left), y cells from the top of the page (of the viewport for fixed clicks), and the value per visit.' items: type: number minItems: 3 maxItems: 3 JourneyPage: allOf: - $ref: '#/components/schemas/ReplayPoint' - type: object required: - id - tab - kind - path - query - at - ended_at - visible_ms - dwell_ms - dwell properties: id: type: string format: uuid tab: type: integer description: The tab, numbered from 1 in the order tabs first reported a page; 0 when unknown. kind: type: string enum: - load - reload - back_forward - route - resume - recorded description: 'How the page came to be: a document load, a reload, back or forward, an in-page navigation, a page reported again when the next session started, or a page known only from a recording.' path: type: string query: type: - string - 'null' description: The query parameters the site keeps. at: type: string format: date-time ended_at: type: - string - 'null' format: date-time visible_ms: type: - integer - 'null' description: How long the tab was visible on the page, when known. dwell_ms: type: - integer - 'null' dwell: type: string enum: - measured - estimated - open - unknown description: measured, the page reported its end; estimated, from the next page in the same tab; open, the visitor is still on it; unknown, neither. SessionHeatmap: type: object required: - pages - heatmap properties: pages: type: array description: The pages with a heatmap, in the order the session first came to them, by device. items: $ref: '#/components/schemas/HeatmapPage' heatmap: description: The page and device asked for, or the defaults; null when the session has no heatmap for it. oneOf: - $ref: '#/components/schemas/Heatmap' - type: 'null' HeatmapBackground: type: object required: - recording_id - epoch_index - started_at - offset_ms - duration_ms - layout_hash - viewport_width - viewport_height properties: recording_id: type: string format: uuid epoch_index: type: integer started_at: type: string format: date-time offset_ms: type: integer description: Where the visit starts in its recording, in milliseconds from the recording's first event, as the replay player counts them. duration_ms: type: integer description: How long the visit lasted. layout_hash: type: - string - 'null' viewport_width: type: integer viewport_height: type: integer SessionDetail: allOf: - $ref: '#/components/schemas/SessionRow' - type: object required: - sdk_version - ip_source - user_agent - viewport - screen - pixel_ratio - language - timezone - clock_offset_ms - pages - recordings properties: sdk_version: type: - string - 'null' ip_source: type: - string - 'null' enum: - cloudflare - edge - peer - null user_agent: type: - string - 'null' viewport: oneOf: - $ref: '#/components/schemas/Size' - type: 'null' screen: oneOf: - $ref: '#/components/schemas/Size' - type: 'null' pixel_ratio: type: - number - 'null' language: type: - string - 'null' timezone: type: - string - 'null' clock_offset_ms: type: integer description: The server's clock minus the browser's, measured when the session started; journey and timeline times include it. pages: type: array items: $ref: '#/components/schemas/JourneyPage' recordings: type: array items: $ref: '#/components/schemas/SessionRecording' Size: type: object required: - width - height properties: width: type: integer height: type: integer ReplayPoint: type: object required: - recording_id - offset_ms - replay properties: recording_id: type: - string - 'null' format: uuid description: The recording that saw this moment. offset_ms: type: - integer - 'null' description: Where in that recording's replay it happened, when it can be played. replay: type: string enum: - available - recording - expired - deleted - discarded - unplayable - not_recorded description: 'Whether this moment can be watched: available; recording, in a recording still open; expired, deleted, discarded (too short or without interaction) or unplayable, a recording saw it and cannot be played; not_recorded, no recording covers it.' HeatmapBucket: type: string enum: - mobile - tablet - desktop description: By viewport width when the visit began, under 768, under 1280, and wider. Error: type: object required: - error properties: error: type: object required: - code - message - docs properties: code: type: string description: Stable machine-readable code. message: type: string description: Human-readable explanation; do not parse. docs: type: string format: uri request_id: type: string HeatmapPage: type: object required: - path - visits - mobile - tablet - desktop properties: path: type: string visits: type: integer mobile: type: integer tablet: type: integer desktop: type: integer securitySchemes: apiKey: type: http scheme: bearer bearerFormat: if_live_ platform API key description: 'A platform API key minted in the portal. Its scopes are its permissions - `analytics:sites:read`, `analytics:sites:write` and the other `analytics:` scopes, or `analytics:*` - and it acts only in the organisation it was minted for. Keys without the scope a route needs are refused with 403. ' bearerAuth: type: http scheme: bearer bearerFormat: Warden JWT description: 'A Warden user token. Each route also requires a tenant permission in the acting tenant (`X-Ironfang-Tenant`), resolved live from Warden, which each operation names: sites need `analytics.sites.read` to read and `analytics.sites.write` to change; sessions, recordings and heatmaps need `analytics.sessions.read`, and deleting a session or a recording `analytics.sessions.delete`. A missing permission is 403; a permission lookup failure is 503. ' externalDocs: description: Ironfang Analytics documentation url: https://ironfang.com/analytics/docs