Base URL and description
The API reference in these docs is generated from that document: every endpoint, with its
parameters and response fields.
Authentication
Send a project token as a bearer token:403, and a revoked token gets 401.
The console calls the same endpoints with your session cookie instead: it reads every project of
the organizations you belong to, and any other project gets 403. Issuing project tokens from
the console is not available yet.
An OAuth access token from Apsio’s sign-in (WorkOS AuthKit) issued for the API
(https://api.apsio.io) reads what its user can read, like the console session: memberships are
checked on every request, so a person removed from an organization loses its projects at once.
A token that names an organization (org_id) reads only that organization. Tokens issued for the
MCP server work only through the MCP server, which passes them on with
its own credential; sent to the API directly they get 401, like a token for any other audience.
Every request with a project token or an access token, directly or through the MCP server, is
written to the organization’s audit log before anything is read, with the user or the project
token’s id (never the token), the route and, from the MCP server, the tool. If the entry cannot
be written the request fails with 503 and reads nothing. Requests from the console, with your
session, are not agent calls and are not written. The entries are in
GET /v1/orgs/{orgId}/audit with the action agent.call.
GET /v1/projects lists the projects your credentials can read, with their apps: a project
token’s one project, or every project of your organizations.
When you run the Apsio service locally from its repository, mint a project token with
pnpm --filter @apsio/api token <project id> [ttl seconds], which signs it with the
API_TOKEN_SECRET of your environment. This is an operator tool for local and self-hosted
stacks, not how customers get tokens.X-Request-Id header; quote it when you report a
problem.
The public sandbox
To try the API without an account, ask for a token for the public sandbox: a read-only example project (Acme Shop on iOS and Android) filled with synthetic data.Apsio-Sandbox: 1 header is required. The answer has a token, its expires_at and the
sandbox’s project_id. The token reads that
project through every endpoint below for a few hours, and nothing else; every route that would
change something answers 403 with the code sandbox_read_only. Tokens and reads are
rate-limited per client. The same example project opens in the console at
app.apsio.io/sandbox.
Endpoints
All paths start with/v1/projects/{projectId}.
Users are counted by install id. When an app turns the install id off, user numbers are
null and only the session numbers are given.
An issue’s id is its fingerprint: the exception type and the top in-app frames of the stack
once symbolicated. When an occurrence arrives before its symbols, symbolicated is false
and missing_debug_ids lists every image of its stack without symbols, system frameworks
included. The in-app images to upload come from
GET /v1/projects/{projectId}/releases/{release}/missing-symbols. After an upload, the
occurrence is symbolicated again and may move to another issue. grouping tells which rule
made the fingerprint: see Grouping.
In an issue’s latest occurrence, frames is the crashing thread symbolicated (function, file,
line, image, whether it is in your app), and raw_frames is the stack as the device sent it.
/issues takes kind (crash, error, anr, hang or abnormal_exit; every kind by
default) and sort (last_seen, occurrences or users, descending).
Vitals percentiles are exact and weighted by each span’s sample weight. TTFD is listed only for
screens where the app called reportFullyDrawn(): Apsio never substitutes TTID for it. A hang
rate counts sessions with at least one hang occurrence (iOS), an ANR rate sessions with an ANR
(Android).
A cohort is defined by the same filters as /sessions (at least one besides app_id, which
narrows both sides). For each value of a dimension, difference is its share in the cohort minus
its share in the other sessions, and lift their ratio; values are ranked by difference, and
only over-represented ones are listed. Shares from a few sessions are noisy: min_sessions
(default 2) leaves out rarer values, and a small cohort deserves a higher one. When the cohort or
the rest is empty, comparable is false, no values are listed and note says why. Feature
flags come from the evaluation records (feature_flag.key and feature_flag.result.variant)
and from the feature_flag.<name> attributes a session carries after a rotation; network types
from network.connection.type. A cohort covers at most 30 days, and so do vitals.
Performance and network
Requests are grouped by the endpoint template:url.template when the app or its HTTP library
sends it (without any query or fragment), otherwise the path of url.full with every
segment that looks like an identifier or personal data (ids, UUIDs, long hexadecimal, phone
numbers, tokens, email addresses, free text) replaced by {id}. So
GET /orders/1001 and GET /orders/1002 are one endpoint, GET /orders/{id}. A request
failed when it got a 5xx or no response at all (error.type set, no status); 4xx responses are
counted on their own, since many are expected. Request counts and percentiles are reweighted by
session sampling, as vitals are; network percentiles are estimates (t-digest), app start and
screen percentiles are exact. A method other than the standard ones is _OTHER.
/sessions and /cohorts/compare take an endpoint (endpoint_method, endpoint_host and
endpoint_template, with endpoint_failed=true for failed requests only) or a screen
(screen, with min_ttid_ms for slow loads only), so the sessions behind a failing endpoint
or a slow screen are one request away.
Each sample request and trace carries apm_url, the same trace in your APM, built from the
project’s APM link (Settings, or PATCH /v1/orgs/{orgId}/projects/{projectId}): an https URL
with a {trace_id} placeholder, for example
https://app.datadoghq.com/apm/trace/{trace_id}. Apps send traceparent only to the hosts you
list, so the trace id is the one your backend saw. MetricKit histograms come from every user’s
day, not from sampled sessions; points with other bucket bounds than a release’s most common
ones are counted in excluded_points and not added. Performance views cover at most 30 days.
Examples
Release health since October 1:Time ranges and pages
fromandtoare RFC 3339 times. The default range is the last 7 days (90 days for one issue, 30 days for a release comparison, 14 days for the daily series); a request may cover at most 90 days, and 30 days for session search, vitals and cohorts.- The daily series has one entry per UTC day. Without
fromit starts at midnight UTC 13 days beforeto, so it holds 14 days; the last day is partial (up toto), and so is the first whenfromis not at midnight. - A request that hits a query limit (time, memory or result size) gets
query_limitwith503or422: narrow the time range or pick an app. - Lists return
next_cursor. Pass it back ascursorfor the next page; it isnullon the last page.limitsets the page size.
Errors
Errors are JSON with a code and a message:invalid_request (400), unauthorized (401), forbidden (403) or not_found
(404).