LogSure

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

NameKind
serverKeybearerAn app's server key (cc_…), for back-ends only. Requests with a key and an Origin are refused.
deviceTokenbearerA device token (cc_…) made in Settings › Mobile app.
mcpTokenbearerAn MCP access token from /oauth/token, or a personal API key (ccm_…).
sessioncookieThe 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.

Ok ok: true

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).

Ok ok: true

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

NameInType
namepathstring (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.

Ok ok: true

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.

Ok ok: true

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

NameInType
kindpathTicketKind
app optionalquerystringThe 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).

Ok ok: true

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

NameInType
app optionalquerystring

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

NameInType
range optionalqueryRangeKeyThe window. Ignored when from and to are both given. Unknown values fall back to 7d.
from optionalqueryWindowEndStart 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 optionalqueryWindowEndEnd 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

NameInType
apppathstring
range optionalqueryRangeKeyThe window. Ignored when from and to are both given. Unknown values fall back to 7d.
from optionalqueryWindowEndStart 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 optionalqueryWindowEndEnd 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 optionalquerystringA saved segment's ID, or all. Default is the app's default segment.
cev optionalquerystringUp to three custom events to draw as chart lines, comma-separated.
path optionalquerystring
source optionalquerystringThe referrer's host or utm_source; (none) for direct visits.
country optionalquerystring
device optionalquerystring
browser optionalquerystring
os optionalquerystring
lang optionalquerystring
host optionalquerystring
event optionalquerystringOnly 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

NameInType
apppathstring
dimqueryDimension
limit optionalqueryinteger (min 1, max 250, default 50)
range optionalqueryRangeKeyThe window. Ignored when from and to are both given. Unknown values fall back to 7d.
from optionalqueryWindowEndStart 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 optionalqueryWindowEndEnd 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 optionalquerystringA saved segment's ID, or all. Default is the app's default segment.
path optionalquerystring
source optionalquerystringThe referrer's host or utm_source; (none) for direct visits.
country optionalquerystring
device optionalquerystring
browser optionalquerystring
os optionalquerystring
lang optionalquerystring
host optionalquerystring
event optionalquerystringOnly visits that sent this custom event.

Responses

200 The rows, by visitors.

imported: ImportedWindow dim: Dimension rows: array of <a href="#schema-Row">Row</a>

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

NameInType
apppathstring
namepathstring (max length 64)
range optionalqueryRangeKeyThe window. Ignored when from and to are both given. Unknown values fall back to 7d.
from optionalqueryWindowEndStart 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 optionalqueryWindowEndEnd 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 optionalquerystringA saved segment's ID, or all. Default is the app's default segment.
path optionalquerystring
source optionalquerystringThe referrer's host or utm_source; (none) for direct visits.
country optionalquerystring
device optionalquerystring
browser optionalquerystring
os optionalquerystring
lang optionalquerystring
host optionalquerystring
event optionalquerystringOnly 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

NameInType
apppathstring
range optionalqueryRangeKeyThe window. Ignored when from and to are both given. Unknown values fall back to 7d.
from optionalqueryWindowEndStart 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 optionalqueryWindowEndEnd 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 optionalquerystringA saved segment's ID, or all. Default is the app's default segment.
path optionalquerystring
source optionalquerystringThe referrer's host or utm_source; (none) for direct visits.
country optionalquerystring
device optionalquerystring
browser optionalquerystring
os optionalquerystring
lang optionalquerystring
host optionalquerystring
event optionalquerystringOnly 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

NameInType
apppathstring
range optionalqueryRangeKeyThe window. Ignored when from and to are both given. Unknown values fall back to 7d.
from optionalqueryWindowEndStart 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 optionalqueryWindowEndEnd 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 optionalquerystringA saved segment's ID, or all. Default is the app's default segment.
path optionalquerystring
source optionalquerystringThe referrer's host or utm_source; (none) for direct visits.
country optionalquerystring
device optionalquerystring
browser optionalquerystring
os optionalquerystring
lang optionalquerystring
host optionalquerystring
event optionalquerystringOnly visits that sent this custom event.

Request body

application/json

funnel: FunnelInput

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

NameInType
apppathstring
funnelpathstringThe funnel's ID.
range optionalqueryRangeKeyThe window. Ignored when from and to are both given. Unknown values fall back to 7d.
from optionalqueryWindowEndStart 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 optionalqueryWindowEndEnd 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 optionalquerystringA saved segment's ID, or all. Default is the app's default segment.
path optionalquerystring
source optionalquerystringThe referrer's host or utm_source; (none) for direct visits.
country optionalquerystring
device optionalquerystring
browser optionalquerystring
os optionalquerystring
lang optionalquerystring
host optionalquerystring
event optionalquerystringOnly 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

NameInType
apppathstring

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

NameInType
apppathstring

Request body

application/json

name: string (max length 40) layout?: DashboardLayout

Responses

201 The dashboard.

DashboardView id: string name: string customized: boolean updatedAt: integer | null layout: DashboardLayout

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

