It isn't working
Almost every report we get is one of four things, and the first one accounts for most of them. Work down this page in order. If none of it helps, email us with your app name and roughly when you ran it, we can see what arrived.
Nothing shows up at all
1. Your build isn't sending, by design. Debug and Simulator builds (Android: emulator and debuggable builds) do not send by default. This is the single most common cause. It is deliberate, your development noise should never land in production numbers, but it means a fresh integration looks broken until you opt in:
AppGlance.configure(apiKey: "glance_live_…", debug: true)
AppGlance.configure(this, "glance_live_…", debug = BuildConfig.DEBUG)
With that set, the build sends and the SDK also narrates every event to the
Xcode consolelogcat, under the tag AppGlance,
so you can see the send happen rather than guessing.
2. You're looking at the wrong scope.
Even when a debug build is sending, its events are tagged
simulator / debugemulator / debug
, and the dashboard opens on Live, which shows App Store traffic only. Your events are
arriving; you are filtering them out. Switch the scope selector to All.
This is worth internalising, because it is also the answer to "my TestFlight numbers vanished": TestFlight and Play beta are their own scope, kept apart from production on purpose.
3. The key belongs to a different app.
A write key is scoped to one app. Pasting the key from another app in your dashboard sends the events
successfully, to that other app. Check the key in Setup against the app you are
looking at. Keys shipped before 2026-08-15 (the old lud_… prefix) no longer exist at all; every
live key starts glance_live_.
4. You're on Android and the dependency never resolved.
app.appglance:appglance:1.0.0 is on Maven Central, and mavenCentral() is in a default
Android project's repositories, so it should just resolve. If Gradle can't find it, the usual cause is a
repositories block that was narrowed, a dependencyResolutionManagement block in
settings.gradle.kts overrides the per-project one. You do not need
mavenLocal(); if an older guide told you to add it, remove it. If the build succeeded, this isn't
your problem.
Every error the API can return
If you are using the HTTP API directly, or reading SDK logs, these are the complete
set. A 202 is success and carries {"accepted": n, "rejected": m}, a non-zero
rejected means those rows were malformed and dropped, and retrying will not help.
| Status | Body | What it means | Retry? |
|---|---|---|---|
| 401 | missing or malformed API key | No Authorization: Bearer header, or it isn't shaped like a key. | No, fix the header |
| 401 | unknown API key | Well-formed but not a key we know. Usually a rotated or deleted key, or a typo. | No, reissue in Setup |
| 400 | body is not JSON | The payload didn't parse. | No |
| 400 | body must be a JSON array of events | You sent a single object. The body is always an array, even for one event. | No |
| 400 | bad bundle id | /v1/icon only, the bundle id wasn't usable. | No |
| 413 | body too large | Over 256 KB. | Halve the batch and retry |
| 413 | too many events in one batch | Over 500 events. | Halve the batch and retry |
| 429 | too many requests | Rate limited. Note that repeated failed auth is rate limited by IP, so a loop retrying a bad key will start seeing this. | Yes, after a wait |
| 502 | storage unavailable | We accepted the request but the database didn't answer. Nothing was stored. | Yes, keep the batch |
| 500 | internal error | Our fault. Tell us. | Yes |
| 405 | POST only | You used GET on an endpoint that takes POST. | No |
| 404 | not found | No such path. | No |
The retry rule, in one line.
429, 408, 425, any 5xx, or no response at all → keep
everything and retry later. Any other 4xx → that batch will never be accepted, so drop it
rather than wedging the queue behind it forever. Both shipped SDKs already do this for you.
The numbers look wrong
Presence pings and user labels are not events.
Heartbeats (which is how "active right now" works), user.identify and user.reset
are free: they never count toward your quota, never appear in your bill, and are excluded from event counts
and charts. If you are comparing a number in your own logs against the dashboard, this is usually the gap.
Old events are gone, deliberately. Every plan has a history window, and events past it are purged nightly. A count over "all time" is a count over your retention window. Per-user lifetime totals and the retention curve are kept as separate lifetime counters, so they are not affected, which is why subtracting one from the other never gives a meaningful answer.
Over quota drops billable events, but never identity.
Past your monthly allowance, billable events stop being stored. user.identify and
user.reset keep flowing, so your user labels stay correct and nothing is silently mislabelled
when you upgrade. The Plan page shows where you are.
Country is empty. Country comes from the device's locale/region setting, never GPS and never the IP address, so a device with no region set has no country. An app can be switched to derive country from the connecting address instead; in that mode the SDK's value is ignored entirely and only the two-letter code is kept.
Still stuck
[email protected]. Include your app name and roughly when you ran the build, we can look up exactly what arrived and what we did with it, which is usually faster than guessing from this page. Please don't paste your write key into a public issue; mail reaches us privately.