Quick start
How to start sending session_start and session_end events without an SDK.
appmetrica.online receives system events from your apps over HTTP. There is no SDK: the app itself
sends session_start when it comes to the foreground and session_end when it goes to the
background. Every report is built from these two events: active and new users, sessions and their
duration, retention, realtime and profiles.
1. Create an app
In the portfolio, click Add app and choose the platforms and the time zone. An app is one
product: if it ships on iOS, Android and the web, check all three platforms instead of creating
three apps. The time zone sets the day boundaries in reports; if you change it later, past days are
recalculated in the new zone. Each platform gets its own write key
pk_…, and the Integration tab shows the keys and ready-made snippets for every platform. You
can add platforms later in the app settings.
The write key is not a secret: it is embedded in the app and visible to anyone who unpacks it. Rate limits protect against abuse, and a compromised key can be revoked and replaced with a new one.
2. Send your first event
Test the key from a terminal. Put it in the AM_PK variable:
export AM_PK="pk_…"and send a single event. The examples in these docs use the placeholder domain
analytics.example.com: replace i.analytics.example.com with the ingest address of your
installation, which you'll find on the Integration tab.
NOW=$(date -u +"%Y-%m-%dT%H:%M:%S.000Z")
curl -sS https://i.analytics.example.com/v1/events \
-H "Content-Type: application/json" \
-H "X-App-Key: $AM_PK" \
--data-binary @- <<JSON
{
"sent_at": "$NOW",
"context": { "platform": "ios", "app_version": "1.0.0", "environment": "development" },
"events": [
{
"insert_id": "$(uuidgen | tr A-Z a-z)",
"name": "session_start",
"timestamp": "$NOW",
"install_id": "$(uuidgen | tr A-Z a-z)",
"session_id": "$(uuidgen | tr A-Z a-z)"
}
]
}
JSONA 200 response arrives only after the events are written to storage. accepted is the number of
accepted events, rejected lists rejected events with a reason code, and warnings lists events
that were accepted with a correction:
{ "accepted": 1, "rejected": [], "warnings": [], "server_time": "2026-10-14T09:15:02.318Z" }environment: "development" keeps test events apart from production ones: by default, reports show
production only.
3. Add sending to your app
Analytics is created once per process, and a session follows the lifecycle of the whole app, not of an individual screen. Entry points and hooks for each platform are collected in Where to initialize analytics.
Then pick the recipe for your platform. Each one covers storing install_id, the session lifecycle
and an offline queue:
- iOS (Swift) — URLSession, Keychain, SwiftUI and UIKit;
- Android (Kotlin) — HttpURLConnection, SharedPreferences, ProcessLifecycleOwner;
- Web (JavaScript) —
navigator.sendBeacon,localStorage; - React Native (TypeScript) —
fetch, Keychain and AsyncStorage,AppState; - Flutter (Dart) —
package:http, a file-backed queue,WidgetsBindingObserveron phones and desktop; - Kotlin Multiplatform — Ktor,
expect/actual, a shared tracker for Android and iOS; - Bots and backends — Telegram bots, server-side sessions,
context.ip; - curl — every request from a terminal: batches, late events, gzip, retries, Read API;
- Go — a client with retries for backends, CLIs and cron jobs.
Integrating with an AI coding agent? Point it to /llms.txt: integration instructions
for every platform in one file. /llms-full.txt holds these docs as a single
Markdown file.
4. Check the data
The Integration tab shows the latest events and the rejection errors by app version. The event shows up on the Realtime tab within 10 seconds, and the Audience tab shows DAU and new users for the selected period.
Event platform
One app accepts events from all of its platforms. The platform of an event is determined in this order:
context.platformof the event or the batch; the event context overrides the batch context;- the platform of the write key the batch was sent with;
- the app's only platform, if it has just one;
- otherwise
other, and the response carries aplatform_missingwarning with the event index.
Keys created together with the app are bound to their platforms: embed each build's own platform key,
and the platform is resolved even without context.platform. In the settings you can issue an
Any platform key — it's handy when one codebase is built for several platforms (Flutter, React
Native, Kotlin Multiplatform). With such a key, pass context.platform in every batch. An event
with a platform that isn't in the app's set is accepted as is.
By default, reports are aggregated and count people across platforms — see People on multiple platforms.
TVs, watches and tablets
The set of platforms is fixed: ios, android, web, macos, windows, linux, bot, server
and other. Any other value, such as tvos, is rejected with
invalid_field. A device without a platform of its own is sent
with the platform of its OS family, its own device_type and os_name, and the key of the family
platform: the iOS key for Apple TV, the Android key for Android TV.
| Device | platform | device_type | os_name |
|---|---|---|---|
| iPhone | ios | phone | iOS |
| iPad | ios | tablet | As reported by the system: iPadOS or iOS |
| Apple TV | ios | tv | tvOS |
| Apple Watch | ios | watch | watchOS |
| Apple Vision Pro | ios | unknown | visionOS |
| Android phone | android | phone | Android |
| Android tablet | android | tablet | Android |
| Android TV, Google TV, Fire TV | android | tv | Android TV |
| Wear OS watch | android | watch | Wear OS |
| Web app on a smart TV (Tizen, webOS) | web | tv | Tizen, webOS |
| Roku and other TV platforms | other | tv | The OS name, for example Roku OS |
Reports filter by os_name, not by device_type, which is why TVs and watches get an OS name of their
own: the Android TV filter opens the reports for TVs only, and the breakdown by device type shows
each type's share. On Apple devices, UIDevice.current.systemName already returns tvOS or
visionOS. How to detect the device type:
- Swift:
UIDevice.current.userInterfaceIdiom, where.padis a tablet and.tvis a TV, as in the iOS recipe; - Kotlin:
resources.configuration.uiMode and Configuration.UI_MODE_TYPE_MASK, whereUI_MODE_TYPE_TELEVISIONis a TV andUI_MODE_TYPE_WATCHis a watch, as in the Android recipe; - React Native:
DeviceInfo.getDeviceType()fromreact-native-device-inforeturnsTv; - Flutter: on Android TV,
systemFeaturesofAndroidDeviceInfofromdevice_info_pluscontainsandroid.software.leanback; - Web: the user agent of TV browsers contains
SMART-TV,TizenorWeb0S.
What to read next
- Where to initialize analytics — entry points and lifecycle hooks.
- Identity —
install_id,user_idand how people are counted. - Sessions — when to start and end a session.
- Time and the offline queue — skewed clocks and late events within 7 days.
- Errors and retries — response codes and what to do about each one.
- Privacy and GDPR — what is stored and how to delete data.
- Ingestion API — the full batch format.
- Read API — the same reports for scripts, with an
sk_…key.