NameInType
apppathstring
dashboardpathstringdefault, or a dashboard ID from listDashboards.

Responses

200 The dashboard.

DashboardView id: string name: string customized: boolean updatedAt: integer | null layout: DashboardLayout

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

NameInType
apppathstring
dashboardpathstringdefault, or a dashboard ID from listDashboards.

Request body

application/json

name?: string (max length 40) layout?: DashboardLayout

Responses

200 The dashboard as saved.

DashboardView id: string name: string customized: boolean updatedAt: integer | null layout: DashboardLayout

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

NameInType
apppathstring
dashboardpathstringdefault, or a dashboard ID from listDashboards.

Responses

200 The dashboard as reset.

DashboardView id: string name: string customized: boolean updatedAt: integer | null layout: DashboardLayout

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

NameInType
apppathstring
dashboardpathstringdefault, or a dashboard ID from listDashboards.

Responses

200 Done.

Ok ok: true

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

NameInType
app optionalquerystring
kind optionalqueryTicketKind
status optionalquery"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 optionalquerystringWords in the subject, text, name or email address.
limit optionalqueryinteger (min 1, max 500)
offset optionalqueryinteger (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

NameInType
idpathstring (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

NameInType
idpathstring (pattern ^t_)

Request body

application/json

status: TicketStatus

Responses

200 Done.

Ok ok: true

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

NameInType
idpathstring (pattern ^t_)

Request body

application/json

body: string (min length 1)

Responses

200 Done.

Ok ok: true

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

NameInType
idpathstring (pattern ^t_)

Responses

200 Done.

Ok ok: true

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

app?: string kind?: TicketKind

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

NameInType
idpathstring

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

NameInType
app optionalquerystring
kind optionalqueryTicketKind
status optionalquerystring
q optionalquerystring

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

NameInType
app optionalquerystring
list optionalquerystring
status optionalquerySignupStatus
q optionalquerystring
limit optionalqueryinteger (min 1, max 500)
offset optionalqueryinteger (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

NameInType
app optionalquerystring
range optionalqueryRangeKeyThe window. Ignored when from and to are both given. Unknown values fall back to 7d.
from optionalqueryWindowEndStart 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 optionalqueryWindowEndEnd 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

NameInType
idpathstring (pattern ^[0-9]+$)

Request body

application/json

status: SignupStatus

Responses

200 Done.

Ok ok: true

400 401 403 404 Refused. ApiError

POST/api/v1/waitlist/{id}/deleteRemove a signup.

Operation deleteSignup · authentication: deviceToken, session · scope write

Parameters

NameInType
idpathstring (pattern ^[0-9]+$)

Responses

200 Done.

Ok ok: true

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

NameInType
app optionalquerystring
list optionalquerystring
status optionalquerySignupStatus
q optionalquerystring

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

NameInType
apppathstring
status optionalquery"open" | "resolved" | "ignored" | "all"open (default), resolved, ignored or all.
release optionalquerystring
environment optionalquerystring
source optionalqueryErrorSourceOnly issues with occurrences caught this way.
q optionalquerystringWords in the message or type.
range optionalqueryRangeKeyThe window. Ignored when from and to are both given. Unknown values fall back to 7d.
from optionalqueryWindowEndStart 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 optionalqueryWindowEndEnd 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 optionalqueryinteger (min 1, max 500)
offset optionalqueryinteger (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

NameInType
apppathstring

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

NameInType
errorpathstring (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

NameInType
errorpathstring (pattern ^e_)
before optionalqueryintegerThe next of the previous page.
limit optionalqueryinteger (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

NameInType
errorpathstring (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

NameInType
errorpathstring (pattern ^e_)

Request body

application/json

body: string (max length 5000)

Responses

200 Done.

Ok ok: true

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

NameInType
apppathstring
range optionalqueryRangeKeyThe window. Ignored when from and to are both given. Unknown values fall back to 7d.
from optionalqueryWindowEndStart 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 optionalqueryWindowEndEnd 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 optionalquerystring
name optionalquerystringThe root span's name; a trailing * matches a prefix.
status optionalquery"ok" | "error" | "unset"error for traces with an error span anywhere.
minDuration optionalquerynumberMilliseconds.
maxDuration optionalquerynumber
attr optionalqueryarray of stringkey=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 optionalquery"time" | "duration"
limit optionalqueryinteger (min 1, max 500)
offset optionalqueryinteger (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

NameInType
apppathstring
tracepathstring (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

NameInType
apppathstring
tracepathstring (pattern ^[0-9a-f]{32}$)
spanpathstring (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

NameInType
apppathstring
range optionalqueryRangeKeyThe window. Ignored when from and to are both given. Unknown values fall back to 7d.
from optionalqueryWindowEndStart 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 optionalqueryWindowEndEnd 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 optionalquerystringWords in the body.
severity optionalquerystringThe least severity, as OpenTelemetry's numbers (9 info, 13 warn, 17 error, 21 fatal) or words.
service optionalquerystring
trace optionalquerystring
attr optionalqueryarray of stringkey=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 optionalqueryintegerThe next of the previous page.
after optionalqueryintegerOnly logs stored after this one (an id), the oldest first, for polling.
limit optionalqueryinteger (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

NameInType
apppathstring
after optionalqueryinteger
severity optionalquerystring
service optionalquerystring
q optionalquerystring

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

NameInType
apppathstring
range optionalqueryRangeKeyThe window. Ignored when from and to are both given. Unknown values fall back to 7d.
from optionalqueryWindowEndStart 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 optionalqueryWindowEndEnd 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 optionalquerystring

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

NameInType
apppathstring

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

NameInType
app optionalquerystring

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

NameInType
app optionalquerystring

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

NameInType
apppathstring

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

NameInType
apppathstring

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

NameInType
apppathstring
flagpathstring (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

NameInType
apppathstring
flagpathstring (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

NameInType
apppathstring
flagpathstring (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

NameInType
apppathstring
flagpathstring (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

NameInType
apppathstring
flagpathstring (pattern ^f_)

Responses

200 Done.

Ok ok: true

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

NameInType
apppathstring
flagpathstring (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

NameInType
apppathstring
status optionalquery"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

NameInType
apppathstring

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

NameInType
apppathstring
experimentpathstring (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

NameInType
apppathstring
experimentpathstring (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

NameInType
apppathstring
experimentpathstring (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

NameInType
apppathstring
experimentpathstring (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

NameInType
apppathstring
experimentpathstring (pattern ^x_)

Responses

200 Done.

Ok ok: true

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

NameInType
apppathstring
experimentpathstring (pattern ^x_)
segment optionalquery"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

NameInType
apppathstring

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.

AppFeatures app: string own: Features effective: Features

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

NameInType
apppathstring
featurepathAppFeatureKey

Responses

200 How many rows went, by kind.

feature: AppFeatureKey deleted: object

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

credential: PasskeyAssertion remember?: boolean // Stay signed in for 30 days.

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

NameInType
include optionalquerystringerrors, 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

NameInType
apppathstring

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.

app: App

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.

app: 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

NameInType
apppathstring

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

NameInType
apppathstring

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

NameInType
apppathstring

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

NameInType
apppathstring

Request body

application/json

segment: SegmentInput

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

NameInType
apppathstring

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

NameInType
apppathstring

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

NameInType
apppathstring

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

NameInType
apppathstring

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

NameInType
apppathstring

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

NameInType
apppathstring

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

NameInType
apppathstring

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

NameInType
apppathstring

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

NameInType
code optionalquerystring
state optionalquerystring
error optionalquerystring

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

NameInType
apppathstring

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

NameInType
apppathstring

Responses

200 Done.

Ok ok: true

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

NameInType
apppathstring

Responses

200 The app with its icon address.

app: App

401 403 404 Refused. ApiError

GET/api/v1/icons/{app}The app's icon image.

Operation getAppIcon · authentication: deviceToken, session · scope analytics

Parameters

NameInType
apppathstring
v optionalquerystringThe 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

NameInType
include optionalquerystringerrors, 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.

Ok ok: true

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

EmailBody email: string (max length 254)

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

EmailBody email: string (max length 254)

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.

Ok ok: true

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.

Ok ok: true

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 &quot;analytics&quot; | &quot;inbox&quot; | &quot;write&quot; | &quot;settings&quot; 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

NameInType
idpathstring

Responses

200 Done.

Ok ok: true

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

NameInType
client_id optionalquerystring
redirect_uri optionalquerystring
response_type optionalquerystring
state optionalquerystring
code_challenge optionalquerystring
code_challenge_method optionalquerystring
scope optionalquerystring
resource optionalquerystring

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.

Ok ok: true

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.

Ok ok: true

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

NameInType
passkeypathstring

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

NameInType
passkeypathstring

Responses

200 Done.

Ok ok: true

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.

Ok ok: true

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

NameInType
idpathstring

Responses

200 Done.

Ok ok: true

401 403 404 Refused. ApiError

POST/api/v1/devices/{id}/renameRename a connected device.

Operation renameDevice · authentication: deviceToken, session · scope write

Parameters

NameInType
idpathstring

Request body

application/json

name: string (min length 1, max length 60) // Trimmed; one line, without control characters.

Responses

200 The device as saved.

device: Device

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

NameInType
userpathstring

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

NameInType
userpathstring

Responses

200 Done.

Ok ok: true

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

NameInType
invitepathstring

Responses

200 Done.

Ok ok: true

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

NameInType
before optionalqueryintegerThe next of the previous page.
limit optionalqueryinteger (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

NameInType
codequerystring

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

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

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

FormField

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)

ExtraType

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.

SegmentInput

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.

FunnelInput

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.

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.

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.

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.

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

app: string own: Features effective: Features

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 &quot;analytics&quot; | &quot;inbox&quot; | &quot;write&quot; | &quot;settings&quot; 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)