The Fullview API gives you programmatic, read-only access to your organisation's session data — the same sessions, transcripts and AI summaries you see in the Fullview dashboard. Use it to sync sessions into your data warehouse, enrich support tickets with what a user actually did, or build internal tooling on top of Fullview recordings.
Interactive reference: a full OpenAPI reference with a try-it-out console lives at https://api.<region>.fullview.io/public/v1/docs, and the raw OpenAPI document at …/public/v1/openapi.json.
Base URL
https://api.<region>.fullview.io/public/v1
Replace <region> with your organisation's region — eu1, eu2 or us1. It's the same region that appears in your Fullview dashboard URL.
All endpoints are versioned under /v1. Breaking changes will only ever ship under a new version prefix.
Authentication
Every request is authenticated with an organisation API key, sent as a bearer token:
Authorization: Bearer fv_Kj8mN2pQ7rT4vX9yA3bC6dF0gH5jL1nP8sV2wY4zR6k
Creating and managing keys
API keys are created in the Fullview dashboard under Settings → API keys (you need organisation-settings permissions, e.g. an admin role).
The key is shown once, at creation. Fullview stores only a hash — if you lose a key, revoke it and create a new one.
Keys start with the
fv_prefix, so secret scanners can recognise them. Treat them like passwords: keep them in a secret manager, never in client-side code or a repository.Keys belong to your organisation, not to the user who created them. They keep working when that person leaves.
Keys don't expire — they live until you revoke them. Revocation takes effect immediately.
You can hold up to 10 active keys per organisation. Use separate keys per integration so you can revoke one without breaking the others.
The dashboard shows each key's masked form (
fv_…R6k) and when it was last used.
Scoping
Every request is scoped to the organisation the key belongs to. There is no organisation or tenant parameter anywhere in the API — you can only ever see your own data.
Authentication errors
A missing, malformed, unknown or revoked key always returns the same 401 Unauthorized response, with no further detail.
Rate limits
120 requests per minute per API key. The window is a fixed minute.
Every response carries
X-RateLimit-LimitandX-RateLimit-Remainingheaders, so a well-behaved client can slow down before being refused.When you exceed the limit you get
429 Too Many Requestswith aRetry-Afterheader (in seconds). Wait that long and retry.
AI summary generation (see below) has its own, separate limits: 10 generations per minute and 300 per UTC day per organisation. Reading an existing summary never counts against these.
Errors
Errors are returned as RFC 9457 problem details — a real HTTP status code plus a small typed JSON body:
{
"type": "urn:fullview:api:error:not-found",
"title": "Not found",
"status": 404,
"detail": "No session with that id in your organisation."
}The type values are stable strings you can match on:
Status | type | When |
400 |
| A query parameter or path segment failed validation. |
401 |
| Missing, malformed, unknown or revoked API key. |
403 |
| The key lacks the scope this endpoint needs. |
404 |
| No such resource in your organisation. |
429 |
| Rate limit or generation limit exceeded — see |
500 |
| Something failed on our side. 5xx bodies carry no |
Sessions
A session is one recorded stretch of a user's activity. It comes in two types:
meeting— a session with a call on it (a Fullview meeting or cobrowse).replay— a browsing session recorded without a call.
List sessions
GET /v1/sessions
Returns your organisation's sessions, newest first by default, filtered server-side.
curl "https://api.eu1.fullview.io/public/v1/sessions?type=meeting&startedAfter=2026-08-01T00:00:00Z&hasTranscript=true&limit=100" \
-H "Authorization: Bearer fv_..."
Filters — all optional, combined with AND:
Parameter | Type | Description |
| ISO 8601 | Only sessions that started at or after this time. |
| ISO 8601 | Only sessions that started at or before this time. |
|
| Session type. |
| string | Fullview customer identifier. |
| string | The identifier your system knows the person by, as supplied to |
| Exact match on the identified customer email. | |
| Exact match on the email of the agent who joined the session. | |
| string | The environment reported at identify time, e.g. |
| integer ≥ 0 | Drop sessions shorter than this — useful for excluding bounces. |
| boolean | Only sessions that do / do not have a finished transcript. |
| boolean | Only sessions that do / do not have recorded audio. |
| boolean | Only sessions with at least one console error. |
| boolean | Only sessions with at least one rage click. |
|
| Order by start time. Default |
| 1–200 | Page size. Default 50. |
| string | The |
Boolean parameters must be a literal true or false — anything else is a 400, not a guess.
Pagination is cursor-based:
{
"data": [ ... ],
"pagination": {
"nextCursor": "eyJ...",
"totalCount": 1284
}
}Pass
nextCursorback ascursor, together with the same filters — a cursor is only valid for the query that produced it.nextCursorisnullon the last page.totalCountis the total matching the filters, regardless of cursor position.Cursors stay valid indefinitely, so a paused export can resume later.
Get one session
GET /v1/sessions/{sessionId}
Returns the full session object:
{
"data": {
"id": "ses_9f2c1a7e3b90",
"type": "meeting",
"startedAt": "2026-08-19T09:58:12.000Z",
"endedAt": "2026-08-19T10:12:45.000Z",
"durationSeconds": 873,
"device": {
"browser": "Chrome 128",
"deviceId": "d_4c1a...",
"ipAddress": "203.0.113.7",
"environment": "production"
},
"participants": {
"customer": {
"id": "cus_71be...",
"name": "Ada Lovelace",
"email": "ada@example.com",
"externalId": "user-1815",
"isAnonymous": false
},
"agent": {
"id": "usr_20aa...",
"name": "Sam Agent",
"email": "sam@yourcompany.com"
}
},
"signals": {
"userSteps": 42,
"consoleErrors": 3,
"consoleWarnings": 7,
"rageClicks": 1
},
"recording": {
"callId": "call_88f0...",
"hasAudio": true,
"hasTranscript": true,
"transcriptStatus": "ready"
},
"hasSummary": true,
"isValid": true
}
}Field notes:
participants.agentisnullfor a session nobody joined.participants.customer.isAnonymousistruewhen the session was never associated with an identified person.recording.transcriptStatusis one ofready,processing,failed,none— the same values the transcript endpoint reports.hasSummary: falsemeans "not generated yet", not "nothing to summarise" (summaries are generated lazily — see below).isValid: falsemarks a session too short or incomplete to replay; these are excluded from list results.
Get a session's events
GET /v1/sessions/{sessionId}/events
Everything recorded during the session, grouped by what you'd ask of it. Each group is complete and chronological — no sampling, no pagination:
{
"data": {
"id": "ses_9f2c1a7e3b90",
"startedAt": "2026-08-19T09:58:12.000Z",
"userJourney": [
{ "timestamp": "...", "type": "dom.mouse.click", "target": "Submit expense", "url": "https://app.example.com/expenses" },
{ "timestamp": "...", "type": "dom.rage.click", "target": "Submit expense", "url": "https://app.example.com/expenses" }
],
"consoleEvents": [
{ "timestamp": "...", "level": "error", "message": "ValidationError: amount is required" }
],
"navigationEvents": [
{ "timestamp": "...", "url": "https://app.example.com/expenses" }
],
"networkRequests": [
{ "timestamp": "...", "method": "POST", "url": "https://api.example.com/expenses", "statusCode": 422, "durationMs": 310 }
]
}
}userJourneyis every user action in order: clicks, rage clicks, navigations and tab changes. Anagent.prefix on thetypemarks an action taken by the agent during a call rather than by the user.Navigations appear both in the journey (they're part of the user's story) and in
navigationEvents(so "which pages" needs no filtering) — the groups are views over one event stream, not a partition of it.
Get a session's transcript
GET /v1/sessions/{sessionId}/transcript
The spoken transcript of the call on this session — chronological and speaker-attributed, one segment per consecutive same-speaker stretch:
{
"data": {
"id": "ses_9f2c1a7e3b90",
"status": "ready",
"source": "meeting",
"segments": [
{ "speaker": "Ada Lovelace", "text": "Can you see my screen now?", "offsetMs": 5230 },
{ "speaker": "Sam Agent", "text": "Yes — go ahead and open the expenses page.", "offsetMs": 8110 }
],
"actionPoints": [
{ "speaker": "Sam Agent", "text": "Send the updated expense policy to the customer after the call.", "offsetMs": 185000 }
]
}
}An empty segments array is a normal answer, not an error — status says why:
status | Meaning |
| Transcription finished; segments are complete. |
| The call ended recently and transcription is still running — it finishes within minutes; poll again shortly. Segments may be partially filled. |
| The pipeline reported an error; no transcript is coming. |
| This session can never have a transcript (no call, or the call was recorded without audio). |
offsetMsis milliseconds from the session start, not epoch time. It matches the#[s:<seconds>]timestamps in the AI summary after dividing by 1000.actionPointsare AI-extracted action items from the call, with the same offset basis.For cheap discovery across many sessions, filter the list endpoint by
hasTranscriptinstead of polling this endpoint per session.502/503from this endpoint mean the transcript store is temporarily unavailable — safe to retry later.
Get a session's AI summary
GET /v1/sessions/{sessionId}/summary
An AI-written recap of what happened in the session — a few sentences plus an optional outcome — generated from the session's events and, for calls, the spoken transcript.
{
"data": {
"id": "ses_9f2c1a7e3b90",
"hasSummary": true,
"summary": "The user tried to submit an expense report and hit a validation error twice #[s:45]. An agent joined and completed the submission with them #[s:190].",
"outcome": {
"label": "resolved with agent assistance",
"severity": "neutral"
},
"generatedAt": "2026-08-19T10:15:00.000Z"
}
}Summaries are generated lazily. hasSummary: false means "not generated yet", not "nothing to summarise". To generate one on the spot:
GET /v1/sessions/{sessionId}/summary?createIfMissing=trueThe call is synchronous — expect a few seconds.
It is idempotent — the first result is cached permanently, and every later call returns it unchanged.
generatedAtdoubles as a cache validator: samegeneratedAt, same summary.Generation is counted against per-organisation limits of 10 per minute and 300 per UTC day, separately from the request rate limit. A
429tells you which viaRetry-After— up to a minute for the per-minute limits, until the next UTC midnight for the daily one.Reads of existing summaries never count against generation limits.
A
502means generation failed and nothing was cached — retrying withcreateIfMissing=trueis safe.
Field notes:
Inline
#[s:<seconds>]tags insummarymark the moment each statement refers to, in whole seconds from the session start. Strip them with the pattern#\[s:\d+\]if you only want prose.outcome.severityisneutral(ordinary or successful),warning(the user hit friction) orcritical(they clearly failed at what they came to do).outcomeisnullwhen no outcome could be confidently inferred.
Recipes
Nightly export of yesterday's meetings with transcripts:
curl "https://api.eu1.fullview.io/public/v1/sessions?type=meeting&hasTranscript=true&startedAfter=2026-08-25T00:00:00Z&startedBefore=2026-08-26T00:00:00Z&limit=200" \
-H "Authorization: Bearer fv_..."
# ...then page with `cursor` until nextCursor is null,
# and fetch /transcript per session id.
Find sessions where a specific user struggled:
curl "https://api.eu1.fullview.io/public/v1/sessions?customerExternalId=user-1815&hasRageClicks=true" \
-H "Authorization: Bearer fv_..."
Enrich a support ticket with a session recap:
curl "https://api.eu1.fullview.io/public/v1/sessions/ses_9f2c1a7e3b90/summary?createIfMissing=true" \
-H "Authorization: Bearer fv_..."
FAQ
Is there a write API?
No — v1 is read-only, plus on-demand summary generation. Session data is produced by the Fullview SDK and dashboard.
Can one key access multiple organisations?
No. A key belongs to exactly one organisation and can only read that organisation's data.
How fresh is the data?
Sessions become queryable shortly after they're recorded and indexed — typically within moments of the session ending. Transcripts finish within minutes of a call ending.
Can I be notified when something happens, instead of polling?
Yes — see the Webhooks article. Webhooks push meeting events to your endpoint as they happen; the session id in a webhook payload is the same id this API's GET /v1/sessions/{id} takes.