API reference
The ingest API apps call and the console API the LogSure clients call. Generated from api/openapi.yaml in logsure/server (OpenAPI 3.1.0, API version 1.0.0). Fields marked ? are optional.
Two surfaces on one server.
The ingest API lives under a prefix (default /_care) on every host the server answers, so an app can route its own /_care/ path to LogSure and stay first-party. Browsers are identified by their Origin, back-ends by a server key. Error answers carry a human error, a stable code and, for validation, the field.
The console API lives under /api/v1 on the console's host. Devices (the iOS and terminal apps) send a bearer device token; the web console signs in with a username and password (POST /api/v1/session, then the two-factor code when it is on) and is then known by an HttpOnly, SameSite=Strict session cookie. With the cookie, anything but a GET must come from the console's own origin (its Origin header), or it is refused with 403.
The HTML pages under the prefix (the transparency page at /_care/ and the unsubscribe page) are for people, not programs, and are not part of this contract.
Times are milliseconds since the Unix epoch; days are UTC. Operations that return personal data (email addresses, names) are marked x-logsure-scope: inbox, operations that change data x-logsure-scope: write, everything else analytics.
Every session, device token and API key belongs to a user, and acts with that user's role: owner, admin, member or viewer. Operations that need more than a viewer are marked x-logsure-role with the least role that may call them (member or admin); the rest are open to every role. A role too low is refused with 403 forbidden_role and need, the role it takes. A user limited to some apps gets 404 for the others, as if they did not exist. A passkey counts as two-factor sign-in. When the team requires two-factor sign-in and the user has neither a two-factor app nor a passkey, everything but getMe, the profile and the security operations is refused with 403 two_factor_required. Team changes (updateUser, deleteUser, createInvite, revokeInvite, setTeam) take a device token only while its user has two-factor sign-in (an app or a passkey) on; otherwise 403 two_factor_required_for_device. While the team requires two-factor, a user with passkeys but no two-factor app signs in with a passkey: the password alone is refused with 401 passkey_required. Changes made through a device or an AI client name it in the audit entry's detail (device or aiClient).
Authentication
| Name | Kind | |
|---|
serverKey | bearer | An app's server key (cc_…), for back-ends only. Requests with a key and an Origin are refused. |
deviceToken | bearer | A device token (cc_…) made in Settings › Mobile app. |
mcpToken | bearer | An MCP access token from /oauth/token, or a personal API key (ccm_…). |
session | cookie | The web console's session, set by signIn (named cc_session on plain-http development servers). |
Ingest
Called by the tracker script, app forms and app back-ends.
GET/_care/script.jsThe tracker script.
Operation getTrackerScript · authentication: none
Responses
200 The script, cacheable for 10 minutes.
string
GET/_care/healthWhether the server and its database answer.
Operation getHealth · authentication: none
Responses
200 Healthy.
503 Refused, with a stable code. IngestError
GET/_care/privacy.jsonWhat the ingest endpoints receive, use and keep, from the running configuration.
Operation getPrivacyFacts · authentication: none
On an app's own address (by the Host header), that app's: its retention, which error reports its pages send, and whether its servers send traces and logs. A feature switched off for it is left out, so each section is there only when it applies; elsewhere, the server's switches.
Responses
200 The facts behind the public transparency page.
PrivacyFacts
errors?:
object
endpoints:
array of string
personalData:
false
captured:
array of string // The kinds of errors reported, from the app's error settings.
note?:
string // Off an app's address, that each site chooses the kinds its pages report.
kept:
array of string
scrubbed:
string
retentionDays:
integer
tracing?:
object
endpoints:
array of string
source:
string
traces:
boolean // The app's servers have sent traces.
logs:
boolean
sent:
array of string
scrubbed:
string
retentionDays:
object
traces:
integer
logs:
integer
analytics?:
object
endpoints:
array of string
personalData:
false
cookies:
false
deviceStorage:
false
received?:
array of string
used?:
array of string
visitorCode?:
string
neverStored?:
array of string
retentionDays:
integer
customerCare?:
object
endpoints:
array of string
personalData?:
string
note?:
string
retentionDays:
object
erasure?:
string
POST/_care/eventRecord one event or a batch of up to 25.
Operation postEvent · authentication: serverKey or none
Request body
application/json
Event | array of <a href="#schema-Event">Event</a> (min items 1, max items 25) Responses
202 Taken (bots are taken and dropped).
400 401 403 405 413 415 429 Refused, with a stable code. IngestError
OPTIONS/_care/eventCORS preflight; the CORS headers come only for an Origin that belongs to an app.
Operation preflightEvent · authentication: none
Responses
204 No body.
POST/_care/event/{name}Record one event whose name is in the path (the body's name is ignored).
Operation postNamedEvent · authentication: serverKey or none
Parameters
| Name | In | Type | |
|---|
name | path | string (min length 1, max length 64) | |
Request body optional
application/json
EventWithoutName
app?:
string // The app's ID, when the Origin is shared by several apps or the call comes from a back-end.
url?:
string (max length 2048) // An absolute http(s) URL or a path. Only the host, the path and utm_* are kept.
hash?:
boolean // Keep the URL's fragment as part of the path (hash-routed apps).
referrer?:
string (max length 2048)
props?:
object (max keys 20) // Strings up to 256 characters; no email addresses, IP addresses or User-Agents.
value?:
number
width?:
integer (min 0, max 10000)
lang?:
string (max length 35)
version?:
string (max length 32)
ip?:
string // Server key only - the visitor's address, used in memory for the daily visitor code.
userAgent?:
string // Server key only.
country?:
string (pattern ^[A-Z]{2}$) // Server key only.
ts?:
Time // Server key only - within the last 15 minutes of the current UTC day. Responses
202 Taken.
400 404 Refused, with a stable code. IngestError
POST/_care/feedbackFeedback, with an optional star rating.
Operation postFeedback · authentication: serverKey or none
Request body
application/json
Feedback
app?:
string
email?:
string (max length 254, email)
name?:
string (max length 100)
url?:
string // The page it was sent from (http or https).
props?:
object
fields?:
object // The app's custom fields (see getFormDefinition).
website?:
string // A honeypot - leave empty. A filled one gets the success answer and nothing is stored.
token?:
string // The Cloudflare Turnstile response, when the app requires it.
userAgent?:
string // Server key only.
country?:
string // Server key only.
message:
string (min length 1, max length 5000)
rating?:
integer (min 1, max 5)
category?:
string (max length 32) Responses
201 Stored.
ok: true
id: string (pattern ^t_)
400 403 429 Refused, with a stable code. IngestError
POST/_care/bugA bug report, with up to 3 attachments.
Operation postBugReport · authentication: serverKey or none
Request body
application/json
BugReport
app?:
string
email?:
string (max length 254, email)
name?:
string (max length 100)
url?:
string // The page it was sent from (http or https).
props?:
object
fields?:
object // The app's custom fields (see getFormDefinition).
website?:
string // A honeypot - leave empty. A filled one gets the success answer and nothing is stored.
token?:
string // The Cloudflare Turnstile response, when the app requires it.
userAgent?:
string // Server key only.
country?:
string // Server key only.
title:
string (min length 3, max length 150)
description:
string (min length 1, max length 10000)
steps?:
string (max length 5000)
expected?:
string (max length 5000)
version?:
string (max length 32)
attachments?:
array of object (max items 3)
name?:
string (max length 100)
type?:
string // Ignored - the type is read from the bytes.
data:
string // Base64 or a data URL. PNG, JPEG, GIF, WEBP, UTF-8 text or JSON; 5 MB each, 8 MB together (apps may allow more). Responses
201 Stored.
ok: true
id: string (pattern ^t_)
400 403 413 429 503 Refused, with a stable code. IngestError
POST/_care/messageA contact or support message; an email address is required.
Operation postMessage · authentication: serverKey or none
Request body
application/json
Message
app?:
string
email:
string (max length 254, email)
name?:
string (max length 100)
url?:
string // The page it was sent from (http or https).
props?:
object
fields?:
object // The app's custom fields (see getFormDefinition).
website?:
string // A honeypot - leave empty. A filled one gets the success answer and nothing is stored.
token?:
string // The Cloudflare Turnstile response, when the app requires it.
userAgent?:
string // Server key only.
country?:
string // Server key only.
message:
string (min length 1, max length 10000)
subject?:
string (max length 150) Responses
201 Stored.
ok: true
id: string (pattern ^t_)
400 403 429 Refused, with a stable code. IngestError
POST/_care/waitlistJoin a waitlist. A repeat signup gets the same answer, so membership does not leak.
Operation postWaitlistSignup · authentication: serverKey or none
Request body
application/json
WaitlistSignup
app?:
string
email:
string (max length 254, email)
name?:
string (max length 100)
list?:
string (pattern ^[a-z0-9][a-z0-9-]{0,31}$, default "default")
consent?:
boolean // Required when the app's rules ask for consent.
props?:
object
referrer?:
string
url?:
string
website?:
string
token?:
string
country?:
string // Server key only.
lang?:
string // Server key only. Responses
202 Taken.
400 403 429 Refused, with a stable code. IngestError
GET/_care/form/{kind}The app's custom fields and attachment rules for one kind of request.
Operation getFormDefinition · authentication: none
Parameters
| Name | In | Type | |
|---|
kind | path | TicketKind | |
app optional | query | string | The app, when the Origin does not name it. |
Responses
200 The definition.
FormDefinition
app:
string
kind:
TicketKind
fields:
array of object
key:
string
label:
string
type:
"text" | "textarea" | "number" | "select" | "checkbox" | "email" | "url" | "date" | "json"
required?:
boolean
options?:
array of string
max?:
integer
help?:
string
attachments?:
object // Bug reports only.
count?:
integer
maxFileBytes?:
integer
maxTotalBytes?:
integer
types?:
array of string 403 404 Refused, with a stable code. IngestError
POST/_care/import/waitlistAn app's earlier signups, up to 1000 per call, with their own times. Server key only.
Operation importWaitlist · authentication: serverKey
Request body
application/json
WaitlistImport
app?:
string
entries:
array of object (max items 1000)
email:
string
name?:
string
list?:
string
status?:
SignupStatus
lang?:
string
created_at?:
string | integer // When they signed up, as an ISO time or milliseconds since the epoch. Default now.
consent?:
object // The consent they gave, when the app records it.
version:
string
at:
string | integer Responses
200 What was imported and what was skipped.
WaitlistImportResult
imported:
integer
skipped:
integer
errors:
array of object
index:
integer
error:
string
code:
string
field?:
string | null 401 Refused, with a stable code. IngestError
POST/_care/errorAn error from an app, from the tracker, the app's code or its server.
Operation postError · authentication: serverKey or none
From a browser on one of the app's domains (as events), or with the app's server key. Before anything is stored, email addresses, tokens and keys, card-like numbers and URL query strings are removed from the message, the stack, the page and the props; the stack is cut at 16 KB and 50 frames. Reports caught automatically (auto) are kept only when the app turns automatic capture on; reports whose message contains one of the app's ignored patterns are dropped. Past the app's daily cap reports are dropped too. The answer is the same either way.
Request body
application/json
ErrorReport
app?:
string
message:
string (max length 2000)
type?:
string (max length 100) // e.g. TypeError.
stack?:
string // The stack as JavaScript (or the platform) writes it; read into frames when frames are not sent.
frames?:
array of <a href="#schema-ErrorFrame">ErrorFrame</a> (max items 50) // The stack, innermost frame first.
url?:
string // The page or screen it happened on.
release?:
string (max length 64)
environment?:
string (max length 32) // Default production.
props?:
object // Like an event's props.
fingerprint?:
string (max length 200) // Groups reports by this instead of the type, message and top frames.
handled?:
boolean // Caught by the app (logsure.error) rather than escaped.
auto?:
boolean // Caught by the tracker by itself (the earlier way to say source uncaught).
source?:
ErrorSource
request?:
ErrorRequest
timestamp?:
integer // When it happened (ms); at most a day old, else now.
userAgent?:
string // For server and native clients; a browser's own User-Agent header is used otherwise. text/plain
string
Responses
202 Taken (or dropped by a rule above).
400 403 413 429 Refused, with a stable code. IngestError
GET/_care/errors/configWhat the app captures by itself, and its ignored patterns, for the tracker.
Operation getErrorConfig · authentication: none
Parameters
| Name | In | Type | |
|---|
app optional | query | string | |
Responses
200 The app's rules.
ErrorSettings
capture:
boolean // Uncaught errors and rejected promises.
console:
boolean // console.error calls (the console still shows them).
network:
boolean // fetch and XMLHttpRequest requests that fail, or answer 5xx.
network4xx:
boolean // With network, 4xx answers too.
resources:
boolean // Scripts, style sheets and images that do not load.
ignore:
array of string <span class="d">(max length 200)</span> (max items 50) // Reports whose message contains one of these (any case) are dropped. 403 404 Refused, with a stable code. IngestError
Analytics
Visitors, pages, sources and events.
GET/api/v1/overviewEvery app's visitors and pageviews over a window, with the previous period and visitors now.
Operation getOverview · authentication: deviceToken, session · scope analytics
Parameters
| Name | In | Type | |
|---|
range optional | query | RangeKey | The window. Ignored when from and to are both given. Unknown values fall back to 7d. |
from optional | query | WindowEnd | Start of a custom window, UTC: a day (YYYY-MM-DD, from its first minute) or a minute (YYYY-MM-DDTHH:MM, e.g. for the last 4 hours). Used only with to; the two may mix forms. |
to optional | query | WindowEnd | End of a custom window, UTC and inclusive: a day (YYYY-MM-DD, through its last minute) or a minute (YYYY-MM-DDTHH:MM, through the end of that minute). from=2026-10-24T10:00&to=2026-10-24T13:59 is the four hours from 10:00 to 14:00. An end after now stops at now; ends given the wrong way round are swapped. |
Responses
200 The overview.
Overview
range:
Range
apps:
array of object
appId:
string
imported:
ImportedWindow
visitors:
integer
pageviews:
integer
prev:
object | null
visitors:
integer
pageviews:
integer
spark:
array of integer // Visitors per day over the last 30 days.
realtime:
integer // Visitors in the last 5 minutes.
series:
array of integer // Visitors per bucket of the window. 401 403 Refused. ApiError
GET/api/v1/apps/{app}/dashboardOne app's dashboard over a window.
Operation getDashboard · authentication: deviceToken, session · scope analytics
Parameters
| Name | In | Type | |
|---|
app | path | string | |
range optional | query | RangeKey | The window. Ignored when from and to are both given. Unknown values fall back to 7d. |
from optional | query | WindowEnd | Start of a custom window, UTC: a day (YYYY-MM-DD, from its first minute) or a minute (YYYY-MM-DDTHH:MM, e.g. for the last 4 hours). Used only with to; the two may mix forms. |
to optional | query | WindowEnd | End of a custom window, UTC and inclusive: a day (YYYY-MM-DD, through its last minute) or a minute (YYYY-MM-DDTHH:MM, through the end of that minute). from=2026-10-24T10:00&to=2026-10-24T13:59 is the four hours from 10:00 to 14:00. An end after now stops at now; ends given the wrong way round are swapped. |
seg optional | query | string | A saved segment's ID, or all. Default is the app's default segment. |
cev optional | query | string | Up to three custom events to draw as chart lines, comma-separated. |
path optional | query | string | |
source optional | query | string | The referrer's host or utm_source; (none) for direct visits. |
country optional | query | string | |
device optional | query | string | |
browser optional | query | string | |
os optional | query | string | |
lang optional | query | string | |
host optional | query | string | |
event optional | query | string | Only visits that sent this custom event. |
Responses
200 The dashboard.
Dashboard
imported:
ImportedWindow // The window's imported days, when it has some.
app:
App
segment:
string
filters:
object
range:
Range
summary:
Summary
series:
array of <a href="#schema-SeriesPoint">SeriesPoint</a>
pages:
array of <a href="#schema-Row">Row</a>
sources:
array of <a href="#schema-Row">Row</a>
countries:
array of <a href="#schema-Row">Row</a>
devices:
array of <a href="#schema-Row">Row</a>
languages:
array of <a href="#schema-Row">Row</a>
sites:
array of <a href="#schema-Row">Row</a> // By host, when the app has more than one domain.
live:
object
visitors:
integer
pages:
array of object
value:
string | null
visitors:
integer
events:
array of <a href="#schema-Goal">Goal</a>
chartEvents:
array of object (max items 3)
name:
string
series:
array of object
t:
Time
count:
integer
ratings:
Ratings
recent:
array of <a href="#schema-TicketSummary">TicketSummary</a> // The latest feedback (contains personal data). 401 403 404 Refused. ApiError
GET/api/v1/apps/{app}/breakdownThe top values of one dimension.
Operation getBreakdown · authentication: deviceToken, session · scope analytics
Parameters
| Name | In | Type | |
|---|
app | path | string | |
dim | query | Dimension | |
limit optional | query | integer (min 1, max 250, default 50) | |
range optional | query | RangeKey | The window. Ignored when from and to are both given. Unknown values fall back to 7d. |
from optional | query | WindowEnd | Start of a custom window, UTC: a day (YYYY-MM-DD, from its first minute) or a minute (YYYY-MM-DDTHH:MM, e.g. for the last 4 hours). Used only with to; the two may mix forms. |
to optional | query | WindowEnd | End of a custom window, UTC and inclusive: a day (YYYY-MM-DD, through its last minute) or a minute (YYYY-MM-DDTHH:MM, through the end of that minute). from=2026-10-24T10:00&to=2026-10-24T13:59 is the four hours from 10:00 to 14:00. An end after now stops at now; ends given the wrong way round are swapped. |
seg optional | query | string | A saved segment's ID, or all. Default is the app's default segment. |
path optional | query | string | |
source optional | query | string | The referrer's host or utm_source; (none) for direct visits. |
country optional | query | string | |
device optional | query | string | |
browser optional | query | string | |
os optional | query | string | |
lang optional | query | string | |
host optional | query | string | |
event optional | query | string | Only visits that sent this custom event. |
Responses
200 The rows, by visitors.
400 401 403 404 Refused. ApiError
GET/api/v1/apps/{app}/events/{name}One custom event over a window, with its properties.
Operation getEvent · authentication: deviceToken, session · scope analytics
Parameters
| Name | In | Type | |
|---|
app | path | string | |
name | path | string (max length 64) | |
range optional | query | RangeKey | The window. Ignored when from and to are both given. Unknown values fall back to 7d. |
from optional | query | WindowEnd | Start of a custom window, UTC: a day (YYYY-MM-DD, from its first minute) or a minute (YYYY-MM-DDTHH:MM, e.g. for the last 4 hours). Used only with to; the two may mix forms. |
to optional | query | WindowEnd | End of a custom window, UTC and inclusive: a day (YYYY-MM-DD, through its last minute) or a minute (YYYY-MM-DDTHH:MM, through the end of that minute). from=2026-10-24T10:00&to=2026-10-24T13:59 is the four hours from 10:00 to 14:00. An end after now stops at now; ends given the wrong way round are swapped. |
seg optional | query | string | A saved segment's ID, or all. Default is the app's default segment. |
path optional | query | string | |
source optional | query | string | The referrer's host or utm_source; (none) for direct visits. |
country optional | query | string | |
device optional | query | string | |
browser optional | query | string | |
os optional | query | string | |
lang optional | query | string | |
host optional | query | string | |
event optional | query | string | Only visits that sent this custom event. |
Responses
200 The event.
EventDetail
name:
string
range:
Range
series:
array of object
t:
Time
count:
integer
visitors:
integer
props:
array of object
key:
string
value:
string | number | boolean | null
visitors:
integer
count:
integer 401 403 404 Refused. ApiError
GET/api/v1/apps/{app}/funnelsThe app's saved funnels, each counted over a window.
Operation listFunnels · authentication: deviceToken, session · scope analytics
Every saved funnel with its results, under the same window, segment and filters as the dashboard. See FunnelResult for how a funnel is counted.
Parameters
| Name | In | Type | |
|---|
app | path | string | |
range optional | query | RangeKey | The window. Ignored when from and to are both given. Unknown values fall back to 7d. |
from optional | query | WindowEnd | Start of a custom window, UTC: a day (YYYY-MM-DD, from its first minute) or a minute (YYYY-MM-DDTHH:MM, e.g. for the last 4 hours). Used only with to; the two may mix forms. |
to optional | query | WindowEnd | End of a custom window, UTC and inclusive: a day (YYYY-MM-DD, through its last minute) or a minute (YYYY-MM-DDTHH:MM, through the end of that minute). from=2026-10-24T10:00&to=2026-10-24T13:59 is the four hours from 10:00 to 14:00. An end after now stops at now; ends given the wrong way round are swapped. |
seg optional | query | string | A saved segment's ID, or all. Default is the app's default segment. |
path optional | query | string | |
source optional | query | string | The referrer's host or utm_source; (none) for direct visits. |
country optional | query | string | |
device optional | query | string | |
browser optional | query | string | |
os optional | query | string | |
lang optional | query | string | |
host optional | query | string | |
event optional | query | string | Only visits that sent this custom event. |
Responses
200 The funnels and their results, in the saved order.
segment:
string
filters:
object
range:
Range
funnels:
array of <a href="#schema-FunnelResult">FunnelResult</a> 401 403 404 Refused. ApiError
POST/api/v1/apps/{app}/funnels/previewCount a funnel that is not saved, over a window.
Operation previewFunnel · authentication: deviceToken, session · scope analytics
The same results as getFunnel, for a definition in the body; nothing is saved.
Parameters
| Name | In | Type | |
|---|
app | path | string | |
range optional | query | RangeKey | The window. Ignored when from and to are both given. Unknown values fall back to 7d. |
from optional | query | WindowEnd | Start of a custom window, UTC: a day (YYYY-MM-DD, from its first minute) or a minute (YYYY-MM-DDTHH:MM, e.g. for the last 4 hours). Used only with to; the two may mix forms. |
to optional | query | WindowEnd | End of a custom window, UTC and inclusive: a day (YYYY-MM-DD, through its last minute) or a minute (YYYY-MM-DDTHH:MM, through the end of that minute). from=2026-10-24T10:00&to=2026-10-24T13:59 is the four hours from 10:00 to 14:00. An end after now stops at now; ends given the wrong way round are swapped. |
seg optional | query | string | A saved segment's ID, or all. Default is the app's default segment. |
path optional | query | string | |
source optional | query | string | The referrer's host or utm_source; (none) for direct visits. |
country optional | query | string | |
device optional | query | string | |
browser optional | query | string | |
os optional | query | string | |
lang optional | query | string | |
host optional | query | string | |
event optional | query | string | Only visits that sent this custom event. |
Request body
application/json
Responses
200 The results.
FunnelAnswer
segment:
string
filters:
object
range:
Range
funnel:
Funnel
journeys:
integer
steps:
array of <a href="#schema-FunnelStepResult">FunnelStepResult</a>
conversion:
number | null 400 401 403 404 Refused. ApiError
GET/api/v1/apps/{app}/funnels/{funnel}One saved funnel, counted over a window.
Operation getFunnel · authentication: deviceToken, session · scope analytics
Parameters
| Name | In | Type | |
|---|
app | path | string | |
funnel | path | string | The funnel's ID. |
range optional | query | RangeKey | The window. Ignored when from and to are both given. Unknown values fall back to 7d. |
from optional | query | WindowEnd | Start of a custom window, UTC: a day (YYYY-MM-DD, from its first minute) or a minute (YYYY-MM-DDTHH:MM, e.g. for the last 4 hours). Used only with to; the two may mix forms. |
to optional | query | WindowEnd | End of a custom window, UTC and inclusive: a day (YYYY-MM-DD, through its last minute) or a minute (YYYY-MM-DDTHH:MM, through the end of that minute). from=2026-10-24T10:00&to=2026-10-24T13:59 is the four hours from 10:00 to 14:00. An end after now stops at now; ends given the wrong way round are swapped. |
seg optional | query | string | A saved segment's ID, or all. Default is the app's default segment. |
path optional | query | string | |
source optional | query | string | The referrer's host or utm_source; (none) for direct visits. |
country optional | query | string | |
device optional | query | string | |
browser optional | query | string | |
os optional | query | string | |
lang optional | query | string | |
host optional | query | string | |
event optional | query | string | Only visits that sent this custom event. |
Responses
200 The results.
FunnelAnswer
segment:
string
filters:
object
range:
Range
funnel:
Funnel
journeys:
integer
steps:
array of <a href="#schema-FunnelStepResult">FunnelStepResult</a>
conversion:
number | null 401 403 404 Refused. ApiError
GET/api/v1/apps/{app}/dashboardsThe signed-in user's dashboards for an app, the default first.
Operation listDashboards · authentication: deviceToken, session · scope analytics
Dashboards are personal: every user, viewers included, has the built-in default and may customize it or add named ones. Nobody sees another user's.
Parameters
Responses
200 The dashboards, without their layouts.
dashboards: array of <a href="#schema-DashboardRef">DashboardRef</a>
401 403 404 Refused. ApiError
POST/api/v1/apps/{app}/dashboardsA new named dashboard; it starts as the default layout unless one is given.
Operation createDashboard · authentication: deviceToken, session · scope write
Parameters
Request body
application/json
Responses
201 The dashboard.
400 401 403 404 Refused. ApiError
GET/api/v1/apps/{app}/dashboards/{dashboard}One dashboard's layout; default is the built-in one, as this user customized it or as shipped.
Operation getDashboardLayout · authentication: deviceToken, session · scope analytics
Parameters
| Name | In | Type | |
|---|
app | path | string | |
dashboard | path | string | default, or a dashboard ID from listDashboards. |
Responses
200 The dashboard.
401 403 404 Refused. ApiError
POST/api/v1/apps/{app}/dashboards/{dashboard}Change a dashboard's layout, its name, or both.
Operation updateDashboard · authentication: deviceToken, session · scope write
The layout replaces the saved one whole. The default dashboard keeps its name.
Parameters
| Name | In | Type | |
|---|
app | path | string | |
dashboard | path | string | default, or a dashboard ID from listDashboards. |
Request body
application/json
Responses
200 The dashboard as saved.
400 401 403 404 Refused. ApiError
POST/api/v1/apps/{app}/dashboards/{dashboard}/resetBack to the default layout; a named dashboard keeps its name.
Operation resetDashboard · authentication: deviceToken, session · scope write
Parameters
| Name | In | Type | |
|---|
app | path | string | |
dashboard | path | string | default, or a dashboard ID from listDashboards. |
Responses
200 The dashboard as reset.
401 403 404 Refused. ApiError
POST/api/v1/apps/{app}/dashboards/{dashboard}/deleteDelete a named dashboard. The default one is reset, not deleted.
Operation deleteDashboard · authentication: deviceToken, session · scope write
Parameters
| Name | In | Type | |
|---|
app | path | string | |
dashboard | path | string | default, or a dashboard ID from listDashboards. |
Responses
200 Done.
400 401 403 404 Refused. ApiError
Inbox
Feedback, bug reports and messages.
GET/api/v1/ticketsRequests (feedback, bug reports, messages), newest first.
Operation listTickets · authentication: deviceToken, session · scope inbox
Parameters
| Name | In | Type | |
|---|
app optional | query | string | |
kind optional | query | TicketKind | |
status optional | query | "inbox" | "active" | "all" | "open" | "progress" | "resolved" | "spam" | "archived" | A status, or active (open and in progress), inbox (not spam or archived, the default) or all. |
q optional | query | string | Words in the subject, text, name or email address. |
limit optional | query | integer (min 1, max 500) | |
offset optional | query | integer (min 0) | |
Responses
200 One page of requests.
total: integer
items: array of <a href="#schema-TicketSummary">TicketSummary</a>
401 403 404 Refused. ApiError
GET/api/v1/tickets/{id}One request in full, with its notes and attachments.
Operation getTicket · authentication: deviceToken, session · scope inbox
Parameters
| Name | In | Type | |
|---|
id | path | string (pattern ^t_) | |
Responses
200 The request.
Ticket
id:
string
appId:
string
kind:
TicketKind
status:
TicketStatus
subject:
string | null
body:
string
data:
object // Bug report details (steps, expected, version) and the app's custom fields.
meta:
object
url?:
string | null
browser?:
string | null
os?:
string | null
device?:
string | null
country?:
string | null
lang?:
string | null
host?:
string | null
props?:
object | null
email:
string | null
name:
string | null
rating:
integer | null
category:
string | null
createdAt:
Time
updatedAt:
integer | null
notes:
array of object
id:
integer | string
ts:
Time
kind:
string // note (by the owner) or system.
body:
string
attachments:
array of object
id:
string
name:
string
type:
string
size:
integer 401 403 404 Refused. ApiError
POST/api/v1/tickets/{id}/statusMove a request to another status.
Operation setTicketStatus · authentication: deviceToken, session · scope write
Parameters
| Name | In | Type | |
|---|
id | path | string (pattern ^t_) | |
Request body
application/json
Responses
200 Done.
400 401 403 404 Refused. ApiError
POST/api/v1/tickets/{id}/notesAdd an internal note to a request.
Operation addTicketNote · authentication: deviceToken, session · scope write
Parameters
| Name | In | Type | |
|---|
id | path | string (pattern ^t_) | |
Request body
application/json
body: string (min length 1)
Responses
200 Done.
400 401 403 404 Refused. ApiError
POST/api/v1/tickets/{id}/deleteDelete a request with its notes and attachments.
Operation deleteTicket · authentication: deviceToken, session · scope write
Parameters
| Name | In | Type | |
|---|
id | path | string (pattern ^t_) | |
Responses
200 Done.
401 403 404 Refused. ApiError
POST/api/v1/tickets/archive-resolvedMove every resolved request (of one app and kind, when given) to archived.
Operation archiveResolvedTickets · authentication: deviceToken, session · scope write
Request body
application/json
Responses
200 How many were archived.
archived: integer
401 403 404 Refused. ApiError
GET/api/v1/files/{id}An attachment's bytes, as a download.
Operation getAttachment · authentication: deviceToken, session · scope inbox
Parameters
Responses
200 The file. Images keep their type; text is text/plain or application/json; anything else is application/octet-stream.
string (binary)
401 403 404 Refused. ApiError
GET/api/v1/export/inboxRequests as CSV, with the same filters as the list (status defaults to all).
Operation exportTickets · authentication: deviceToken, session · scope inbox
Parameters
| Name | In | Type | |
|---|
app optional | query | string | |
kind optional | query | TicketKind | |
status optional | query | string | |
q optional | query | string | |
Responses
200 CSV with a header row.
string
401 403 Refused. ApiError
Waitlist
Waitlist signups.
GET/api/v1/waitlistWaitlist signups, newest first, with every list's counts.
Operation listSignups · authentication: deviceToken, session · scope inbox
Parameters
| Name | In | Type | |
|---|
app optional | query | string | |
list optional | query | string | |
status optional | query | SignupStatus | |
q optional | query | string | |
limit optional | query | integer (min 1, max 500) | |
offset optional | query | integer (min 0) | |
Responses
200 One page of signups.
WaitlistPage
total:
integer
items:
array of <a href="#schema-Signup">Signup</a>
lists:
array of object
appId:
string
list:
string
total:
integer
waiting:
integer
invited:
integer
joined:
integer
counts:
object
total:
integer
byApp:
object 401 403 404 Refused. ApiError
GET/api/v1/waitlist/seriesSignups per bucket over a window, for one app or all.
Operation getSignupSeries · authentication: deviceToken, session · scope analytics
Parameters
| Name | In | Type | |
|---|
app optional | query | string | |
range optional | query | RangeKey | The window. Ignored when from and to are both given. Unknown values fall back to 7d. |
from optional | query | WindowEnd | Start of a custom window, UTC: a day (YYYY-MM-DD, from its first minute) or a minute (YYYY-MM-DDTHH:MM, e.g. for the last 4 hours). Used only with to; the two may mix forms. |
to optional | query | WindowEnd | End of a custom window, UTC and inclusive: a day (YYYY-MM-DD, through its last minute) or a minute (YYYY-MM-DDTHH:MM, through the end of that minute). from=2026-10-24T10:00&to=2026-10-24T13:59 is the four hours from 10:00 to 14:00. An end after now stops at now; ends given the wrong way round are swapped. |
Responses
200 The series.
range:
Range
series:
array of object
t:
Time
signups:
integer 401 403 404 Refused. ApiError
POST/api/v1/waitlist/{id}/statusMark a signup as waiting, invited or joined. Sends no email.
Operation setSignupStatus · authentication: deviceToken, session · scope write
Parameters
| Name | In | Type | |
|---|
id | path | string (pattern ^[0-9]+$) | |
Request body
application/json
Responses
200 Done.
400 401 403 404 Refused. ApiError
POST/api/v1/waitlist/{id}/deleteRemove a signup.
Operation deleteSignup · authentication: deviceToken, session · scope write
Parameters
| Name | In | Type | |
|---|
id | path | string (pattern ^[0-9]+$) | |
Responses
200 Done.
401 403 404 Refused. ApiError
GET/api/v1/export/waitlistSignups as CSV, with the same filters as the list.
Operation exportSignups · authentication: deviceToken, session · scope inbox
Parameters
| Name | In | Type | |
|---|
app optional | query | string | |
list optional | query | string | |
status optional | query | SignupStatus | |
q optional | query | string | |
Responses
200 CSV with a header row.
string
401 403 Refused. ApiError
Errors
Errors from the apps, grouped into issues.
GET/api/v1/apps/{app}/errorsAn app's errors, grouped into issues, with their numbers over a window; the last seen first.
Operation listErrors · authentication: deviceToken, session · scope analytics
Issues seen in the window. count and visitors are the window's; total is every occurrence ever. A filter on release or environment counts only those occurrences.
Parameters
| Name | In | Type | |
|---|
app | path | string | |
status optional | query | "open" | "resolved" | "ignored" | "all" | open (default), resolved, ignored or all. |
release optional | query | string | |
environment optional | query | string | |
source optional | query | ErrorSource | Only issues with occurrences caught this way. |
q optional | query | string | Words in the message or type. |
range optional | query | RangeKey | The window. Ignored when from and to are both given. Unknown values fall back to 7d. |
from optional | query | WindowEnd | Start of a custom window, UTC: a day (YYYY-MM-DD, from its first minute) or a minute (YYYY-MM-DDTHH:MM, e.g. for the last 4 hours). Used only with to; the two may mix forms. |
to optional | query | WindowEnd | End of a custom window, UTC and inclusive: a day (YYYY-MM-DD, through its last minute) or a minute (YYYY-MM-DDTHH:MM, through the end of that minute). from=2026-10-24T10:00&to=2026-10-24T13:59 is the four hours from 10:00 to 14:00. An end after now stops at now; ends given the wrong way round are swapped. |
limit optional | query | integer (min 1, max 500) | |
offset optional | query | integer (min 0) | |
Responses
200 A page of issues.
total:
integer
items:
array of <a href="#schema-ErrorIssue">ErrorIssue</a>
releases:
array of string // The releases seen in the window, the newest first.
environments:
array of string
range:
Range 400 401 403 404 Refused. ApiError
POST/api/v1/apps/{app}/errors/settingsThe app's error rules, replaced whole; getAppSettings shows them as errors.
Operation setErrorSettings · authentication: deviceToken, session · scope write
Parameters
Request body
application/json
ErrorSettings
capture:
boolean // Uncaught errors and rejected promises.
console:
boolean // console.error calls (the console still shows them).
network:
boolean // fetch and XMLHttpRequest requests that fail, or answer 5xx.
network4xx:
boolean // With network, 4xx answers too.
resources:
boolean // Scripts, style sheets and images that do not load.
ignore:
array of string <span class="d">(max length 200)</span> (max items 50) // Reports whose message contains one of these (any case) are dropped. Responses
200 The rules as saved.
ErrorSettings
capture:
boolean // Uncaught errors and rejected promises.
console:
boolean // console.error calls (the console still shows them).
network:
boolean // fetch and XMLHttpRequest requests that fail, or answer 5xx.
network4xx:
boolean // With network, 4xx answers too.
resources:
boolean // Scripts, style sheets and images that do not load.
ignore:
array of string <span class="d">(max length 200)</span> (max items 50) // Reports whose message contains one of these (any case) are dropped. 400 401 403 404 Refused. ApiError
GET/api/v1/errors/{error}One issue with its breakdowns, the last 30 days by day, its notes and the latest occurrence.
Operation getError · authentication: deviceToken, session · scope analytics
Parameters
| Name | In | Type | |
|---|
error | path | string (pattern ^e_) | |
Responses
200 The issue.
ErrorDetail
id:
string
appId:
string
type:
string | null
message:
string
culprit:
string | null // Where it happened, the top frame of the app's own code, e.g. render (app.js:12).
status:
"open" | "resolved" | "ignored"
count:
integer // Occurrences in the window (or all of them, for getError).
visitors:
integer // Visitors it happened to, counted per day like the dashboard's.
total:
integer // Every occurrence ever.
firstSeen:
Time
lastSeen:
Time
lastRelease:
string | null
lastEnvironment:
string | null
reopenedAt:
integer | null // When it last came back after being resolved.
resolvedAt:
integer | null
spark?:
array of integer // In listErrors only: occurrences per UTC day over the last 14 days up to today, oldest first (14 numbers), whatever the window, for a sparkline.
sources:
array of <a href="#schema-Row">Row</a>
releases:
array of <a href="#schema-Row">Row</a>
environments:
array of <a href="#schema-Row">Row</a>
browsers:
array of <a href="#schema-Row">Row</a>
systems:
array of <a href="#schema-Row">Row</a>
devices:
array of <a href="#schema-Row">Row</a>
pages:
array of <a href="#schema-Row">Row</a>
series:
array of object // The last 30 days, by UTC day.
t:
Time
count:
integer
notes:
array of object
id:
integer
ts:
Time
kind:
string // note, or system (it came back, it was resolved).
body:
string
latest:
ErrorOccurrence 401 403 404 Refused. ApiError
GET/api/v1/errors/{error}/occurrencesAn issue's occurrences, the newest first, with their full stacks.
Operation listErrorOccurrences · authentication: deviceToken, session · scope analytics
Parameters
| Name | In | Type | |
|---|
error | path | string (pattern ^e_) | |
before optional | query | integer | The next of the previous page. |
limit optional | query | integer (min 1, max 500) | |
Responses
200 A page.
items: array of <a href="#schema-ErrorOccurrence">ErrorOccurrence</a>
next: integer | null
401 403 404 Refused. ApiError
POST/api/v1/errors/{error}/statusOpen, resolve or ignore an issue. A resolved issue opens again by itself when it happens again.
Operation setErrorStatus · authentication: deviceToken, session · scope write
Parameters
| Name | In | Type | |
|---|
error | path | string (pattern ^e_) | |
Request body
application/json
status: "open" | "resolved" | "ignored"
Responses
200 The issue.
ErrorIssue
id:
string
appId:
string
type:
string | null
message:
string
culprit:
string | null // Where it happened, the top frame of the app's own code, e.g. render (app.js:12).
status:
"open" | "resolved" | "ignored"
count:
integer // Occurrences in the window (or all of them, for getError).
visitors:
integer // Visitors it happened to, counted per day like the dashboard's.
total:
integer // Every occurrence ever.
firstSeen:
Time
lastSeen:
Time
lastRelease:
string | null
lastEnvironment:
string | null
reopenedAt:
integer | null // When it last came back after being resolved.
resolvedAt:
integer | null
spark?:
array of integer // In listErrors only: occurrences per UTC day over the last 14 days up to today, oldest first (14 numbers), whatever the window, for a sparkline. 400 401 403 404 Refused. ApiError
POST/api/v1/errors/{error}/notesAn internal note on an issue.
Operation addErrorNote · authentication: deviceToken, session · scope write
Parameters
| Name | In | Type | |
|---|
error | path | string (pattern ^e_) | |
Request body
application/json
body: string (max length 5000)
Responses
200 Done.
400 401 403 404 Refused. ApiError
Tracing
OpenTelemetry traces and logs.
POST/_care/v1/tracesOTLP/HTTP trace export, as any OpenTelemetry SDK or Collector sends it.
Operation otlpTraces · authentication: serverKey
Authenticate with the app's server key, in Authorization: Bearer <key> or x-logsure-key: <key>; the key decides the app. Point an exporter at it with OTEL_EXPORTER_OTLP_ENDPOINT=https://<host>/_care and OTEL_EXPORTER_OTLP_HEADERS=x-logsure-key=<key>. The answer is in the request's format: an empty ExportTraceServiceResponse, with partialSuccess counting rejectedSpans dropped by the app's daily cap or its sample rate. Up to 4 MB as sent and 16 MB unpacked.
Request body
application/x-protobuf
string (binary)
application/json
object
Responses
200 Taken (partly, when partialSuccess says so).
string (binary)
400 401 403 413 415 429 Refused, with a stable code. IngestError
POST/_care/v1/logsOTLP/HTTP log export, as any OpenTelemetry SDK or Collector sends it.
Operation otlpLogs · authentication: serverKey
Authenticate with the app's server key, in Authorization: Bearer <key> or x-logsure-key: <key>; the key decides the app. Point an exporter at it with OTEL_EXPORTER_OTLP_ENDPOINT=https://<host>/_care and OTEL_EXPORTER_OTLP_HEADERS=x-logsure-key=<key>. The answer is in the request's format: an empty ExportLogsServiceResponse, with partialSuccess counting rejectedLogRecords dropped by the app's daily cap or its sample rate. Up to 4 MB as sent and 16 MB unpacked.
Request body
application/x-protobuf
string (binary)
application/json
object
Responses
200 Taken (partly, when partialSuccess says so).
string (binary)
400 401 403 413 415 429 Refused, with a stable code. IngestError
POST/_care/v1/metricsOTLP metrics are not taken yet.
Operation otlpMetrics · authentication: none
Responses
501 Refused, with a stable code. IngestError
GET/api/v1/apps/{app}/tracesTraces (one row each, named by the root span), the newest or the slowest first.
Operation listTraces · authentication: deviceToken, session · scope analytics
Parameters
| Name | In | Type | |
|---|
app | path | string | |
range optional | query | RangeKey | The window. Ignored when from and to are both given. Unknown values fall back to 7d. |
from optional | query | WindowEnd | Start of a custom window, UTC: a day (YYYY-MM-DD, from its first minute) or a minute (YYYY-MM-DDTHH:MM, e.g. for the last 4 hours). Used only with to; the two may mix forms. |
to optional | query | WindowEnd | End of a custom window, UTC and inclusive: a day (YYYY-MM-DD, through its last minute) or a minute (YYYY-MM-DDTHH:MM, through the end of that minute). from=2026-10-24T10:00&to=2026-10-24T13:59 is the four hours from 10:00 to 14:00. An end after now stops at now; ends given the wrong way round are swapped. |
service optional | query | string | |
name optional | query | string | The root span's name; a trailing * matches a prefix. |
status optional | query | "ok" | "error" | "unset" | error for traces with an error span anywhere. |
minDuration optional | query | number | Milliseconds. |
maxDuration optional | query | number | |
attr optional | query | array of string | key=value, repeatable; all must match. http.route, http.request.method, http.response.status_code and db.system are fast; any other key is matched in the stored attributes. |
sort optional | query | "time" | "duration" | |
limit optional | query | integer (min 1, max 500) | |
offset optional | query | integer (min 0) | |
Responses
200 A page.
items:
array of <a href="#schema-TraceSummary">TraceSummary</a>
next:
integer | null // The offset of the next page.
services:
array of string // The services seen in the window.
range:
Range 400 401 403 404 Refused. ApiError
GET/api/v1/apps/{app}/traces/{trace}Every span of a trace, for a waterfall, with its logs.
Operation getTrace · authentication: deviceToken, session · scope analytics
Parameters
| Name | In | Type | |
|---|
app | path | string | |
trace | path | string (pattern ^[0-9a-f]{32}$) | |
Responses
200 The trace.
Trace
traceId:
string
start:
number
duration:
number
services:
array of string
spans:
array of <a href="#schema-Span">Span</a> // Ordered by start; a parent before its children.
logs:
array of <a href="#schema-LogEntry">LogEntry</a>
errors:
array of object // The error issues this trace's exceptions and error logs belong to.
id:
string
message:
string
spanId:
string | null 401 403 404 Refused. ApiError
GET/api/v1/apps/{app}/traces/{trace}/spans/{span}One span in full, with its resource, events, links and logs.
Operation getSpan · authentication: deviceToken, session · scope analytics
Parameters
| Name | In | Type | |
|---|
app | path | string | |
trace | path | string (pattern ^[0-9a-f]{32}$) | |
span | path | string (pattern ^[0-9a-f]{16}$) | |
Responses
200 The span.
SpanDetail
traceId:
string
spanId:
string
parentId:
string | null
name:
string
kind:
SpanKind
service:
string
start:
number
duration:
number
status:
SpanStatus
statusMessage:
string | null
route:
string | null
method:
string | null
httpStatus:
integer | null
dbSystem:
string | null
attributes:
object
events:
array of object
time:
number
name:
string
attributes:
object
resource:
object
scope:
string | null // The instrumentation scope's name (and version).
links:
array of object
traceId:
string
spanId:
string
attributes:
object
logs:
array of <a href="#schema-LogEntry">LogEntry</a> 401 403 404 Refused. ApiError
GET/api/v1/apps/{app}/logsLogs, the newest (by their own time) first, or the ones stored after a cursor (in arrival order) to follow along.
Operation searchLogs · authentication: deviceToken, session · scope analytics
Parameters
| Name | In | Type | |
|---|
app | path | string | |
range optional | query | RangeKey | The window. Ignored when from and to are both given. Unknown values fall back to 7d. |
from optional | query | WindowEnd | Start of a custom window, UTC: a day (YYYY-MM-DD, from its first minute) or a minute (YYYY-MM-DDTHH:MM, e.g. for the last 4 hours). Used only with to; the two may mix forms. |
to optional | query | WindowEnd | End of a custom window, UTC and inclusive: a day (YYYY-MM-DD, through its last minute) or a minute (YYYY-MM-DDTHH:MM, through the end of that minute). from=2026-10-24T10:00&to=2026-10-24T13:59 is the four hours from 10:00 to 14:00. An end after now stops at now; ends given the wrong way round are swapped. |
q optional | query | string | Words in the body. |
severity optional | query | string | The least severity, as OpenTelemetry's numbers (9 info, 13 warn, 17 error, 21 fatal) or words. |
service optional | query | string | |
trace optional | query | string | |
attr optional | query | array of string | key=value, repeatable; all must match. http.route, http.request.method, http.response.status_code and db.system are fast; any other key is matched in the stored attributes. |
before optional | query | integer | The next of the previous page. |
after optional | query | integer | Only logs stored after this one (an id), the oldest first, for polling. |
limit optional | query | integer (min 1, max 500) | |
Responses
200 A page.
items: array of <a href="#schema-LogEntry">LogEntry</a>
next: integer | null
last: integer | null // The newest id in the answer, for after next time.
400 401 403 404 Refused. ApiError
GET/api/v1/apps/{app}/logs/tailNew logs as they arrive, as Server-Sent Events, with searchLogs' filters.
Operation tailLogs · authentication: deviceToken, session · scope analytics
Each event is data: <LogEntry JSON> with the log's id as the event id; a reconnecting client's Last-Event-ID (or after) picks up where it left. A comment line every 15 seconds keeps the connection open; the stream ends after 10 minutes, and the client reconnects.
Parameters
| Name | In | Type | |
|---|
app | path | string | |
after optional | query | integer | |
severity optional | query | string | |
service optional | query | string | |
q optional | query | string | |
Responses
200 The stream.
string
400 401 403 404 Refused. ApiError
GET/api/v1/apps/{app}/servicesPer service and operation, over a window, the rate, error rate and p50, p95 and p99 latency.
Operation getServiceStats · authentication: deviceToken, session · scope analytics
From spans of kind server and consumer, and root spans. Percentiles sample at most 10,000 durations per series.
Parameters
| Name | In | Type | |
|---|
app | path | string | |
range optional | query | RangeKey | The window. Ignored when from and to are both given. Unknown values fall back to 7d. |
from optional | query | WindowEnd | Start of a custom window, UTC: a day (YYYY-MM-DD, from its first minute) or a minute (YYYY-MM-DDTHH:MM, e.g. for the last 4 hours). Used only with to; the two may mix forms. |
to optional | query | WindowEnd | End of a custom window, UTC and inclusive: a day (YYYY-MM-DD, through its last minute) or a minute (YYYY-MM-DDTHH:MM, through the end of that minute). from=2026-10-24T10:00&to=2026-10-24T13:59 is the four hours from 10:00 to 14:00. An end after now stops at now; ends given the wrong way round are swapped. |
service optional | query | string | |
Responses
200 The services.
services:
array of <a href="#schema-ServiceStats">ServiceStats</a>
range:
Range 401 403 404 Refused. ApiError
POST/api/v1/apps/{app}/tracing/settingsThe app's tracing rules, replaced whole; getAppSettings shows them as tracing.
Operation setTracingSettings · authentication: deviceToken, session · scope write
Parameters
Request body
application/json
TracingSettings
sampleRate:
number (min 0, max 1) // The share of traces kept, whole traces by their ID (1, the default, keeps all the SDKs send).
denyAttributes:
array of string <span class="d">(max length 200)</span> (max items 100) // Attribute keys removed before storage, on top of the built-in ones (credentials, cookies, authorization headers); a trailing * removes a prefix, e.g. app.user.*. Responses
200 The rules as saved.
TracingSettings
sampleRate:
number (min 0, max 1) // The share of traces kept, whole traces by their ID (1, the default, keeps all the SDKs send).
denyAttributes:
array of string <span class="d">(max length 200)</span> (max items 100) // Attribute keys removed before storage, on top of the built-in ones (credentials, cookies, authorization headers); a trailing * removes a prefix, e.g. app.user.*. 400 401 403 404 Refused. ApiError
Lab
A/B testing without cookies: experiments, assignment in the site, and results.
GET/_care/flagsThe app's flags and their rules, for evaluating in the page or in a back-end.
Operation getFlagConfig · authentication: serverKey or none
Public and cacheable (30 seconds, with an ETag): no id and no attribute is sent here, so the tracker evaluates flags in the page and nothing about the visitor reaches the server. From one of the app's sites (or with app), or with the server key. Empty when lab is off for the app.
Parameters
| Name | In | Type | |
|---|
app optional | query | string | |
Responses
200 The flags.
FlagConfig
flags:
array of <a href="#schema-FlagDefinition">FlagDefinition</a> 304 Unchanged since the If-None-Match ETag.
401 403 404 Refused, with a stable code. IngestError
POST/_care/flags/evaluateEvaluate the app's flags for an id and attributes, for back-ends that would rather not evaluate locally.
Operation evaluateFlags · authentication: serverKey
The id and attributes are used for the answer and not stored.
Request body
application/json
id?: string | null // The app's own id for the user, for rules on id.
score?: number | string | null // The app's score, 0 to 100 (clamped); not a number is no score. Used only by flags that use it.
attrs?: object
flags?: array of string // Only these keys (default every flag).
Responses
200 Each flag's value.
flags: object
400 401 403 Refused, with a stable code. IngestError
GET/_care/experimentsThe app's running experiments, for logsure.variant in the tracker.
Operation getRunningExperiments · authentication: none
From one of the app's sites (or with app), like errors/config. Empty when lab is off for the app. Cached for a minute.
Parameters
| Name | In | Type | |
|---|
app optional | query | string | |
Responses
200 The running experiments.
experiments: array of object
key: string
variants: array of <a href="#schema-Variant">Variant</a>
403 404 Refused, with a stable code. IngestError
GET/api/v1/apps/{app}/flagsThe app's feature flags, by key.
Operation listFlags · authentication: deviceToken, session · scope analytics
Parameters
Responses
200 The flags.
items: array of <a href="#schema-Flag">Flag</a>
401 403 404 Refused. ApiError
POST/api/v1/apps/{app}/flagsA new flag, off until switched on.
Operation createFlag · authentication: deviceToken, session · scope write
Parameters
Request body
application/json
FlagInput
key:
string (pattern ^[a-z0-9][a-z0-9_.-]{0,59}$)
name:
string (min length 1, max length 100)
description?:
string | null (max length 1000)
type:
FlagType
variants?:
array of <a href="#schema-FlagVariant">FlagVariant</a> (min items 1, max items 10) // The values it may serve; default for boolean, on (true) and off (false).
defaultVariant?:
string // Served while the flag is off (the kill switch), and, when it uses the score, without a score or outside every range. Default the last variant (off for boolean).
fallthrough?:
string // Served when the flag is on, no rule matched, and it does not use the score. Default the first variant that is not the default one (on for boolean).
rules?:
array of <a href="#schema-FlagRule">FlagRule</a> (max items 50) // Tried in order; the first that matches serves, before the score.
useScore?:
boolean
ranges?:
array of <a href="#schema-FlagScoreRange">FlagScoreRange</a>
exposure?:
boolean // Send a $flag event (flag, variant) the first time a page evaluates it, to analyse it like an experiment. Responses
201 Made.
Flag
key:
string
type:
FlagType
enabled:
boolean // The kill switch, apart from the value; off serves the default variant.
variants:
array of <a href="#schema-FlagVariant">FlagVariant</a>
defaultVariant:
string
fallthrough:
string
rules:
array of <a href="#schema-FlagRule">FlagRule</a>
useScore:
boolean
ranges:
array of <a href="#schema-FlagScoreRange">FlagScoreRange</a>
exposure:
boolean
version:
integer // Goes up with every change.
id:
string
appId:
string
name:
string
description:
string | null
createdAt:
Time
updatedAt:
Time
experiments:
array of string // The IDs of the experiments that measure it.
overlaps:
array of object // For the editor's warning, each range that overlaps an earlier one (which serves).
range:
integer
earlier:
integer 400 401 403 404 409 Refused. ApiError
GET/api/v1/apps/{app}/flags/{flag}One flag.
Operation getFlag · authentication: deviceToken, session · scope analytics
Parameters
| Name | In | Type | |
|---|
app | path | string | |
flag | path | string (pattern ^f_) | |
Responses
200 The flag.
Flag
key:
string
type:
FlagType
enabled:
boolean // The kill switch, apart from the value; off serves the default variant.
variants:
array of <a href="#schema-FlagVariant">FlagVariant</a>
defaultVariant:
string
fallthrough:
string
rules:
array of <a href="#schema-FlagRule">FlagRule</a>
useScore:
boolean
ranges:
array of <a href="#schema-FlagScoreRange">FlagScoreRange</a>
exposure:
boolean
version:
integer // Goes up with every change.
id:
string
appId:
string
name:
string
description:
string | null
createdAt:
Time
updatedAt:
Time
experiments:
array of string // The IDs of the experiments that measure it.
overlaps:
array of object // For the editor's warning, each range that overlaps an earlier one (which serves).
range:
integer
earlier:
integer 401 403 404 Refused. ApiError
POST/api/v1/apps/{app}/flags/{flag}Change a flag; fields left out keep their value. The type and key are fixed.
Operation updateFlag · authentication: deviceToken, session · scope write
Parameters
| Name | In | Type | |
|---|
app | path | string | |
flag | path | string (pattern ^f_) | |
Request body
application/json
FlagChanges
name?:
string
description?:
string | null
variants?:
array of <a href="#schema-FlagVariant">FlagVariant</a>
defaultVariant?:
string
fallthrough?:
string
rules?:
array of <a href="#schema-FlagRule">FlagRule</a>
useScore?:
boolean
ranges?:
array of <a href="#schema-FlagScoreRange">FlagScoreRange</a>
exposure?:
boolean Responses
200 The flag as saved.
Flag
key:
string
type:
FlagType
enabled:
boolean // The kill switch, apart from the value; off serves the default variant.
variants:
array of <a href="#schema-FlagVariant">FlagVariant</a>
defaultVariant:
string
fallthrough:
string
rules:
array of <a href="#schema-FlagRule">FlagRule</a>
useScore:
boolean
ranges:
array of <a href="#schema-FlagScoreRange">FlagScoreRange</a>
exposure:
boolean
version:
integer // Goes up with every change.
id:
string
appId:
string
name:
string
description:
string | null
createdAt:
Time
updatedAt:
Time
experiments:
array of string // The IDs of the experiments that measure it.
overlaps:
array of object // For the editor's warning, each range that overlaps an earlier one (which serves).
range:
integer
earlier:
integer 400 401 403 404 409 Refused. ApiError
POST/api/v1/apps/{app}/flags/{flag}/toggleSwitch a flag on or off (the kill switch); off serves the default variant to everyone.
Operation toggleFlag · authentication: deviceToken, session · scope write
Parameters
| Name | In | Type | |
|---|
app | path | string | |
flag | path | string (pattern ^f_) | |
Request body
application/json
enabled: boolean
Responses
200 The flag as it is now.
Flag
key:
string
type:
FlagType
enabled:
boolean // The kill switch, apart from the value; off serves the default variant.
variants:
array of <a href="#schema-FlagVariant">FlagVariant</a>
defaultVariant:
string
fallthrough:
string
rules:
array of <a href="#schema-FlagRule">FlagRule</a>
useScore:
boolean
ranges:
array of <a href="#schema-FlagScoreRange">FlagScoreRange</a>
exposure:
boolean
version:
integer // Goes up with every change.
id:
string
appId:
string
name:
string
description:
string | null
createdAt:
Time
updatedAt:
Time
experiments:
array of string // The IDs of the experiments that measure it.
overlaps:
array of object // For the editor's warning, each range that overlaps an earlier one (which serves).
range:
integer
earlier:
integer 400 401 403 404 Refused. ApiError
POST/api/v1/apps/{app}/flags/{flag}/rolloutSet the flag's score targeting: whether it uses the app's score, and its ranges.
Operation setFlagRollout · authentication: deviceToken, session · scope write
Parameters
| Name | In | Type | |
|---|
app | path | string | |
flag | path | string (pattern ^f_) | |
Request body
application/json
FlagScoreSettings
useScore?:
boolean // Whether the flag targets by the score the app passes (0 to 100, set by the app, with whatever meaning it likes; LogSure never makes one). Off (the default), any score passed is ignored.
ranges?:
array of <a href="#schema-FlagScoreRange">FlagScoreRange</a> (max items 20) // Tried top first; a score in none of them (or no score) gets the default variant. Responses
200 The flag as it is now.
Flag
key:
string
type:
FlagType
enabled:
boolean // The kill switch, apart from the value; off serves the default variant.
variants:
array of <a href="#schema-FlagVariant">FlagVariant</a>
defaultVariant:
string
fallthrough:
string
rules:
array of <a href="#schema-FlagRule">FlagRule</a>
useScore:
boolean
ranges:
array of <a href="#schema-FlagScoreRange">FlagScoreRange</a>
exposure:
boolean
version:
integer // Goes up with every change.
id:
string
appId:
string
name:
string
description:
string | null
createdAt:
Time
updatedAt:
Time
experiments:
array of string // The IDs of the experiments that measure it.
overlaps:
array of object // For the editor's warning, each range that overlaps an earlier one (which serves).
range:
integer
earlier:
integer 400 401 403 404 Refused. ApiError
POST/api/v1/apps/{app}/flags/{flag}/deleteDelete a flag; from the web console only. The tracker serves the site's fallback for it from then.
Operation deleteFlag · authentication: session · scope write
Parameters
| Name | In | Type | |
|---|
app | path | string | |
flag | path | string (pattern ^f_) | |
Responses
200 Done.
401 403 404 Refused. ApiError
GET/api/v1/apps/{app}/flags/{flag}/historyWho changed the flag, when, and how, from the audit log; the newest first.
Operation getFlagHistory · authentication: deviceToken, session · scope analytics
Parameters
| Name | In | Type | |
|---|
app | path | string | |
flag | path | string (pattern ^f_) | |
Responses
200 The changes.
items: array of <a href="#schema-AuditEntry">AuditEntry</a>
401 403 404 Refused. ApiError
GET/api/v1/apps/{app}/experimentsThe app's experiments, the newest first, each with its exposures so far.
Operation listExperiments · authentication: deviceToken, session · scope analytics
Parameters
| Name | In | Type | |
|---|
app | path | string | |
status optional | query | "draft" | "running" | "stopped" | "all" | |
Responses
200 The experiments.
items: array of <a href="#schema-ExperimentSummary">ExperimentSummary</a>
401 403 404 Refused. ApiError
POST/api/v1/apps/{app}/experimentsA new experiment, as a draft.
Operation createExperiment · authentication: deviceToken, session · scope write
Parameters
Request body
application/json
ExperimentInput
key:
string (pattern ^[a-z0-9][a-z0-9_-]{0,59}$) // What logsure.variant and the events name it by; unique in the app.
name:
string (min length 1, max length 100)
hypothesis?:
string | null (max length 1000)
variants:
array of <a href="#schema-Variant">Variant</a> (min items 2, max items 5) // The first is the control.
goal:
ExperimentGoal
guardrails?:
array of <a href="#schema-ExperimentGoal">ExperimentGoal</a> (max items 3)
flag?:
string | null // A flag's key: the experiment measures that flag, its variants being the flag's (same keys), and its exposures also the flag's $flag events. Responses
201 Made.
Experiment
flag:
string | null
id:
string
appId:
string
key:
string
name:
string
hypothesis:
string | null
status:
"draft" | "running" | "stopped"
variants:
array of <a href="#schema-Variant">Variant</a>
goal:
ExperimentGoal
guardrails:
array of <a href="#schema-ExperimentGoal">ExperimentGoal</a>
createdAt:
Time
updatedAt:
Time
startedAt:
integer | null
stoppedAt:
integer | null 400 401 403 404 409 Refused. ApiError
GET/api/v1/apps/{app}/experiments/{experiment}One experiment.
Operation getExperiment · authentication: deviceToken, session · scope analytics
Parameters
| Name | In | Type | |
|---|
app | path | string | |
experiment | path | string (pattern ^x_) | |
Responses
200 The experiment.
Experiment
flag:
string | null
id:
string
appId:
string
key:
string
name:
string
hypothesis:
string | null
status:
"draft" | "running" | "stopped"
variants:
array of <a href="#schema-Variant">Variant</a>
goal:
ExperimentGoal
guardrails:
array of <a href="#schema-ExperimentGoal">ExperimentGoal</a>
createdAt:
Time
updatedAt:
Time
startedAt:
integer | null
stoppedAt:
integer | null 401 403 404 Refused. ApiError
POST/api/v1/apps/{app}/experiments/{experiment}Change an experiment; fields left out keep their value.
Operation updateExperiment · authentication: deviceToken, session · scope write
A draft may change everything. Once started, the key and the variants are fixed (409); the name, hypothesis, goal and guardrails may still change, and results are computed again from the events.
Parameters
| Name | In | Type | |
|---|
app | path | string | |
experiment | path | string (pattern ^x_) | |
Request body
application/json
ExperimentChanges
key?:
string
name?:
string
hypothesis?:
string | null
variants?:
array of <a href="#schema-Variant">Variant</a>
goal?:
ExperimentGoal
guardrails?:
array of <a href="#schema-ExperimentGoal">ExperimentGoal</a>
flag?:
string | null Responses
200 The experiment as saved.
Experiment
flag:
string | null
id:
string
appId:
string
key:
string
name:
string
hypothesis:
string | null
status:
"draft" | "running" | "stopped"
variants:
array of <a href="#schema-Variant">Variant</a>
goal:
ExperimentGoal
guardrails:
array of <a href="#schema-ExperimentGoal">ExperimentGoal</a>
createdAt:
Time
updatedAt:
Time
startedAt:
integer | null
stoppedAt:
integer | null 400 401 403 404 409 Refused. ApiError
POST/api/v1/apps/{app}/experiments/{experiment}/startStart a draft; exposures count from now.
Operation startExperiment · authentication: deviceToken, session · scope write
Parameters
| Name | In | Type | |
|---|
app | path | string | |
experiment | path | string (pattern ^x_) | |
Responses
200 The experiment as it is now.
Experiment
flag:
string | null
id:
string
appId:
string
key:
string
name:
string
hypothesis:
string | null
status:
"draft" | "running" | "stopped"
variants:
array of <a href="#schema-Variant">Variant</a>
goal:
ExperimentGoal
guardrails:
array of <a href="#schema-ExperimentGoal">ExperimentGoal</a>
createdAt:
Time
updatedAt:
Time
startedAt:
integer | null
stoppedAt:
integer | null 401 403 404 409 Refused. ApiError
POST/api/v1/apps/{app}/experiments/{experiment}/stopStop a running experiment; its results stay, for the time it ran.
Operation stopExperiment · authentication: deviceToken, session · scope write
Parameters
| Name | In | Type | |
|---|
app | path | string | |
experiment | path | string (pattern ^x_) | |
Responses
200 The experiment as it is now.
Experiment
flag:
string | null
id:
string
appId:
string
key:
string
name:
string
hypothesis:
string | null
status:
"draft" | "running" | "stopped"
variants:
array of <a href="#schema-Variant">Variant</a>
goal:
ExperimentGoal
guardrails:
array of <a href="#schema-ExperimentGoal">ExperimentGoal</a>
createdAt:
Time
updatedAt:
Time
startedAt:
integer | null
stoppedAt:
integer | null 401 403 404 409 Refused. ApiError
POST/api/v1/apps/{app}/experiments/{experiment}/deleteDelete a draft or a stopped experiment (its exposure events stay with analytics).
Operation deleteExperiment · authentication: deviceToken, session · scope write
Parameters
| Name | In | Type | |
|---|
app | path | string | |
experiment | path | string (pattern ^x_) | |
Responses
200 Done.
401 403 404 409 Refused. ApiError
GET/api/v1/apps/{app}/experiments/{experiment}/resultsPer variant, exposures, conversions and rates, with uplift, confidence, the chance to beat control and checks.
Operation getExperimentResults · authentication: deviceToken, session · scope analytics
The unit is a visitor-day (LogSure keeps no identifier across days): a unit is exposed at its first exposure event, to that event's variant, and converts when it reaches the goal after it, the same UTC day. Uplift is relative to control (the first variant), with a 95% interval; the p-value is a two-sided two-proportion z test; probBeatControl is P(rate > control's) with Beta(1 + conversions, 1 + non-conversions) posteriors. A sample-ratio mismatch (chi-square p below 0.001 against the weights) makes the results untrustworthy.
Parameters
| Name | In | Type | |
|---|
app | path | string | |
experiment | path | string (pattern ^x_) | |
segment optional | query | "device" | "country" | "browser" | "os" | "source" | A dimension to split the results by. |
Responses
200 The results.
ExperimentResults
experiment:
Experiment
from:
integer | null // When it started (null for a draft).
to:
Time
unit:
"visitor-day"
control:
string
variants:
array of <a href="#schema-VariantResult">VariantResult</a>
srm:
object
pValue:
number | null
mismatch:
boolean
expected:
array of number // Exposures each variant should have by its weight.
observed:
array of integer
enough:
boolean // Every variant has the minimum exposures and conversions.
minimum:
object
exposures:
integer
conversions:
integer
hint:
"draft" | "collecting" | "keep_running" | "variant_ahead" | "control_ahead" | "srm" // collecting: below the minimum. keep_running: no clear answer yet. variant_ahead: a variant beats control (p below 0.05 and probBeatControl at least 0.95), named in best. control_ahead: every variant is worse. srm: the split does not match the weights; check the assignment before reading anything.
best:
string | null
guardrails:
array of object
goal:
ExperimentGoal
variants:
array of <a href="#schema-VariantResult">VariantResult</a>
harmed:
array of string // Variants clearly worse than control on it (p below 0.05, the wrong way).
series:
array of object // Cumulative, by UTC day.
t:
Time
variants:
array of object
key:
string
exposures:
integer
conversions:
integer
rate:
number | null
segments:
object | null
dimension:
string
rows:
array of object // The 10 biggest values.
value:
string | null
variants:
array of <a href="#schema-VariantResult">VariantResult</a> 400 401 403 404 Refused. ApiError
Features
Switching whole features off, for the instance and per app.
GET/api/v1/featuresWhich features are on, for the instance and for each app the user sees.
Operation getFeatures · authentication: deviceToken, session · scope analytics
A feature is on for an app only when the instance allows it and the app has not switched it off; funnels and lab also need analytics. getMe has the same answer as features, for clients to hide what is off.
Responses
200 The switches.
FeatureState
instance:
InstanceFeatures
defaults:
Features // The deployment's defaults (FEATURES_DISABLED), for what the instance has not set.
apps:
array of <a href="#schema-AppFeatures">AppFeatures</a> 401 403 Refused. ApiError
POST/api/v1/featuresSwitch features on or off for the whole instance; keys left out keep their value.
Operation setFeatures · authentication: deviceToken, session · scope write
mcp is the MCP server's switch (turnAiOn and turnAiOff): it changes only from the web console, signed in, and turning it on needs the owner's two-factor sign-in. Data already stored stays until its retention or deleteFeatureData.
Request body
application/json
FeatureChanges
analytics?:
boolean
funnels?:
boolean
inbox?:
boolean
waitlist?:
boolean
errors?:
boolean
tracing?:
boolean
lab?:
boolean
mcp?:
boolean // setFeatures only, from the web console. Responses
200 The switches as saved.
FeatureState
instance:
InstanceFeatures
defaults:
Features // The deployment's defaults (FEATURES_DISABLED), for what the instance has not set.
apps:
array of <a href="#schema-AppFeatures">AppFeatures</a> 400 401 403 Refused. ApiError
POST/api/v1/apps/{app}/featuresSwitch features off (or back on) for one app; keys left out keep their value.
Operation setAppFeatures · authentication: deviceToken, session · scope write
Parameters
Request body
application/json
FeatureChanges
analytics?:
boolean
funnels?:
boolean
inbox?:
boolean
waitlist?:
boolean
errors?:
boolean
tracing?:
boolean
lab?:
boolean
mcp?:
boolean // setFeatures only, from the web console. Responses
200 The app's switches as saved.
400 401 403 404 Refused. ApiError
POST/api/v1/apps/{app}/features/{feature}/deleteDelete everything one feature stored for an app, at once and for good.
Operation deleteFeatureData · authentication: deviceToken, session · scope write
analytics: events, sessions and imported Google Analytics data. funnels: the saved funnels. inbox: feedback, bug reports and messages with their attachments and notes. waitlist: the signups. errors: issues, occurrences and notes. tracing: spans, traces and logs. lab: the experiments. The feature need not be off. Audited.
Parameters
Responses
200 How many rows went, by kind.
401 403 404 Refused. ApiError
Account
The signed-in user, devices, notifications and privacy requests.
POST/api/v1/sessionSign in with the username and password (any username until one is set; the root password from the deployment until you set your own).
Operation signIn · authentication: none · scope analytics
Request body
application/json
username: string
password: string
remember?: boolean // Stay signed in for 30 days instead of 12 hours.
Responses
200 Signed in; the session cookie is set.
signedIn: true
202 The password was right and two-factor sign-in is on; send the code to /api/v1/session/code. A short-lived cookie carries the pending sign-in.
needsCode: true
400 401 403 429 503 Refused. ApiError
DELETE/api/v1/sessionSign out; the session cookie is cleared.
Operation signOut · authentication: none · scope analytics
Responses
204 Signed out.
403 Refused. ApiError
POST/api/v1/session/codeThe second step of signing in, with the 6-digit code from the authenticator app or a recovery code.
Operation signInCode · authentication: none · scope analytics
Request body
application/json
code: string
Responses
200 Signed in; the session cookie is set and the pending one cleared.
signedIn: true
401 A wrong code (unauthorized), or no pending sign-in, e.g. it took longer than 10 minutes (expired). ApiError
403 429 Refused. ApiError
POST/api/v1/session/passkey/optionsThe request to sign in with a passkey, for navigator.credentials.get(); no username needed.
Operation passkeySignInOptions · authentication: none · scope analytics
From the console's own origin. The challenge works once, for 5 minutes. allowCredentials is empty, so the browser offers every passkey it has for this site (conditional UI works too).
Request body optional
application/json
object
Responses
200 The options.
403 429 Refused. ApiError
POST/api/v1/session/passkeySign in with a passkey; it stands for both the password and the two-factor code.
Operation signInPasskey · authentication: none · scope analytics
The passkey must have checked it was the person (a fingerprint, face or PIN: user verification). The answer is as signIn's when signed in, with the session cookie; there is no code step. Wrong passkeys count toward the same lockouts as wrong passwords, and every sign-in, right or wrong, is in the audit log.
Request body
application/json
Responses
200 Signed in; the session cookie is set.
signedIn: true
400 401 403 429 Refused. ApiError
GET/api/v1/meThe device, the user and their role, the apps, the bell and the request counts.
Operation getMe · authentication: deviceToken, session · scope analytics
Parameters
| Name | In | Type | |
|---|
include optional | query | string | errors, to have new and returning errors in the bell (clients that know the error item). |
Responses
200 Everything a client needs to start.
Me
features:
FeatureState // What is on, as getFeatures answers; clients hide the menus, panels and dashboard panel kinds of what is off.
device:
DeviceRef
user:
string | null // The sign-in username, when one is set.
name:
string | null // The user's display name.
userId:
string
role:
Role
appAccess:
array | null // The app IDs this user is limited to; null for every app.
twoFactorRequired:
boolean // The team requires two-factor sign-in and this user has neither a two-factor app nor a passkey yet.
passkeys:
integer // How many passkeys this user has.
apps:
array of <a href="#schema-App">App</a>
notifications:
Notifications
counts:
TicketCounts 401 403 Refused. ApiError
POST/api/v1/apps/{app}Rename an app, replace its domains, or set its webhook and bot check.
Operation updateApp · authentication: deviceToken, session · scope write
Parameters
Request body
application/json
name?: string (max length 100)
domains?: array of string
webhook?: string // An https:// URL for a short note on each new request or signup (no personal data); '' for none.
turnstileSecret?: string // The Cloudflare Turnstile secret key that forms must pass; '' turns the check off. Never read back.
Responses
200 The app as saved.
400 401 403 404 Refused. ApiError
POST/api/v1/appsRegister an app.
Operation createApp · authentication: deviceToken, session · scope write
Request body
application/json
id: string (pattern ^[a-z0-9][a-z0-9-]{1,31}$) // Never changes; pages and the API name the app with it.
name: string (max length 100)
domains?: array of string // example.com (www. included), *.example.com, or host:port for development.
Responses
201 The app.
400 401 403 Refused. ApiError
GET/api/v1/apps/{app}/settingsEverything the app's settings page shows, its status today and the setup facts.
Operation getAppSettings · authentication: deviceToken, session · scope analytics
Parameters
Responses
200 The settings.
AppSettings
features:
AppFeatures
app:
App
hasKey:
boolean
keyHint:
string | null // The server key's last 4 characters.
webhook:
string | null
turnstile:
boolean // Whether a Turnstile secret is set (the secret itself is never shown).
forms:
object
bug:
array of <a href="#schema-FormField">FormField</a>
feedback:
array of <a href="#schema-FormField">FormField</a>
message:
array of <a href="#schema-FormField">FormField</a>
attachments:
AttachmentRules
retention:
RetentionRules
native:
NativeRules
waitlist:
WaitlistRules
notes:
string | null
segments:
array of <a href="#schema-Segment">Segment</a>
defaultSegment:
string | null
funnels:
array of <a href="#schema-Funnel">Funnel</a>
errors:
ErrorSettings
tracing:
TracingSettings
effective:
object // The rules in force, the deployment's defaults filled in.
attachments:
object
count:
integer
maxFileBytes:
integer
maxTotalBytes:
integer
extraTypes:
array of string
retention:
object
eventDays:
integer
resolvedDays:
integer
spamDays:
integer
attachmentDays:
integer
ticketDays:
integer | null
waitlist:
object
requireConsent:
boolean
consentVersion:
string | null
defaultList:
string
status:
object
lastEvent:
integer | null
today:
object // Counts of the current UTC day.
events:
integer
tickets:
integer
signups:
integer
dropped:
integer // Events over the daily cap.
rateLimited:
integer
overCap:
integer // Requests or signups over a daily cap.
setup:
object // The facts the setup instructions are made from.
prefix:
string
consoleHost:
string
script:
string // The script tag for the app's own host, routed to this server under the prefix.
consoleScript:
string // The script tag loading straight from the console's host.
endpoint:
string // Where back-ends send events with the server key.
domains:
array of object
domain:
string
route:
"live" | "redirect" | "none" | "wildcard" | "development" // Whether https://<domain><prefix>/health reaches this server (probed, cached a minute). 401 403 404 Refused. ApiError
POST/api/v1/apps/{app}/rulesSet some of an app's rules. Every rule given is checked before any is saved; null removes one.
Operation setAppRules · authentication: deviceToken, session · scope write
Parameters
Request body
application/json
forms?:
object | null // Fields per kind (bug, feedback, message); a kind given replaces that kind's fields, the others stay. null removes them all.
attachments?:
AttachmentRules
retention?:
RetentionRules
native?:
NativeRules
waitlist?:
WaitlistRules
notes?:
string | null // Support notes (Markdown, at most 50 KB), shown with every request of the app. Responses
200 The settings as saved.
AppSettings
features:
AppFeatures
app:
App
hasKey:
boolean
keyHint:
string | null // The server key's last 4 characters.
webhook:
string | null
turnstile:
boolean // Whether a Turnstile secret is set (the secret itself is never shown).
forms:
object
bug:
array of <a href="#schema-FormField">FormField</a>
feedback:
array of <a href="#schema-FormField">FormField</a>
message:
array of <a href="#schema-FormField">FormField</a>
attachments:
AttachmentRules
retention:
RetentionRules
native:
NativeRules
waitlist:
WaitlistRules
notes:
string | null
segments:
array of <a href="#schema-Segment">Segment</a>
defaultSegment:
string | null
funnels:
array of <a href="#schema-Funnel">Funnel</a>
errors:
ErrorSettings
tracing:
TracingSettings
effective:
object // The rules in force, the deployment's defaults filled in.
attachments:
object
count:
integer
maxFileBytes:
integer
maxTotalBytes:
integer
extraTypes:
array of string
retention:
object
eventDays:
integer
resolvedDays:
integer
spamDays:
integer
attachmentDays:
integer
ticketDays:
integer | null
waitlist:
object
requireConsent:
boolean
consentVersion:
string | null
defaultList:
string
status:
object
lastEvent:
integer | null
today:
object // Counts of the current UTC day.
events:
integer
tickets:
integer
signups:
integer
dropped:
integer // Events over the daily cap.
rateLimited:
integer
overCap:
integer // Requests or signups over a daily cap.
setup:
object // The facts the setup instructions are made from.
prefix:
string
consoleHost:
string
script:
string // The script tag for the app's own host, routed to this server under the prefix.
consoleScript:
string // The script tag loading straight from the console's host.
endpoint:
string // Where back-ends send events with the server key.
domains:
array of object
domain:
string
route:
"live" | "redirect" | "none" | "wildcard" | "development" // Whether https://<domain><prefix>/health reaches this server (probed, cached a minute). 400 401 403 404 Refused. ApiError
POST/api/v1/apps/{app}/segmentsReplace the app's segments, set its default segment, or both.
Operation setSegments · authentication: deviceToken, session · scope write
Parameters
Request body
application/json
segments?: array of <a href="#schema-SegmentInput">SegmentInput</a> (max items 12)
defaultSegment?: string | null // One of the segments' ids, or null for none (the dashboards then show all).
Responses
200 The settings as saved.
AppSettings
features:
AppFeatures
app:
App
hasKey:
boolean
keyHint:
string | null // The server key's last 4 characters.
webhook:
string | null
turnstile:
boolean // Whether a Turnstile secret is set (the secret itself is never shown).
forms:
object
bug:
array of <a href="#schema-FormField">FormField</a>
feedback:
array of <a href="#schema-FormField">FormField</a>
message:
array of <a href="#schema-FormField">FormField</a>
attachments:
AttachmentRules
retention:
RetentionRules
native:
NativeRules
waitlist:
WaitlistRules
notes:
string | null
segments:
array of <a href="#schema-Segment">Segment</a>
defaultSegment:
string | null
funnels:
array of <a href="#schema-Funnel">Funnel</a>
errors:
ErrorSettings
tracing:
TracingSettings
effective:
object // The rules in force, the deployment's defaults filled in.
attachments:
object
count:
integer
maxFileBytes:
integer
maxTotalBytes:
integer
extraTypes:
array of string
retention:
object
eventDays:
integer
resolvedDays:
integer
spamDays:
integer
attachmentDays:
integer
ticketDays:
integer | null
waitlist:
object
requireConsent:
boolean
consentVersion:
string | null
defaultList:
string
status:
object
lastEvent:
integer | null
today:
object // Counts of the current UTC day.
events:
integer
tickets:
integer
signups:
integer
dropped:
integer // Events over the daily cap.
rateLimited:
integer
overCap:
integer // Requests or signups over a daily cap.
setup:
object // The facts the setup instructions are made from.
prefix:
string
consoleHost:
string
script:
string // The script tag for the app's own host, routed to this server under the prefix.
consoleScript:
string // The script tag loading straight from the console's host.
endpoint:
string // Where back-ends send events with the server key.
domains:
array of object
domain:
string
route:
"live" | "redirect" | "none" | "wildcard" | "development" // Whether https://<domain><prefix>/health reaches this server (probed, cached a minute). 400 401 403 404 Refused. ApiError
POST/api/v1/apps/{app}/segments/previewWhat a segment would keep over the last 7 days, without saving it.
Operation previewSegment · authentication: deviceToken, session · scope analytics
Parameters
Request body
application/json
Responses
200 The counts.
segment:
Segment
range:
Range
visits:
integer
visitors:
integer
pageviews:
integer 400 401 403 404 Refused. ApiError
POST/api/v1/apps/{app}/funnelsReplace the app's saved funnels.
Operation setFunnels · authentication: deviceToken, session · scope write
The list replaces the saved one whole: read it from getAppSettings or listFunnels, change it, and send back every funnel to keep. An empty list removes them all. Every funnel is checked before any is saved.
Parameters
Request body
application/json
funnels: array of <a href="#schema-FunnelInput">FunnelInput</a> (max items 20)
Responses
200 The settings as saved.
AppSettings
features:
AppFeatures
app:
App
hasKey:
boolean
keyHint:
string | null // The server key's last 4 characters.
webhook:
string | null
turnstile:
boolean // Whether a Turnstile secret is set (the secret itself is never shown).
forms:
object
bug:
array of <a href="#schema-FormField">FormField</a>
feedback:
array of <a href="#schema-FormField">FormField</a>
message:
array of <a href="#schema-FormField">FormField</a>
attachments:
AttachmentRules
retention:
RetentionRules
native:
NativeRules
waitlist:
WaitlistRules
notes:
string | null
segments:
array of <a href="#schema-Segment">Segment</a>
defaultSegment:
string | null
funnels:
array of <a href="#schema-Funnel">Funnel</a>
errors:
ErrorSettings
tracing:
TracingSettings
effective:
object // The rules in force, the deployment's defaults filled in.
attachments:
object
count:
integer
maxFileBytes:
integer
maxTotalBytes:
integer
extraTypes:
array of string
retention:
object
eventDays:
integer
resolvedDays:
integer
spamDays:
integer
attachmentDays:
integer
ticketDays:
integer | null
waitlist:
object
requireConsent:
boolean
consentVersion:
string | null
defaultList:
string
status:
object
lastEvent:
integer | null
today:
object // Counts of the current UTC day.
events:
integer
tickets:
integer
signups:
integer
dropped:
integer // Events over the daily cap.
rateLimited:
integer
overCap:
integer // Requests or signups over a daily cap.
setup:
object // The facts the setup instructions are made from.
prefix:
string
consoleHost:
string
script:
string // The script tag for the app's own host, routed to this server under the prefix.
consoleScript:
string // The script tag loading straight from the console's host.
endpoint:
string // Where back-ends send events with the server key.
domains:
array of object
domain:
string
route:
"live" | "redirect" | "none" | "wildcard" | "development" // Whether https://<domain><prefix>/health reaches this server (probed, cached a minute). 400 401 403 404 Refused. ApiError
GET/api/v1/apps/{app}/importThe app's import from Google Analytics (GA4): the connection, the property and the import's state.
Operation getImport · authentication: deviceToken, session · scope analytics
One import per app at a time. Imported data is kept apart from LogSure's own and counted only for days with no events of the app, so the two never add up for the same day.
Parameters
Responses
200 The import.
ImportStatus
provider:
"ga4"
configured:
boolean // Whether the server has a Google OAuth client.
setup:
string | null // What whoever runs the server must set, when not configured.
redirectUri:
string // The redirect URI to register with the Google OAuth client.
connected:
boolean
connectedBy:
string | null
connectedAt:
integer | null
property:
object | null
id:
string
name:
string
state:
"idle" | "running" | "waiting" | "done" | "failed" | "cancelled" // waiting is a pause for Google's quota, until retryAt.
from:
string | null (date)
to:
string | null (date)
doneThrough:
string | null (date) // The last day fetched.
progress:
number | null // 0 to 1.
days:
integer // Days imported, all imports together.
rows:
integer // Breakdown rows imported, all imports together.
requests:
integer // Reports asked of Google by the current or last import.
error:
string | null
retryAt:
integer | null
startedAt:
integer | null
finishedAt:
integer | null
firstEvent:
string | null (date) // The day of the app's first own event; imports stop the day before.
latest:
string (date) // The last day an import can include. 401 403 404 Refused. ApiError
POST/api/v1/apps/{app}/importImport a GA4 property from a day up to the day before the app's first event (or yesterday), in the background.
Operation startImport · authentication: deviceToken, session · scope write
Days are fetched a month at a time, oldest first, and the import goes on where it stopped after a restart. When Google's quota for the property runs out the state is waiting until retryAt. Days fetched again replace what was imported for them.
Parameters
Request body
application/json
property: string // A property ID from listGoogleProperties, e.g. 123456789.
from: string (date) // The first day to import (UTC).
Responses
202 The import, started.
ImportStatus
provider:
"ga4"
configured:
boolean // Whether the server has a Google OAuth client.
setup:
string | null // What whoever runs the server must set, when not configured.
redirectUri:
string // The redirect URI to register with the Google OAuth client.
connected:
boolean
connectedBy:
string | null
connectedAt:
integer | null
property:
object | null
id:
string
name:
string
state:
"idle" | "running" | "waiting" | "done" | "failed" | "cancelled" // waiting is a pause for Google's quota, until retryAt.
from:
string | null (date)
to:
string | null (date)
doneThrough:
string | null (date) // The last day fetched.
progress:
number | null // 0 to 1.
days:
integer // Days imported, all imports together.
rows:
integer // Breakdown rows imported, all imports together.
requests:
integer // Reports asked of Google by the current or last import.
error:
string | null
retryAt:
integer | null
startedAt:
integer | null
finishedAt:
integer | null
firstEvent:
string | null (date) // The day of the app's first own event; imports stop the day before.
latest:
string (date) // The last day an import can include. 400 409 401 403 404 Refused. ApiError
POST/api/v1/apps/{app}/import/cancelStop a running import; the days fetched so far stay.
Operation cancelImport · authentication: deviceToken, session · scope write
Parameters
Responses
200 The import.
ImportStatus
provider:
"ga4"
configured:
boolean // Whether the server has a Google OAuth client.
setup:
string | null // What whoever runs the server must set, when not configured.
redirectUri:
string // The redirect URI to register with the Google OAuth client.
connected:
boolean
connectedBy:
string | null
connectedAt:
integer | null
property:
object | null
id:
string
name:
string
state:
"idle" | "running" | "waiting" | "done" | "failed" | "cancelled" // waiting is a pause for Google's quota, until retryAt.
from:
string | null (date)
to:
string | null (date)
doneThrough:
string | null (date) // The last day fetched.
progress:
number | null // 0 to 1.
days:
integer // Days imported, all imports together.
rows:
integer // Breakdown rows imported, all imports together.
requests:
integer // Reports asked of Google by the current or last import.
error:
string | null
retryAt:
integer | null
startedAt:
integer | null
finishedAt:
integer | null
firstEvent:
string | null (date) // The day of the app's first own event; imports stop the day before.
latest:
string (date) // The last day an import can include. 400 409 401 403 404 Refused. ApiError
POST/api/v1/apps/{app}/import/deleteDelete the app's imported data; the Google connection stays.
Operation deleteImport · authentication: deviceToken, session · scope write
Parameters
Responses
200 The import.
ImportStatus
provider:
"ga4"
configured:
boolean // Whether the server has a Google OAuth client.
setup:
string | null // What whoever runs the server must set, when not configured.
redirectUri:
string // The redirect URI to register with the Google OAuth client.
connected:
boolean
connectedBy:
string | null
connectedAt:
integer | null
property:
object | null
id:
string
name:
string
state:
"idle" | "running" | "waiting" | "done" | "failed" | "cancelled" // waiting is a pause for Google's quota, until retryAt.
from:
string | null (date)
to:
string | null (date)
doneThrough:
string | null (date) // The last day fetched.
progress:
number | null // 0 to 1.
days:
integer // Days imported, all imports together.
rows:
integer // Breakdown rows imported, all imports together.
requests:
integer // Reports asked of Google by the current or last import.
error:
string | null
retryAt:
integer | null
startedAt:
integer | null
finishedAt:
integer | null
firstEvent:
string | null (date) // The day of the app's first own event; imports stop the day before.
latest:
string (date) // The last day an import can include. 409 401 403 404 Refused. ApiError
POST/api/v1/apps/{app}/import/google/connectStart connecting a Google account: where to send the browser.
Operation connectGoogle · authentication: session · scope write
Needs GOOGLE_CLIENT_ID and GOOGLE_CLIENT_SECRET on the server; without them 409 not_configured, and getImport's setup says what to set. Asks Google for read-only access to Analytics.
Parameters
Responses
200 Where to go.
url: string // Google's consent page; it comes back to googleCallback, then to the app's settings in the console.
409 401 403 404 Refused. ApiError
POST/api/v1/apps/{app}/import/google/disconnectForget the Google connection and revoke it at Google; imported data stays.
Operation disconnectGoogle · authentication: deviceToken, session · scope write
Parameters
Responses
200 The import.
ImportStatus
provider:
"ga4"
configured:
boolean // Whether the server has a Google OAuth client.
setup:
string | null // What whoever runs the server must set, when not configured.
redirectUri:
string // The redirect URI to register with the Google OAuth client.
connected:
boolean
connectedBy:
string | null
connectedAt:
integer | null
property:
object | null
id:
string
name:
string
state:
"idle" | "running" | "waiting" | "done" | "failed" | "cancelled" // waiting is a pause for Google's quota, until retryAt.
from:
string | null (date)
to:
string | null (date)
doneThrough:
string | null (date) // The last day fetched.
progress:
number | null // 0 to 1.
days:
integer // Days imported, all imports together.
rows:
integer // Breakdown rows imported, all imports together.
requests:
integer // Reports asked of Google by the current or last import.
error:
string | null
retryAt:
integer | null
startedAt:
integer | null
finishedAt:
integer | null
firstEvent:
string | null (date) // The day of the app's first own event; imports stop the day before.
latest:
string (date) // The last day an import can include. 409 401 403 404 Refused. ApiError
GET/api/v1/apps/{app}/import/google/propertiesThe GA4 properties the connected Google account can read.
Operation listGoogleProperties · authentication: deviceToken, session · scope analytics
Parameters
Responses
200 The properties.
properties: array of <a href="#schema-GoogleProperty">GoogleProperty</a>
409 502 401 403 404 Refused. ApiError
GET/api/v1/import/google/callbackWhere Google sends the browser back after consent; stores the connection and goes on to the console.
Operation googleCallback · authentication: none
Register https://<CONSOLE_HOST>/api/v1/import/google/callback as the OAuth client's redirect URI (getImport's redirectUri). The state is signed by the server and works for 10 minutes. The answer is always a redirect to the app's settings in the console, with ?import=connected, or ?import=error&reason=….
Parameters
| Name | In | Type | |
|---|
code optional | query | string | |
state optional | query | string | |
error optional | query | string | |
Responses
303 On to the console.
POST/api/v1/apps/{app}/keyA new server key, replacing the old one at once. Shown in this answer only; the web console, signed in, only.
Operation rotateServerKey · authentication: session · scope write
Parameters
Responses
200 The key, this once.
key: string (pattern ^cc_)
keyHint: string // Its last 4 characters, as the settings show it from now on.
401 403 404 Refused. ApiError
POST/api/v1/apps/{app}/deleteDelete an app. It leaves the console and stops taking data at once; its data goes in the background. The web console, signed in, only.
Operation deleteApp · authentication: session · scope write
Parameters
Responses
200 Done.
401 403 404 Refused. ApiError
POST/api/v1/apps/{app}/iconFetch the app's icon from its site again.
Operation refreshAppIcon · authentication: deviceToken, session · scope write
Parameters
Responses
200 The app with its icon address.
401 403 404 Refused. ApiError
GET/api/v1/icons/{app}The app's icon image.
Operation getAppIcon · authentication: deviceToken, session · scope analytics
Parameters
| Name | In | Type | |
|---|
app | path | string | |
v optional | query | string | The icon's version, for caching. |
Responses
200 The image.
string (binary)
401 403 404 Refused. ApiError
GET/api/v1/notificationsThe bell, newest requests and signups first.
Operation getNotifications · authentication: deviceToken, session · scope inbox
Parameters
| Name | In | Type | |
|---|
include optional | query | string | errors, to have new and returning errors in the bell (clients that know the error item). |
Responses
200 The bell.
Notifications
count:
integer // Items newer than seenAt.
seenAt:
integer | null
items:
array of <a href="#schema-NotificationItem">NotificationItem</a> 401 403 Refused. ApiError
POST/api/v1/notifications/readMark everything in the bell as seen.
Operation markNotificationsRead · authentication: deviceToken, session · scope write
Responses
200 Done.
401 403 Refused. ApiError
POST/api/v1/privacy/findEverything one email address sent. The address travels in the body, never the URL.
Operation findByEmail · authentication: deviceToken, session · scope inbox
Request body
application/json
Responses
200 The requests and signups.
email: string
tickets: array of <a href="#schema-TicketSummary">TicketSummary</a>
signups: array of <a href="#schema-Signup">Signup</a>
400 401 403 Refused. ApiError
POST/api/v1/privacy/deleteDelete everything one email address sent.
Operation deleteByEmail · authentication: deviceToken, session · scope write
Request body
application/json
Responses
200 How many were deleted.
tickets: integer
signups: integer
400 401 403 Refused. ApiError
GET/api/v1/profileThe owner's display name and photo.
Operation getProfile · authentication: deviceToken, session · scope analytics
Responses
200 The profile.
name: string | null
photoAt: integer | null // When the photo was set; also its cache key.
user: string | null
401 403 Refused. ApiError
POST/api/v1/profileChange the display name, or set or remove the photo.
Operation updateProfile · authentication: deviceToken, session · scope write
Request body
application/json
name?: string
photo?: string // A PNG or JPEG as a data URL.
removePhoto?: boolean
Responses
200 The profile as saved.
Profile
name:
string | null
photoAt:
integer | null // When the photo was set; also its cache key. 400 401 403 Refused. ApiError
GET/api/v1/profile/photoThe owner's photo.
Operation getProfilePhoto · authentication: deviceToken, session · scope analytics
Responses
200 The image.
string (binary)
401 403 404 Refused. ApiError
GET/api/v1/aiThe MCP server's switch, its address, and the AI clients and API keys connected. The web console, signed in, only.
Operation getAiAccess · authentication: session · scope analytics
Responses
200 AI access.
enabled: boolean
live: boolean // On, and two-factor sign-in on; only then does the MCP server answer.
twoFactor: boolean
url: string // The MCP server's address, to paste into an AI client.
connections: array of <a href="#schema-AiConnection">AiConnection</a>
401 403 Refused. ApiError
POST/api/v1/ai/onTurn the MCP server on. Needs two-factor sign-in on.
Operation turnAiOn · authentication: session · scope write
Responses
200 Done.
401 403 Refused. ApiError
POST/api/v1/ai/offTurn the MCP server off; connections stay, and work again when it is turned on.
Operation turnAiOff · authentication: session · scope write
Responses
200 Done.
401 403 Refused. ApiError
POST/api/v1/ai/keysA personal API key for AI clients that cannot sign in (sent as Authorization: Bearer ccm_… to /mcp). Shown this once. Needs two-factor sign-in on.
Operation createApiKey · authentication: session · scope write
Request body
application/json
name?: string (max length 60)
inbox?: boolean // Also requests and signups, with email addresses.
write?: boolean // Also changing statuses and adding notes (with inbox).
settings?: boolean // Also changing apps' rules, form fields, support notes and segments. Off unless asked for.
Responses
201 The key, this once.
id: string
name: string
token: string (pattern ^ccm_)
scope: array of "analytics" | "inbox" | "write" | "settings"
live: boolean // false while the MCP server is off (the key works once it is on).
400 401 403 Refused. ApiError
POST/api/v1/ai/grants/{id}/revokeEnd a connection or an API key; its next request gets 401.
Operation revokeAiConnection · authentication: session · scope write
Parameters
Responses
200 Done.
401 403 404 Refused. ApiError
GET/api/v1/oauth/requestWhat an AI client asks for, for the web console's consent page at /oauth/authorize, which passes its own query on unchanged.
Operation describeAuthorization · authentication: session · scope analytics
The authorization endpoint the OAuth metadata names, /oauth/authorize, is a page of the web console. It reads its query, calls this with the same parameters, and asks the signed-in owner. A 400 means the client or its return address is not one registered here: show the message and never redirect. When redirect is set the request itself is wrong (no PKCE, another response type): send the browser there at once. Otherwise show the client's name, where the browser goes back to (returnTo) and the access to choose (analytics always; inbox and write as checkboxes, checked when scope asks for them, and settings as a checkbox left unticked unless scope asks for it), then call authorizeClient.
Parameters
| Name | In | Type | |
|---|
client_id optional | query | string | |
redirect_uri optional | query | string | |
response_type optional | query | string | |
state optional | query | string | |
code_challenge optional | query | string | |
code_challenge_method optional | query | string | |
scope optional | query | string | |
resource optional | query | string | |
Responses
200 The request.
client: object
id: string
name: string
returnTo: string // The host (or the app's own scheme) the browser goes back to.
scope: array of string
redirect: string | null
400 401 403 Refused. ApiError
POST/api/v1/oauth/authorizeAllow or deny an AI client; the answer is where to send the browser (back to the client, with a code or an error).
Operation authorizeClient · authentication: session · scope write
Send the authorization request's parameters as they came, plus the decision and the access chosen. The consent page then navigates to redirect itself (location.assign), so no form posts to the client's address and the console's CSP needs no form-action for it.
Request body
application/json
client_id: string
redirect_uri?: string
response_type?: string
state?: string
code_challenge?: string
code_challenge_method?: string
scope?: string
resource?: string
decision: "allow" | "deny"
inbox?: boolean
write?: boolean
settings?: boolean // Changing apps' rules, form fields, support notes and segments; offered unticked.
Responses
200 Where to send the browser.
redirect: string
400 401 403 Refused. ApiError
POST/oauth/registerRFC 7591 dynamic registration of a public AI client (no secret). Only while the MCP server is on.
Operation registerClient · authentication: none
Request body
application/json
client_name?: string
redirect_uris: array of string (min items 1, max items 5)
Responses
201 The client.
client_id: string
client_id_issued_at?: integer
client_name: string
redirect_uris: array of string
grant_types: array of string
response_types: array of string
token_endpoint_auth_method: string
400 Refused. OAuthError
404 The MCP server is off. object
POST/oauth/tokenAn authorization code with its PKCE verifier, or a refresh token, for an access token (1 hour) and a new refresh token (60 days; the old one stops working).
Operation getToken · authentication: none
Request body
application/x-www-form-urlencoded
TokenRequest
grant_type:
"authorization_code" | "refresh_token"
code?:
string
code_verifier?:
string
redirect_uri?:
string
client_id?:
string
refresh_token?:
string application/json
TokenRequest
grant_type:
"authorization_code" | "refresh_token"
code?:
string
code_verifier?:
string
redirect_uri?:
string
client_id?:
string
refresh_token?:
string Responses
200 The tokens.
access_token: string (pattern ^ccm_)
token_type: "Bearer"
expires_in: integer
refresh_token: string (pattern ^ccm_)
scope: string
400 Refused (invalid_grant, unsupported_grant_type, invalid_request). OAuthError
404 The MCP server is off. object
GET/.well-known/oauth-authorization-serverRFC 8414 metadata (also at /.well-known/openid-configuration). Only while the MCP server is on.
Operation getAuthorizationServer · authentication: none
Responses
200 The OAuth endpoints.
issuer: string
authorization_endpoint: string // A web console page (see describeAuthorization).
token_endpoint: string
registration_endpoint: string
code_challenge_methods_supported: array of string
scopes_supported?: array of string
404 The MCP server is off. object
GET/.well-known/oauth-protected-resourceRFC 9728 metadata of the MCP server (also at /.well-known/oauth-protected-resource/mcp).
Operation getProtectedResource · authentication: none
Responses
200 Which authorization server signs clients in.
resource: string
authorization_servers: array of string
scopes_supported?: array of string
404 The MCP server is off. object
POST/mcpThe MCP server (Streamable HTTP, JSON answers, no sessions). Its methods and tools are described by MCP itself (initialize, tools/list).
Operation callMcp · authentication: mcpToken
JSON-RPC 2.0, one message or a batch of up to 50. Tools by scope: analytics — list_apps, get_overview, get_app_stats, get_breakdown, get_event, get_waitlist_growth, get_app_settings (getAppSettings without the server key hint and the webhook address), preview_segment; inbox — list_requests, get_request, list_waitlist, get_notifications; write — set_request_status, add_request_note, set_signup_status, archive_resolved_requests; settings — set_app_rules (setAppRules: each rule named is replaced whole, so read, change and send it back), set_segments (setSegments). Settings are never implied by another scope, and nothing reaches sign-in, two-factor, devices, API keys, AI access, privacy requests, creating or deleting apps or server keys. Without a valid token the answer is 401 with a WWW-Authenticate: Bearer resource_metadata="…" header pointing at the protected-resource metadata.
Request body
application/json
object | array
Responses
200 The JSON-RPC answer (or answers).
object | array
202 Only notifications were sent.
400 Not JSON. object
401 No valid token. object
404 The MCP server is off. object
429 Too many failed tokens from this address. object
GET/api/v1/devicesThe connected devices.
Operation listDevices · authentication: deviceToken, session · scope analytics
Responses
200 The devices.
devices: array of <a href="#schema-Device">Device</a>
401 403 Refused. ApiError
POST/api/v1/devicesConnect a device (the iOS or terminal app). Needs two-factor sign-in on, since a device skips the password and the code; the web console, signed in, only.
Operation createDevice · authentication: session · scope write
Request body
application/json
name?: string (max length 60) // Shown in the list of devices (default iPhone).
Responses
201 The device, and its pairing link with the token in it, shown this once.
device:
Device
link:
string (pattern ^logsure://) // logsure://<console host>/<device token>: the app reads it from the QR code, or it is pasted.
qrSvg:
string | null // The link as a QR code, an SVG to show as it is. 400 401 403 Refused. ApiError
GET/api/v1/securityTwo-factor sign-in and the sign-in details, as the security settings show them. The web console, signed in, only.
Operation getSecurity · authentication: session · scope analytics
Responses
200 The state.
passkeys: integer // How many passkeys the user has.
twoFactor: object
enabled: boolean
stale: boolean // A secret is stored but the deployment's root password changed since, so it cannot be read and counts as off.
since: integer | null
recoveryLeft: integer
setupPending: boolean // This browser is in the middle of setting it up (its setup cookie is still valid).
resetAtStart: boolean // TOTP_RESET is set, so it was turned off at this start and will be again at every start.
signIn: object
username: string | null // null means any username signs in.
ownPassword: boolean // A password set in the console replaces the deployment's root password.
changedAt: integer | null
resetAtStart: boolean // PASSWORD_RESET is set.
issuer: string
account: string // The name authenticator apps show under the issuer (the console's host).
401 403 Refused. ApiError
POST/api/v1/security/two-factor/setupStart (or continue) setting up two-factor sign-in. The secret travels in this answer and in a sealed 10-minute cookie, never in a URL.
Operation setUpTwoFactor · authentication: session · scope write
Responses
200 The secret to add to an authenticator app.
secret:
string (pattern ^[A-Z2-7]{32}$) // Base32, for typing in; shown in groups of four.
otpauthUrl:
string (pattern ^otpauth://totp/)
qrSvg:
string | null // The otpauth URL as a QR code, an SVG to show as it is.
account:
string
issuer:
string
digits:
integer
period:
integer
expiresAt:
Time 401 403 409 503 Refused. ApiError
POST/api/v1/security/two-factor/enableThe first code proves the app has the secret; two-factor sign-in goes on. Wrong codes count toward the sign-in lockouts.
Operation enableTwoFactor · authentication: session · scope write
Request body
application/json
code: string // The 6-digit code from the authenticator app (or, where it says so, a recovery code).
Responses
200 The recovery codes, shown this once; only their hashes are kept.
recoveryCodes: array of string <span class="d">(pattern ^[a-z0-9]{5}-[a-z0-9]{5}$)</span> (min items 10, max items 10)
400 A wrong code (invalid, field code), or the setup took longer than 10 minutes (expired). ApiError
401 403 409 429 Refused. ApiError
POST/api/v1/security/two-factor/recovery-codesTen new recovery codes; the old ones stop working. Takes a current code or a recovery code.
Operation newRecoveryCodes · authentication: session · scope write
Request body
application/json
code: string // The 6-digit code from the authenticator app (or, where it says so, a recovery code).
Responses
200 The recovery codes, shown this once; only their hashes are kept.
recoveryCodes: array of string <span class="d">(pattern ^[a-z0-9]{5}-[a-z0-9]{5}$)</span> (min items 10, max items 10)
400 401 403 409 429 Refused. ApiError
POST/api/v1/security/two-factor/disableTurn two-factor sign-in off. Takes a current code or a recovery code.
Operation disableTwoFactor · authentication: session · scope write
Request body
application/json
code: string // The 6-digit code from the authenticator app (or, where it says so, a recovery code).
Responses
200 Done.
400 401 403 429 Refused. ApiError
POST/api/v1/security/two-factor/cancelStop setting up; nothing changes.
Operation cancelTwoFactorSetup · authentication: session · scope write
Responses
200 Done.
401 403 Refused. ApiError
GET/api/v1/security/passkeysThe signed-in user's passkeys.
Operation listPasskeys · authentication: session · scope analytics
Responses
200 Newest first.
passkeys: array of <a href="#schema-Passkey">Passkey</a>
401 403 Refused. ApiError
POST/api/v1/security/passkeysStore a new passkey from navigator.credentials.create(), made with createPasskeyOptions' challenge.
Operation addPasskey · authentication: session · scope write
Request body
application/json
credential:
PasskeyAttestation
name:
string (max length 60) // What the person calls it, e.g. MacBook or iPhone. Responses
201 The passkey.
Passkey
id:
string
name:
string
provider:
string | null // Where it is kept, when known from its AAGUID (iCloud Keychain, Google Password Manager, …).
aaguid:
string | null
backedUp:
boolean // Synced by its provider (it survives losing the device).
createdAt:
Time
lastUsedAt:
integer | null 400 401 403 409 Refused. ApiError
POST/api/v1/security/passkeys/optionsThe request to make a passkey, for navigator.credentials.create(); needs the current password (and code).
Operation createPasskeyOptions · authentication: session · scope write
Like changing the sign-in details, it takes the current password and, with two-factor on, a current 6-digit code; wrong ones count toward the lockouts. The challenge works once, for 5 minutes, for this user only. The passkey is discoverable (residentKey required), asks for user verification when it can, and excludes the user's existing passkeys.
Request body
application/json
password: string
code?: string // A current 6-digit code, when two-factor is on.
Responses
200 The options.
400 401 403 429 Refused. ApiError
POST/api/v1/security/passkeys/{passkey}Rename a passkey.
Operation renamePasskey · authentication: session · scope write
Parameters
| Name | In | Type | |
|---|
passkey | path | string | |
Request body
application/json
name: string (max length 60)
Responses
200 The passkey.
Passkey
id:
string
name:
string
provider:
string | null // Where it is kept, when known from its AAGUID (iCloud Keychain, Google Password Manager, …).
aaguid:
string | null
backedUp:
boolean // Synced by its provider (it survives losing the device).
createdAt:
Time
lastUsedAt:
integer | null 400 401 403 404 Refused. ApiError
POST/api/v1/security/passkeys/{passkey}/deleteRemove a passkey; it no longer signs in.
Operation deletePasskey · authentication: session · scope write
Parameters
| Name | In | Type | |
|---|
passkey | path | string | |
Responses
200 Done.
401 403 404 Refused. ApiError
POST/api/v1/security/sign-inSet a username, a password of your own, or both. Only with two-factor sign-in on, with the current password and a current 6-digit code. Every other browser is signed out; this one gets a new session cookie.
Operation changeSignIn · authentication: session · scope write
Request body
application/json
username?: string (pattern ^[a-z0-9._@-]{3,64}$) // Compared in any case and stored lower-case.
current: string
next?: string // A new password, at least 16 characters; leave out to keep the password.
confirm?: string // The new password again.
code: string
Responses
200 Saved; the session cookie is set again.
400 Refused, with the field it is about (username, current, next, confirm or code). ApiError
401 403 429 503 Refused. ApiError
POST/api/v1/devices/{id}/deleteDisconnect a device; its next request gets 401.
Operation removeDevice · authentication: deviceToken, session · scope write
Parameters
Responses
200 Done.
401 403 404 Refused. ApiError
POST/api/v1/devices/{id}/renameRename a connected device.
Operation renameDevice · authentication: deviceToken, session · scope write
Parameters
Request body
application/json
name: string (min length 1, max length 60) // Trimmed; one line, without control characters.
Responses
200 The device as saved.
400 The name is empty, too long or holds control characters (field name). ApiError
401 403 404 Refused. ApiError
Team
Users, roles, invites and the audit log; owners and admins only.
GET/api/v1/usersEveryone who can sign in, and the team's rules.
Operation listUsers · authentication: deviceToken, session · scope analytics
Responses
200 The users, the owner first.
users: array of <a href="#schema-User">User</a>
requireTwoFactor: boolean
401 403 Refused. ApiError
POST/api/v1/users/{user}Change a user's role or apps, or sign them out everywhere.
Operation updateUser · authentication: deviceToken, session · scope write
An admin may not change an owner or make anyone an owner, and nobody changes their own role. Signing out everywhere ends the user's sessions and deletes their devices and API keys and AI connections.
Parameters
Request body
application/json
role?:
Role
apps?:
array | null (max items 100) // The apps the user may see; null or empty for every app. Ignored for owners and admins.
signOut?:
boolean Responses
200 The user as saved.
User
id:
string
username:
string | null // null for an owner who signs in with the root password and has not set a username.
name:
string | null
role:
Role
apps:
array | null // The apps the user is limited to; null for every app.
twoFactor:
boolean
createdAt:
Time
lastSeenAt:
integer | null 400 401 403 404 Refused. ApiError
POST/api/v1/users/{user}/deleteRemove a user and everything they hold (sessions, devices, API keys, AI connections).
Operation deleteUser · authentication: deviceToken, session · scope write
The owner cannot be removed; an admin cannot remove another admin's owner, nor themselves.
Parameters
Responses
200 Done.
401 403 404 Refused. ApiError
GET/api/v1/invitesThe invites not used yet and not expired.
Operation listInvites · authentication: deviceToken, session · scope analytics
Responses
200 Newest first.
invites: array of <a href="#schema-Invite">Invite</a>
401 403 Refused. ApiError
POST/api/v1/invitesA one-time link that lets one person join with a role.
Operation createInvite · authentication: deviceToken, session · scope write
The link is shown in this answer only; send it any way you like. It works once, until it expires. Admins may invite admins, members and viewers; only the owner's role is never given by invite.
Request body
application/json
role: "admin" | "member" | "viewer"
apps?: array | null (max items 100) // Limits a member or viewer to these apps; null or empty for every app.
hours?: integer (min 1, max 168, default 48) // How long the link works.
Responses
201 The invite and its link, this once.
invite:
Invite
link:
string // https://<console>/join#<code>; the code is after the #, so it never reaches a server log. 400 401 403 Refused. ApiError
POST/api/v1/invites/{invite}/revokeAn invite stops working.
Operation revokeInvite · authentication: deviceToken, session · scope write
Parameters
| Name | In | Type | |
|---|
invite | path | string | |
Responses
200 Done.
401 403 404 Refused. ApiError
POST/api/v1/teamThe team's rules.
Operation setTeam · authentication: deviceToken, session · scope write
With requireTwoFactor, everyone without two-factor sign-in can only set it up (403 two_factor_required elsewhere) until they do. Turning it on needs two-factor on for the one asking.
Request body
application/json
requireTwoFactor: boolean
Responses
200 The rules as saved.
requireTwoFactor: boolean
401 403 Refused. ApiError
GET/api/v1/auditWhat was done and by whom, newest first; kept for a year.
Operation listAudit · authentication: deviceToken, session · scope analytics
Parameters
| Name | In | Type | |
|---|
before optional | query | integer | The next of the previous page. |
limit optional | query | integer (min 1, max 500) | |
Responses
200 A page of entries.
entries: array of <a href="#schema-AuditEntry">AuditEntry</a>
next: integer | null
401 403 Refused. ApiError
GET/api/v1/joinWhat an invite is for, before joining. No sign-in needed.
Operation getInvite · authentication: none
Parameters
| Name | In | Type | |
|---|
code | query | string | |
Responses
200 The invite.
role:
Role
apps:
array | null
expiresAt:
Time
requireTwoFactor:
boolean 404 Refused. ApiError
POST/api/v1/joinJoin with an invite; signed in at once.
Operation join · authentication: none
Picks a username and a password; the invite stops working. The answer sets the session cookie. When the team requires two-factor sign-in, the new user sets it up next (getMe says so with twoFactorRequired).
Request body
application/json
code: string
username: string // 3 to 64 letters, digits or . _ - @, unused.
password: string
confirm: string
name?: string (max length 60) // The display name.
Responses
200 Joined and signed in.
signedIn: true
twoFactorRequired: boolean
400 404 409 429 Refused. ApiError
Schemas
Ok
ok: true
Time
Milliseconds since the Unix epoch.
integer
IngestError
error:
string // For people, in English. Never parse it.
code:
"required" | "too_short" | "too_long" | "one_line" | "invalid_email" | "invalid_url" | "invalid_type" | "out_of_range" | "consent_required" | "personal_data" | "unknown_field" | "bad_json" | "too_large" | "unsupported_media" | "unknown_site" | "wrong_app" | "rate_limited" | "daily_cap" | "busy" | "server_error" | "method_not_allowed" | "not_found" | "forbidden" | "unauthorized" | "not_implemented" | "bad_protobuf" | "bad_gzip" | "feature_disabled"
field?:
string | null // The body field the error is about, e.g. email, props.plan, events[2].name.
feature?:
FeatureKey // With feature_disabled, the feature that is off. ApiError
error:
string
code:
"unauthorized" | "rate_limited" | "not_found" | "invalid" | "conflict" | "method" | "forbidden" | "forbidden_role" | "two_factor_required" | "two_factor_required_for_device" | "passkey_required" | "not_configured" | "expired" | "server_error" | "feature_disabled"
field?:
string // The body field the refusal is about, when there is one (e.g. code, next).
need?:
Role
feature?:
FeatureKey // With feature_disabled (403), the feature that is off for this app or the instance. Event
name:
string (min length 1, max length 64) // e.g. pageview or signup. A pageview needs url.
app?:
string // The app's ID, when the Origin is shared by several apps or the call comes from a back-end.
url?:
string (max length 2048) // An absolute http(s) URL or a path. Only the host, the path and utm_* are kept.
hash?:
boolean // Keep the URL's fragment as part of the path (hash-routed apps).
referrer?:
string (max length 2048)
props?:
object (max keys 20) // Strings up to 256 characters; no email addresses, IP addresses or User-Agents.
value?:
number
width?:
integer (min 0, max 10000)
lang?:
string (max length 35)
version?:
string (max length 32)
ip?:
string // Server key only - the visitor's address, used in memory for the daily visitor code.
userAgent?:
string // Server key only.
country?:
string (pattern ^[A-Z]{2}$) // Server key only.
ts?:
Time // Server key only - within the last 15 minutes of the current UTC day. EventWithoutName
app?:
string // The app's ID, when the Origin is shared by several apps or the call comes from a back-end.
url?:
string (max length 2048) // An absolute http(s) URL or a path. Only the host, the path and utm_* are kept.
hash?:
boolean // Keep the URL's fragment as part of the path (hash-routed apps).
referrer?:
string (max length 2048)
props?:
object (max keys 20) // Strings up to 256 characters; no email addresses, IP addresses or User-Agents.
value?:
number
width?:
integer (min 0, max 10000)
lang?:
string (max length 35)
version?:
string (max length 32)
ip?:
string // Server key only - the visitor's address, used in memory for the daily visitor code.
userAgent?:
string // Server key only.
country?:
string (pattern ^[A-Z]{2}$) // Server key only.
ts?:
Time // Server key only - within the last 15 minutes of the current UTC day. Feedback
app?: string
email?: string (max length 254, email)
name?: string (max length 100)
url?: string // The page it was sent from (http or https).
props?: object
fields?: object // The app's custom fields (see getFormDefinition).
website?: string // A honeypot - leave empty. A filled one gets the success answer and nothing is stored.
token?: string // The Cloudflare Turnstile response, when the app requires it.
userAgent?: string // Server key only.
country?: string // Server key only.
message: string (min length 1, max length 5000)
rating?: integer (min 1, max 5)
category?: string (max length 32)
BugReport
app?: string
email?: string (max length 254, email)
name?: string (max length 100)
url?: string // The page it was sent from (http or https).
props?: object
fields?: object // The app's custom fields (see getFormDefinition).
website?: string // A honeypot - leave empty. A filled one gets the success answer and nothing is stored.
token?: string // The Cloudflare Turnstile response, when the app requires it.
userAgent?: string // Server key only.
country?: string // Server key only.
title: string (min length 3, max length 150)
description: string (min length 1, max length 10000)
steps?: string (max length 5000)
expected?: string (max length 5000)
version?: string (max length 32)
attachments?: array of object (max items 3)
name?: string (max length 100)
type?: string // Ignored - the type is read from the bytes.
data: string // Base64 or a data URL. PNG, JPEG, GIF, WEBP, UTF-8 text or JSON; 5 MB each, 8 MB together (apps may allow more).
Message
app?: string
email: string (max length 254, email)
name?: string (max length 100)
url?: string // The page it was sent from (http or https).
props?: object
fields?: object // The app's custom fields (see getFormDefinition).
website?: string // A honeypot - leave empty. A filled one gets the success answer and nothing is stored.
token?: string // The Cloudflare Turnstile response, when the app requires it.
userAgent?: string // Server key only.
country?: string // Server key only.
message: string (min length 1, max length 10000)
subject?: string (max length 150)
WaitlistSignup
app?: string
email: string (max length 254, email)
name?: string (max length 100)
list?: string (pattern ^[a-z0-9][a-z0-9-]{0,31}$, default "default")
consent?: boolean // Required when the app's rules ask for consent.
props?: object
referrer?: string
url?: string
website?: string
token?: string
country?: string // Server key only.
lang?: string // Server key only.
WaitlistImport
app?:
string
entries:
array of object (max items 1000)
email:
string
name?:
string
list?:
string
status?:
SignupStatus
lang?:
string
created_at?:
string | integer // When they signed up, as an ISO time or milliseconds since the epoch. Default now.
consent?:
object // The consent they gave, when the app records it.
version:
string
at:
string | integer WaitlistImportResult
imported: integer
skipped: integer
errors: array of object
index: integer
error: string
code: string
field?: string | null
app:
string
kind:
TicketKind
fields:
array of object
key:
string
label:
string
type:
"text" | "textarea" | "number" | "select" | "checkbox" | "email" | "url" | "date" | "json"
required?:
boolean
options?:
array of string
max?:
integer
help?:
string
attachments?:
object // Bug reports only.
count?:
integer
maxFileBytes?:
integer
maxTotalBytes?:
integer
types?:
array of string PrivacyFacts
Each section only when its feature is on (and, for tracing, when the app's servers send any).
errors?: object
endpoints: array of string
personalData: false
captured: array of string // The kinds of errors reported, from the app's error settings.
note?: string // Off an app's address, that each site chooses the kinds its pages report.
kept: array of string
scrubbed: string
retentionDays: integer
tracing?: object
endpoints: array of string
source: string
traces: boolean // The app's servers have sent traces.
logs: boolean
sent: array of string
scrubbed: string
retentionDays: object
traces: integer
logs: integer
analytics?: object
endpoints: array of string
personalData: false
cookies: false
deviceStorage: false
received?: array of string
used?: array of string
visitorCode?: string
neverStored?: array of string
retentionDays: integer
customerCare?: object
endpoints: array of string
personalData?: string
note?: string
retentionDays: object
erasure?: string
WindowEnd
A day (YYYY-MM-DD) or a minute (YYYY-MM-DDTHH:MM), UTC.
string (pattern ^[0-9]{4}-[0-9]{2}-[0-9]{2}(T[0-9]{2}:[0-9]{2})?$)
RangeKey
"30m" | "1h" | "today" | "24h" | "7d" | "30d" | "90d" | "12mo" | "all"
Range
key:
string // A RangeKey, or custom.
label:
string
from:
Time
to:
Time
bucket:
"minute" | "hour" | "day" | "month" Dimension
"path" | "entry" | "exit" | "source" | "utm_source" | "utm_medium" | "utm_campaign" | "country" | "device" | "browser" | "os" | "lang" | "host"
Row
value: string | null // null means no value (e.g. direct traffic).
visitors: integer
count: integer
Totals
importedDays?: integer // Days of the window counted from imported data (present only when there are some).
visitors: integer
visits: integer
pageviews: integer
events: integer
viewsPerVisit: number | null
bounceRate: number | null // 0 to 1.
duration: number | null // Average visit length in milliseconds.
Summary
importedDays?:
integer // Days of the window counted from imported data (present only when there are some).
visitors:
integer
visits:
integer
pageviews:
integer
events:
integer
viewsPerVisit:
number | null
bounceRate:
number | null // 0 to 1.
duration:
number | null // Average visit length in milliseconds.
prev:
Totals // The previous period of the same length; null for all or before the app's first event. SeriesPoint
t:
Time
visitors:
integer
pageviews:
integer Goal
name: string
visitors: integer
count: integer
value: number | null // The sum of the events' values.
conversion: number | null // Visitors who sent it over all visitors, 0 to 1.
Ratings
count: integer
average: number | null
distribution: array of integer (min items 5, max items 5) // How many 1, 2, 3, 4 and 5 star ratings.
Overview
range:
Range
apps:
array of object
appId:
string
imported:
ImportedWindow
visitors:
integer
pageviews:
integer
prev:
object | null
visitors:
integer
pageviews:
integer
spark:
array of integer // Visitors per day over the last 30 days.
realtime:
integer // Visitors in the last 5 minutes.
series:
array of integer // Visitors per bucket of the window. Dashboard
imported:
ImportedWindow // The window's imported days, when it has some.
app:
App
segment:
string
filters:
object
range:
Range
summary:
Summary
series:
array of <a href="#schema-SeriesPoint">SeriesPoint</a>
pages:
array of <a href="#schema-Row">Row</a>
sources:
array of <a href="#schema-Row">Row</a>
countries:
array of <a href="#schema-Row">Row</a>
devices:
array of <a href="#schema-Row">Row</a>
languages:
array of <a href="#schema-Row">Row</a>
sites:
array of <a href="#schema-Row">Row</a> // By host, when the app has more than one domain.
live:
object
visitors:
integer
pages:
array of object
value:
string | null
visitors:
integer
events:
array of <a href="#schema-Goal">Goal</a>
chartEvents:
array of object (max items 3)
name:
string
series:
array of object
t:
Time
count:
integer
ratings:
Ratings
recent:
array of <a href="#schema-TicketSummary">TicketSummary</a> // The latest feedback (contains personal data). EventDetail
name:
string
range:
Range
series:
array of object
t:
Time
count:
integer
visitors:
integer
props:
array of object
key:
string
value:
string | number | boolean | null
visitors:
integer
count:
integer App
id: string
name: string
domains: array of string
icon: string | null // The icon's address on this API, with its version.
segments: array of object
id: string
label: string
defaultSegment: string | null
key: string (pattern ^[a-z][a-z0-9_]{0,31}$)
label: string (max length 80)
type: "text" | "textarea" | "number" | "select" | "checkbox" | "email" | "url" | "date" | "json"
required?: boolean
options?: array of string // The choices of a select.
min?: number
max?: number // Characters (text, textarea, url), the largest number (number), or the most items (json).
maxBytes?: integer // The largest JSON value (json).
help?: string (max length 200)
AttachmentRules
Bigger files on bug reports, and file types beyond the built-in ones.
extraTypes?: array of string <span class="d">(pattern ^[a-z0-9]{1,16}$)</span> | <a href="#schema-ExtraType">ExtraType</a> (max items 10) // File types taken besides the built-in ones, kept and always served as downloads. A bare extension ("log") takes any file with that name. An object also says how the bytes look: with magic, a file starting with those characters is taken whatever its name; with json, a JSON document ({ first) is taken when its name has the extension or its declared type holds one of declared. Then the extension alone is not enough, and the extension is added to the name when it is missing. The app's own file and total limits apply to every type; maxMB holds one type to less.
maxFileMB?: integer (min 1, max 28)
maxTotalMB?: integer (min 1, max 28)
ext: string (pattern ^[a-z0-9]{1,16}$)
type?: string // The media type kept with the file (default application/octet-stream).
magic?: string (min length 1, max length 16) // The printable characters such files start with.
json?: boolean // The type also has a JSON form.
declared?: array of string (max items 5) // Words that, in the declared type, mark a JSON document as this type.
maxMB?: integer (min 1, max 28) // A size limit for this type, at most the app's own file limit.
RetentionRules
The app's own promises, replacing the deployment's periods for its data.
attachmentDays?: integer (min 1, max 3650)
ticketDays?: integer (min 1, max 3650) // Every request is deleted this long after it was sent, open or not.
NativeRules
Requests from clients without a browser (no Origin or Referer, no server key), e.g. a desktop app.
kinds?: array of <a href="#schema-TicketKind">TicketKind</a>
textOnly?: boolean // No attachments from such clients (default true).
dailyCap?: integer (min 1, max 2000)
titlePrefix?: string (max length 40)
WaitlistRules
requireConsent?: boolean
consentVersion?: string (pattern ^[A-Za-z0-9][A-Za-z0-9._-]{0,31}$)
defaultList?: string (pattern ^[a-z0-9][a-z0-9-]{0,31}$)
SegmentRule
field: string // host, path, country, device, browser, os, lang, source, utm_campaign, or prop:<key>.
op: "eq" | "in" | "prefix"
value: string | array of string (min items 1, max items 20) // One value (eq, prefix) or 1 to 20 (in); (none) means no value.
id?: string // Made from the label when left out.
label: string (max length 40)
rules: array of <a href="#schema-SegmentRule">SegmentRule</a> (min items 1, max items 8)
Segment
id: string
label: string
rules: array of <a href="#schema-SegmentRule">SegmentRule</a>
FunnelStep
A page view or a custom event. A page step matches a pageview's path: exactly, or with * standing for any characters, / included (/blog/* is every page under /blog/, /docs/*/install any docs section's install page, * any page). An event step matches a custom event by its name and, with prop, only when that property's value, as text (true, false and numbers as written), equals value.
type: "page" | "event"
path?: string (max length 512) // For a page step, starting with / (or *).
name?: string (max length 64) // For an event step, the event's name.
prop?: string (max length 64) // For an event step, a property key; then value is required.
value?: string (max length 256)
label?: string (max length 40) // What the step is called. Made from the path or the event when left out.
id?:
string // 1 to 32 characters a-z, 0-9 and -. Made from the name when left out.
name:
string (max length 40)
mode?:
FunnelMode
ordered?:
boolean (default true) // true: the steps count only in their order, with anything else allowed between them. false: reaching step N means having done steps 1 to N in any order.
steps:
array of <a href="#schema-FunnelStep">FunnelStep</a> (min items 2, max items 8) FunnelMode
What one journey is. session: one visit (a session ends after 30 minutes without activity). day: one visitor's day, across their visits. LogSure keeps no cookies and visitor codes change at UTC midnight, so a journey that crosses midnight counts as two, and so does one person on two devices or browsers.
"session" | "day" (default "session")
Funnel
id:
string
name:
string
mode:
FunnelMode
ordered:
boolean
steps:
array of <a href="#schema-FunnelStep">FunnelStep</a> FunnelStepResult
label: string
entered: integer // Journeys that reached this step (and, when ordered, every step before it, in order).
fromPrevious: number | null // entered ÷ the previous step's entered, 0 to 1; null for the first step or when it had none.
fromStart: number | null // entered ÷ the first step's entered, 0 to 1; null when the first step had none.
dropOff: integer // Journeys that reached this step and not the next; 0 for the last step.
medianToNext: integer | null // Median milliseconds from reaching this step to reaching the next, over the journeys that reached the next; null for the last step or when none did.
FunnelResult
A funnel counted over a window. A journey (a session, or a visitor-day in day mode) is counted when its session started in the window, under the segment and filters, and only its events in the window are read. In order, each step is the first match after the previous step's, so a journey that does step 2 before step 1 reaches step 2 only if it does step 2 again after step 1.
funnel:
Funnel
journeys:
integer // Journeys in the window, whether or not they reached a step.
steps:
array of <a href="#schema-FunnelStepResult">FunnelStepResult</a>
conversion:
number | null // The last step's fromStart. FunnelAnswer
segment:
string
filters:
object
range:
Range
funnel:
Funnel
journeys:
integer
steps:
array of <a href="#schema-FunnelStepResult">FunnelStepResult</a>
conversion:
number | null Role
"owner" | "admin" | "member" | "viewer"
User
id:
string
username:
string | null // null for an owner who signs in with the root password and has not set a username.
name:
string | null
role:
Role
apps:
array | null // The apps the user is limited to; null for every app.
twoFactor:
boolean
createdAt:
Time
lastSeenAt:
integer | null Invite
id:
string
role:
Role
apps:
array | null
createdAt:
Time
expiresAt:
Time
createdBy:
string | null // The username (or display name) of who made it. AuditEntry
id:
integer
at:
Time
userId:
string | null
username:
string | null // As the user is called now; null once removed or for a failed sign-in by an unknown name.
action:
string // e.g. sign_in, sign_in_failed, two_factor_on, two_factor_off, sign_in_changed, user_joined, role_changed, user_removed, invite_created, invite_revoked, team_changed, app_created, app_changed, app_deleted, key_rotated, privacy_deleted, ai_key_created, ai_revoked, device_added, device_removed.
target:
string | null // What it was done to (an app ID, a user ID, …).
detail:
object | null DashboardPanelKind
The dashboard's cards. New kinds may be added; a client shows a kind it does not know as a placeholder and keeps it in the layout when saving.
"summary" | "chart" | "pages" | "sources" | "locations" | "devices" | "languages" | "sites" | "goals" | "feedback" | "funnel" | "waitlist" | "live" | "errors" | "lab"
DashboardPanel
panel:
DashboardPanelKind
options:
object // The panel's own choices, at most 10 keys (letters and digits, starting with a letter, at most 32), each a string (at most 100 characters), a number, true or false, or a list of at most 10 strings. In use: pages tab (top, entry, exit); sources tab (source, utm_source, utm_medium, utm_campaign); devices tab (device, browser, os); locations tab (map, countries); chart events (up to three custom events); funnel funnel (a funnel ID). errors (the app's top open issues) and lab (its running experiments) need none.
width:
integer (min 1, max 12) // Columns of 12. Sent as a number or a preset (full 12, twoThirds 8, half 6, third 4, quarter 3); saved as a number.
height:
"compact" | "normal" | "tall" // Always answered as one of these words. A request may send a number of rows here instead (or in rows); the answer is then the word the rows fall in: 3 to 6 compact, 7 to 12 normal, 13 to 30 tall, so clients that know only the words keep working.
rows:
integer | null (min 3, max 30) // The exact height in rows of the console's grid, a row being 40 pixels (gaps included); null when the height is just the word. A client that sizes panels by rows uses this and falls back to the word.
collapsed:
boolean DashboardLayout
version: 1 // The layout format. A layout of a newer version than the server knows is refused.
panels: array of <a href="#schema-DashboardPanel">DashboardPanel</a> (max items 40)
DashboardRef
id: string // default for the built-in dashboard.
name: string
customized: boolean // For default, whether this user changed it; always true for a named dashboard.
updatedAt: integer | null
DashboardView
id:
string
name:
string
customized:
boolean
updatedAt:
integer | null
layout:
DashboardLayout Passkey
id:
string
name:
string
provider:
string | null // Where it is kept, when known from its AAGUID (iCloud Keychain, Google Password Manager, …).
aaguid:
string | null
backedUp:
boolean // Synced by its provider (it survives losing the device).
createdAt:
Time
lastUsedAt:
integer | null PasskeyCreationOptions
For navigator.credentials.create({ publicKey }). Binary fields (challenge, user.id, excludeCredentials[].id) are base64url text: decode them to ArrayBuffers first (or use PublicKeyCredential.parseCreationOptionsFromJSON).
challenge: string
rp: object
id: string // The console's host (CONSOLE_HOST without a port).
name: string
user: object
id: string // The user's handle, base64url; not their ID or name.
name: string
displayName: string
pubKeyCredParams: array of object
type: "public-key"
alg: integer // -7 (ES256), -8 (EdDSA), -257 (RS256).
timeout: integer
excludeCredentials: array of <a href="#schema-PasskeyDescriptor">PasskeyDescriptor</a>
authenticatorSelection: object
residentKey: "required"
requireResidentKey: true
userVerification: "preferred"
attestation: "none"
PasskeyRequestOptions
For navigator.credentials.get({ publicKey }); the challenge is base64url text.
challenge: string
rpId: string
allowCredentials: array of <a href="#schema-PasskeyDescriptor">PasskeyDescriptor</a> (max items 0)
userVerification: "required"
timeout: integer
PasskeyDescriptor
type: "public-key"
id: string
PasskeyAttestation
What navigator.credentials.create() gave, as credential.toJSON() writes it (binary as base64url).
id: string
rawId?: string
type: "public-key"
response: object
clientDataJSON: string
attestationObject: string
transports?: array of string
PasskeyAssertion
What navigator.credentials.get() gave, as credential.toJSON() writes it (binary as base64url).
id: string
rawId?: string
type: "public-key"
response: object
clientDataJSON: string
authenticatorData: string
signature: string
userHandle?: string | null
ImportedWindow
A window that holds imported days. Imported data counts in totals, the visitors chart, the overview, goals (from GA key events) and the breakdowns of pages, entry pages, sources, UTM medium and campaign, countries, devices, browsers, systems and languages. It does not count with a segment or filters (merged is false then), for exit pages, hosts and UTM sources, in funnels, a custom event's own page, or live visitors.
days: integer
from: string (date)
to: string (date)
merged: boolean // Whether this answer counts them.
GoogleProperty
id: string
name: string
account: string
ImportStatus
provider: "ga4"
configured: boolean // Whether the server has a Google OAuth client.
setup: string | null // What whoever runs the server must set, when not configured.
redirectUri: string // The redirect URI to register with the Google OAuth client.
connected: boolean
connectedBy: string | null
connectedAt: integer | null
property: object | null
id: string
name: string
state: "idle" | "running" | "waiting" | "done" | "failed" | "cancelled" // waiting is a pause for Google's quota, until retryAt.
from: string | null (date)
to: string | null (date)
doneThrough: string | null (date) // The last day fetched.
progress: number | null // 0 to 1.
days: integer // Days imported, all imports together.
rows: integer // Breakdown rows imported, all imports together.
requests: integer // Reports asked of Google by the current or last import.
error: string | null
retryAt: integer | null
startedAt: integer | null
finishedAt: integer | null
firstEvent: string | null (date) // The day of the app's first own event; imports stop the day before.
latest: string (date) // The last day an import can include.
ErrorFrame
file?: string | null // The script's URL (no query) or file path.
function?: string | null
line?: integer | null
column?: integer | null
inApp?: boolean // The app's own code (not a library or the browser's); worked out when not sent.
ErrorReport
app?:
string
message:
string (max length 2000)
type?:
string (max length 100) // e.g. TypeError.
stack?:
string // The stack as JavaScript (or the platform) writes it; read into frames when frames are not sent.
frames?:
array of <a href="#schema-ErrorFrame">ErrorFrame</a> (max items 50) // The stack, innermost frame first.
url?:
string // The page or screen it happened on.
release?:
string (max length 64)
environment?:
string (max length 32) // Default production.
props?:
object // Like an event's props.
fingerprint?:
string (max length 200) // Groups reports by this instead of the type, message and top frames.
handled?:
boolean // Caught by the app (logsure.error) rather than escaped.
auto?:
boolean // Caught by the tracker by itself (the earlier way to say source uncaught).
source?:
ErrorSource
request?:
ErrorRequest
timestamp?:
integer // When it happened (ms); at most a day old, else now.
userAgent?:
string // For server and native clients; a browser's own User-Agent header is used otherwise. ErrorSource
How it was caught: manual (logsure.error, or a server or app sending it; the default), uncaught (the page's error event), promise (a rejected promise nobody handled), console (console.error), network (a failed fetch or XMLHttpRequest), resource (a script, style sheet or image that did not load), span (an OpenTelemetry span's exception event), log (an OpenTelemetry log at ERROR or above). uncaught to resource are kept only when the app turned that capture on.
"manual" | "uncaught" | "promise" | "console" | "network" | "resource" | "span" | "log"
ErrorRequest
For network errors, the request; never its body or headers.
method?: string | null (max length 10)
url?: string | null // Without its query string.
status?: integer | null // The HTTP status; 0 or null when the request failed before an answer.
duration?: integer | null // Milliseconds.
ErrorSettings
What the tracker captures by itself, each off by default; a request may leave any out (off).
capture: boolean // Uncaught errors and rejected promises.
console: boolean // console.error calls (the console still shows them).
network: boolean // fetch and XMLHttpRequest requests that fail, or answer 5xx.
network4xx: boolean // With network, 4xx answers too.
resources: boolean // Scripts, style sheets and images that do not load.
ignore: array of string <span class="d">(max length 200)</span> (max items 50) // Reports whose message contains one of these (any case) are dropped.
ErrorIssue
id:
string
appId:
string
type:
string | null
message:
string
culprit:
string | null // Where it happened, the top frame of the app's own code, e.g. render (app.js:12).
status:
"open" | "resolved" | "ignored"
count:
integer // Occurrences in the window (or all of them, for getError).
visitors:
integer // Visitors it happened to, counted per day like the dashboard's.
total:
integer // Every occurrence ever.
firstSeen:
Time
lastSeen:
Time
lastRelease:
string | null
lastEnvironment:
string | null
reopenedAt:
integer | null // When it last came back after being resolved.
resolvedAt:
integer | null
spark?:
array of integer // In listErrors only: occurrences per UTC day over the last 14 days up to today, oldest first (14 numbers), whatever the window, for a sparkline. ErrorOccurrence
traceId:
string | null // The OpenTelemetry trace it happened in, for a link to it.
spanId:
string | null
source:
ErrorSource
request:
ErrorRequest
id:
integer
at:
Time
type:
string | null
message:
string
handled:
boolean | null
release:
string | null
environment:
string | null
path:
string | null
host:
string | null
browser:
string | null
os:
string | null
device:
string | null
country:
string | null
frames:
array of <a href="#schema-ErrorFrame">ErrorFrame</a>
stack:
string | null
props:
object | null ErrorDetail
id:
string
appId:
string
type:
string | null
message:
string
culprit:
string | null // Where it happened, the top frame of the app's own code, e.g. render (app.js:12).
status:
"open" | "resolved" | "ignored"
count:
integer // Occurrences in the window (or all of them, for getError).
visitors:
integer // Visitors it happened to, counted per day like the dashboard's.
total:
integer // Every occurrence ever.
firstSeen:
Time
lastSeen:
Time
lastRelease:
string | null
lastEnvironment:
string | null
reopenedAt:
integer | null // When it last came back after being resolved.
resolvedAt:
integer | null
spark?:
array of integer // In listErrors only: occurrences per UTC day over the last 14 days up to today, oldest first (14 numbers), whatever the window, for a sparkline.
sources:
array of <a href="#schema-Row">Row</a>
releases:
array of <a href="#schema-Row">Row</a>
environments:
array of <a href="#schema-Row">Row</a>
browsers:
array of <a href="#schema-Row">Row</a>
systems:
array of <a href="#schema-Row">Row</a>
devices:
array of <a href="#schema-Row">Row</a>
pages:
array of <a href="#schema-Row">Row</a>
series:
array of object // The last 30 days, by UTC day.
t:
Time
count:
integer
notes:
array of object
id:
integer
ts:
Time
kind:
string // note, or system (it came back, it was resolved).
body:
string
latest:
ErrorOccurrence FlagType
Every flag has a type, and every value it serves is of it; a boolean flag is a toggle.
"boolean" | "string" | "number" | "json"
FlagVariant
key: string (pattern ^[a-z0-9][a-z0-9_-]{0,31}$)
name?: string | null
value: // Of the flag's type (any JSON for json).
FlagScoreRange
Scores from min to max, both included (10 to 50 takes 10 and 50), get the variant.
min: number (min 0, max 100)
max: number (min 0, max 100)
variant: string
FlagScoreSettings
useScore?: boolean // Whether the flag targets by the score the app passes (0 to 100, set by the app, with whatever meaning it likes; LogSure never makes one). Off (the default), any score passed is ignored.
ranges?: array of <a href="#schema-FlagScoreRange">FlagScoreRange</a> (max items 20) // Tried top first; a score in none of them (or no score) gets the default variant.
FlagCondition
attr: string // An attribute the app passes; id is the id, and score the score (when the flag uses it).
op: "eq" | "neq" | "in" | "not_in" | "contains" | "starts_with" | "ends_with" | "gt" | "gte" | "lt" | "lte" | "semver_eq" | "semver_gt" | "semver_gte" | "semver_lt" | "semver_lte" | "exists" // eq, neq, in and not_in compare as text; gt to lte as numbers; semver_* as versions (1.2.3, a leading v, a pre-release below its release). An attribute that was not passed matches no condition, negated ones included, and exists answers false for it.
value?: // A string or number, or for in and not_in a list (at most 100); not for exists.
FlagRule
name?: string | null
match?: "all" | "any" // Default all.
conditions: array of <a href="#schema-FlagCondition">FlagCondition</a> (min items 1, max items 20)
variant: string // The variant (value) it serves.
key:
string (pattern ^[a-z0-9][a-z0-9_.-]{0,59}$)
name:
string (min length 1, max length 100)
description?:
string | null (max length 1000)
type:
FlagType
variants?:
array of <a href="#schema-FlagVariant">FlagVariant</a> (min items 1, max items 10) // The values it may serve; default for boolean, on (true) and off (false).
defaultVariant?:
string // Served while the flag is off (the kill switch), and, when it uses the score, without a score or outside every range. Default the last variant (off for boolean).
fallthrough?:
string // Served when the flag is on, no rule matched, and it does not use the score. Default the first variant that is not the default one (on for boolean).
rules?:
array of <a href="#schema-FlagRule">FlagRule</a> (max items 50) // Tried in order; the first that matches serves, before the score.
useScore?:
boolean
ranges?:
array of <a href="#schema-FlagScoreRange">FlagScoreRange</a>
exposure?:
boolean // Send a $flag event (flag, variant) the first time a page evaluates it, to analyse it like an experiment. FlagChanges
name?: string
description?: string | null
variants?: array of <a href="#schema-FlagVariant">FlagVariant</a>
defaultVariant?: string
fallthrough?: string
rules?: array of <a href="#schema-FlagRule">FlagRule</a>
useScore?: boolean
ranges?: array of <a href="#schema-FlagScoreRange">FlagScoreRange</a>
exposure?: boolean
FlagDefinition
key:
string
type:
FlagType
enabled:
boolean // The kill switch, apart from the value; off serves the default variant.
variants:
array of <a href="#schema-FlagVariant">FlagVariant</a>
defaultVariant:
string
fallthrough:
string
rules:
array of <a href="#schema-FlagRule">FlagRule</a>
useScore:
boolean
ranges:
array of <a href="#schema-FlagScoreRange">FlagScoreRange</a>
exposure:
boolean
version:
integer // Goes up with every change. Flag
key:
string
type:
FlagType
enabled:
boolean // The kill switch, apart from the value; off serves the default variant.
variants:
array of <a href="#schema-FlagVariant">FlagVariant</a>
defaultVariant:
string
fallthrough:
string
rules:
array of <a href="#schema-FlagRule">FlagRule</a>
useScore:
boolean
ranges:
array of <a href="#schema-FlagScoreRange">FlagScoreRange</a>
exposure:
boolean
version:
integer // Goes up with every change.
id:
string
appId:
string
name:
string
description:
string | null
createdAt:
Time
updatedAt:
Time
experiments:
array of string // The IDs of the experiments that measure it.
overlaps:
array of object // For the editor's warning, each range that overlaps an earlier one (which serves).
range:
integer
earlier:
integer FlagConfig
flags: array of <a href="#schema-FlagDefinition">FlagDefinition</a>
Variant
key: string (pattern ^[a-z0-9][a-z0-9_-]{0,31}$)
name?: string | null
weight: number // Shares are weight / the sum of the weights.
ExperimentGoal
type: "event" | "page" | "funnel"
event?: string // For event, the custom event's name.
page?: string // For page, a path; a * stands for anything.
funnel?: string // For funnel, a saved funnel's ID; completing it converts.
better?: "higher" | "lower" // For a guardrail, which way is good (default lower, e.g. errors). The goal is always higher.
key:
string (pattern ^[a-z0-9][a-z0-9_-]{0,59}$) // What logsure.variant and the events name it by; unique in the app.
name:
string (min length 1, max length 100)
hypothesis?:
string | null (max length 1000)
variants:
array of <a href="#schema-Variant">Variant</a> (min items 2, max items 5) // The first is the control.
goal:
ExperimentGoal
guardrails?:
array of <a href="#schema-ExperimentGoal">ExperimentGoal</a> (max items 3)
flag?:
string | null // A flag's key: the experiment measures that flag, its variants being the flag's (same keys), and its exposures also the flag's $flag events. ExperimentChanges
key?:
string
name?:
string
hypothesis?:
string | null
variants?:
array of <a href="#schema-Variant">Variant</a>
goal?:
ExperimentGoal
guardrails?:
array of <a href="#schema-ExperimentGoal">ExperimentGoal</a>
flag?:
string | null Experiment
flag:
string | null
id:
string
appId:
string
key:
string
name:
string
hypothesis:
string | null
status:
"draft" | "running" | "stopped"
variants:
array of <a href="#schema-Variant">Variant</a>
goal:
ExperimentGoal
guardrails:
array of <a href="#schema-ExperimentGoal">ExperimentGoal</a>
createdAt:
Time
updatedAt:
Time
startedAt:
integer | null
stoppedAt:
integer | null ExperimentSummary
flag:
string | null
id:
string
appId:
string
key:
string
name:
string
hypothesis:
string | null
status:
"draft" | "running" | "stopped"
variants:
array of <a href="#schema-Variant">Variant</a>
goal:
ExperimentGoal
guardrails:
array of <a href="#schema-ExperimentGoal">ExperimentGoal</a>
createdAt:
Time
updatedAt:
Time
startedAt:
integer | null
stoppedAt:
integer | null
exposures:
integer // Exposed units so far (0 for a draft). Interval
value: number
low: number
high: number
VariantResult
key:
string
exposures:
integer
conversions:
integer
rate:
number | null // conversions / exposures.
diff:
Interval // rate minus control's, with a 95% interval; null for control or without data.
uplift:
Interval // diff relative to control's rate, with the same interval scaled.
pValue:
number | null
probBeatControl:
number | null ExperimentResults
experiment:
Experiment
from:
integer | null // When it started (null for a draft).
to:
Time
unit:
"visitor-day"
control:
string
variants:
array of <a href="#schema-VariantResult">VariantResult</a>
srm:
object
pValue:
number | null
mismatch:
boolean
expected:
array of number // Exposures each variant should have by its weight.
observed:
array of integer
enough:
boolean // Every variant has the minimum exposures and conversions.
minimum:
object
exposures:
integer
conversions:
integer
hint:
"draft" | "collecting" | "keep_running" | "variant_ahead" | "control_ahead" | "srm" // collecting: below the minimum. keep_running: no clear answer yet. variant_ahead: a variant beats control (p below 0.05 and probBeatControl at least 0.95), named in best. control_ahead: every variant is worse. srm: the split does not match the weights; check the assignment before reading anything.
best:
string | null
guardrails:
array of object
goal:
ExperimentGoal
variants:
array of <a href="#schema-VariantResult">VariantResult</a>
harmed:
array of string // Variants clearly worse than control on it (p below 0.05, the wrong way).
series:
array of object // Cumulative, by UTC day.
t:
Time
variants:
array of object
key:
string
exposures:
integer
conversions:
integer
rate:
number | null
segments:
object | null
dimension:
string
rows:
array of object // The 10 biggest values.
value:
string | null
variants:
array of <a href="#schema-VariantResult">VariantResult</a> FeatureKey
analytics (pageviews, events, sessions, imports, segments), funnels, inbox (feedback, bug reports and messages), waitlist, errors, tracing (OpenTelemetry traces and logs), lab (A/B testing) and mcp (the MCP server, instance only).
"analytics" | "funnels" | "inbox" | "waitlist" | "errors" | "tracing" | "lab" | "mcp"
AppFeatureKey
"analytics" | "funnels" | "inbox" | "waitlist" | "errors" | "tracing" | "lab"
Features
analytics: boolean
funnels: boolean
inbox: boolean
waitlist: boolean
errors: boolean
tracing: boolean
lab: boolean
InstanceFeatures
analytics: boolean
funnels: boolean
inbox: boolean
waitlist: boolean
errors: boolean
tracing: boolean
lab: boolean
mcp: boolean
FeatureChanges
Any of the switches, true or false.
analytics?: boolean
funnels?: boolean
inbox?: boolean
waitlist?: boolean
errors?: boolean
tracing?: boolean
lab?: boolean
mcp?: boolean // setFeatures only, from the web console.
AppFeatures
FeatureState
instance:
InstanceFeatures
defaults:
Features // The deployment's defaults (FEATURES_DISABLED), for what the instance has not set.
apps:
array of <a href="#schema-AppFeatures">AppFeatures</a> TracingSettings
sampleRate: number (min 0, max 1) // The share of traces kept, whole traces by their ID (1, the default, keeps all the SDKs send).
denyAttributes: array of string <span class="d">(max length 200)</span> (max items 100) // Attribute keys removed before storage, on top of the built-in ones (credentials, cookies, authorization headers); a trailing * removes a prefix, e.g. app.user.*.
SpanStatus
"unset" | "ok" | "error"
SpanKind
"unspecified" | "internal" | "server" | "client" | "producer" | "consumer"
TraceSummary
traceId:
string
name:
string // The root span's name.
service:
string
start:
number // Milliseconds since the epoch (with microseconds as decimals).
duration:
number // Milliseconds.
spans:
integer
errors:
integer // Spans with status error.
status:
SpanStatus
route:
string | null
method:
string | null
httpStatus:
integer | null Span
traceId:
string
spanId:
string
parentId:
string | null
name:
string
kind:
SpanKind
service:
string
start:
number
duration:
number
status:
SpanStatus
statusMessage:
string | null
route:
string | null
method:
string | null
httpStatus:
integer | null
dbSystem:
string | null
attributes:
object
events:
array of object
time:
number
name:
string
attributes:
object SpanDetail
traceId:
string
spanId:
string
parentId:
string | null
name:
string
kind:
SpanKind
service:
string
start:
number
duration:
number
status:
SpanStatus
statusMessage:
string | null
route:
string | null
method:
string | null
httpStatus:
integer | null
dbSystem:
string | null
attributes:
object
events:
array of object
time:
number
name:
string
attributes:
object
resource:
object
scope:
string | null // The instrumentation scope's name (and version).
links:
array of object
traceId:
string
spanId:
string
attributes:
object
logs:
array of <a href="#schema-LogEntry">LogEntry</a> Trace
traceId: string
start: number
duration: number
services: array of string
spans: array of <a href="#schema-Span">Span</a> // Ordered by start; a parent before its children.
logs: array of <a href="#schema-LogEntry">LogEntry</a>
errors: array of object // The error issues this trace's exceptions and error logs belong to.
id: string
message: string
spanId: string | null
LogEntry
id: integer
time: number // Milliseconds since the epoch.
severity: integer // OpenTelemetry's severity number, 1 to 24 (0 unknown).
severityText: string | null
body: string
service: string
traceId: string | null
spanId: string | null
attributes: object
Latency
p50: number | null
p95: number | null
p99: number | null
ServiceStats
service:
string
spans:
integer
rate:
number // Spans a minute over the window.
errorRate:
number | null // 0 to 1.
latency:
Latency
series:
array of object
t:
Time
spans:
integer
errors:
integer
p95:
number | null
operations:
array of object // The busiest 50.
name:
string
spans:
integer
rate:
number
errorRate:
number | null
latency:
Latency AppSettings
features:
AppFeatures
app:
App
hasKey:
boolean
keyHint:
string | null // The server key's last 4 characters.
webhook:
string | null
turnstile:
boolean // Whether a Turnstile secret is set (the secret itself is never shown).
forms:
object
bug:
array of <a href="#schema-FormField">FormField</a>
feedback:
array of <a href="#schema-FormField">FormField</a>
message:
array of <a href="#schema-FormField">FormField</a>
attachments:
AttachmentRules
retention:
RetentionRules
native:
NativeRules
waitlist:
WaitlistRules
notes:
string | null
segments:
array of <a href="#schema-Segment">Segment</a>
defaultSegment:
string | null
funnels:
array of <a href="#schema-Funnel">Funnel</a>
errors:
ErrorSettings
tracing:
TracingSettings
effective:
object // The rules in force, the deployment's defaults filled in.
attachments:
object
count:
integer
maxFileBytes:
integer
maxTotalBytes:
integer
extraTypes:
array of string
retention:
object
eventDays:
integer
resolvedDays:
integer
spamDays:
integer
attachmentDays:
integer
ticketDays:
integer | null
waitlist:
object
requireConsent:
boolean
consentVersion:
string | null
defaultList:
string
status:
object
lastEvent:
integer | null
today:
object // Counts of the current UTC day.
events:
integer
tickets:
integer
signups:
integer
dropped:
integer // Events over the daily cap.
rateLimited:
integer
overCap:
integer // Requests or signups over a daily cap.
setup:
object // The facts the setup instructions are made from.
prefix:
string
consoleHost:
string
script:
string // The script tag for the app's own host, routed to this server under the prefix.
consoleScript:
string // The script tag loading straight from the console's host.
endpoint:
string // Where back-ends send events with the server key.
domains:
array of object
domain:
string
route:
"live" | "redirect" | "none" | "wildcard" | "development" // Whether https://<domain><prefix>/health reaches this server (probed, cached a minute). AiConnection
id:
string
name:
string
kind:
"oauth" | "key"
scope:
array of "analytics" | "inbox" | "write" | "settings"
clientId:
string | null
refreshExpires:
integer | null
createdAt:
Time
lastUsedAt:
integer | null TokenRequest
grant_type: "authorization_code" | "refresh_token"
code?: string
code_verifier?: string
redirect_uri?: string
client_id?: string
refresh_token?: string
OAuthError
error: string
error_description?: string
Me
features:
FeatureState // What is on, as getFeatures answers; clients hide the menus, panels and dashboard panel kinds of what is off.
device:
DeviceRef
user:
string | null // The sign-in username, when one is set.
name:
string | null // The user's display name.
userId:
string
role:
Role
appAccess:
array | null // The app IDs this user is limited to; null for every app.
twoFactorRequired:
boolean // The team requires two-factor sign-in and this user has neither a two-factor app nor a passkey yet.
passkeys:
integer // How many passkeys this user has.
apps:
array of <a href="#schema-App">App</a>
notifications:
Notifications
counts:
TicketCounts DeviceRef
id: string
name: string
Device
id:
string
name:
string
createdAt:
Time
lastUsedAt:
integer | null Profile
name: string | null
photoAt: integer | null // When the photo was set; also its cache key.
TicketCounts
open: integer
progress: integer
resolved: integer
spam: integer
archived: integer
total: integer
byApp: object
byKind: object
Notifications
count: integer // Items newer than seenAt.
seenAt: integer | null
items: array of <a href="#schema-NotificationItem">NotificationItem</a>
NotificationItem
type:
"ticket" | "signups" | "error" // error items come only when the request asks with include=errors.
id?:
string // The request's ID (type ticket), or the issue's (type error).
reopened?:
boolean // For an error, whether it came back after being resolved (else it is new).
appId:
string
kind?:
TicketKind
subject?:
string | null
rating?:
integer | null
list?:
string // The waitlist (type signups).
at:
Time
n:
integer // How many signups this item stands for. TicketKind
"bug" | "feedback" | "message"
TicketStatus
"open" | "progress" | "resolved" | "spam" | "archived"
TicketSummary
id:
string
appId:
string
kind:
TicketKind
status:
TicketStatus
subject:
string | null
preview:
string // The first 200 characters of the text.
email:
string | null
name:
string | null
rating:
integer | null
category:
string | null
createdAt:
Time
updatedAt:
integer | null
files?:
integer // How many attachments (in lists). Ticket
id:
string
appId:
string
kind:
TicketKind
status:
TicketStatus
subject:
string | null
body:
string
data:
object // Bug report details (steps, expected, version) and the app's custom fields.
meta:
object
url?:
string | null
browser?:
string | null
os?:
string | null
device?:
string | null
country?:
string | null
lang?:
string | null
host?:
string | null
props?:
object | null
email:
string | null
name:
string | null
rating:
integer | null
category:
string | null
createdAt:
Time
updatedAt:
integer | null
notes:
array of object
id:
integer | string
ts:
Time
kind:
string // note (by the owner) or system.
body:
string
attachments:
array of object
id:
string
name:
string
type:
string
size:
integer SignupStatus
"waiting" | "invited" | "joined"
Signup
id:
integer
appId:
string
list:
string
email:
string
name:
string | null
status:
SignupStatus
source:
string | null
country:
string | null
lang:
string | null
createdAt:
Time
updatedAt?:
integer | null WaitlistPage
total: integer
items: array of <a href="#schema-Signup">Signup</a>
lists: array of object
appId: string
list: string
total: integer
waiting: integer
invited: integer
joined: integer
counts: object
total: integer
byApp: object
EmailBody
email: string (max length 254)