Analytics API
Analytics on Appsinity is event rows plus one aggregate read. The counters you see on a listing are recomputed from the event table on demand and written back onto your listing document.
Open Appsinity StudioReading your numbers
- The listing identifier may be sent as
listingId,appIdorgameId; all three are accepted. collectionmust be exactlyappsto read the apps side; anything else resolves togames.- Reading this endpoint has a side effect: it recomputes
downloads,visits,ratingAvg,ratingCountandreviewCountonto the listing document. - Sources are grouped and ordered by count, limited to twenty rows; country breakdowns to fifty.
GET /api/listing-analytics?collection=games&listingId=my-game
Authorization: Bearer <studio token>
200 {
"ok": true,
"downloads": 412,
"visits": 9080,
"reviewsSummary": { "ratingAvg": 4.3, "ratingCount": 27 },
"sources": [ { "source": "search", "count": 3110 }, ... ], // top 20
"countries": [ { "country": "IN", "event_type": "visit", "count": 2400 }, ... ] // top 50
}
400 { "error": "Missing listing id" }
500 { "error": "Could not load listing analytics" }Recording events
| Call | Body | Effect |
|---|---|---|
POST /api/listing-traffic | collection, listingId, eventType, optional source and country | One listing_events row |
POST /api/listing-play | collection, listingId, playSeconds | One web_game_sessions row |
GET /api/listing-play-stats | query identifier only | Session totals for a listing |
GET /api/listing-traffic | public read | The counter a listing page prints |
POST /api/listing-traffic
{ "collection": "games", "listingId": "my-game",
"eventType": "visit", "source": "search", "country": "IN" }
// eventType is normalised: only the literal "visit" counts as a visit;
// every other value is recorded as "download"
POST /api/listing-play
{ "collection": "games", "listingId": "my-game", "playSeconds": 340 }
// playSeconds is clamped at 0 and rounded; 0 makes the call a no-op that still answers ok
GET /api/listing-play-stats?collection=games&listingId=my-game
200 { total_sessions, avg_play_seconds, total_play_seconds }Ratings and reviews
- Rating is clamped to the range 1 to 5, so a submitted 9 becomes 5 and a 0 is refused as missing.
- Text is capped at 2,000 characters, deviceId at 200 and source at 120.
- One row per (collection, listing, uid): a second submission updates the first rather than adding to the average.
- The author name shown is resolved from the store profile, falling back to the token's display name, then the email, then "Appsinity user".
- The read is limited to the hundred most recently updated rows; the average covers all of them.
GET /api/listing-reviews?collection=games&listingId=my-game
200 {
"ok": true,
"reviews": [ { id, uid, userName, rating, text, createdAt, updatedAt } ], // last 100
"summary": { "ratingAvg": 4.3, "ratingCount": 27 }
}
POST /api/listing-reviews Authorization: Bearer <player token>
{ "collection": "games", "listingId": "my-game", "rating": 4,
"text": "...", "deviceId": "...", "source": "store" }
200 the stored row
400 { "error": "Missing listing id" } | "Choose a rating first"
401 { "error": "Sign in to write a review" }
404 { "error": "Listing not found" }Public reads
Anything a player can see is available without a credential, and these are the calls worth building against.
| Call | Auth | Returns |
|---|---|---|
GET /api/catalog?type=game&limit=100 | None | items and games, published and visible rows only |
GET /api/listings/:id | None | One listing document |
GET /api/listing-traffic?... | None | The listing's counter |
GET /api/developers/:id | None | The public developer page |
GET /api/streams/scheduled | None | Upcoming streams |
GET /api/docs/:collection | Owner token | Your own documents, newest first |
published without a pre-registration flag, so a draft, an unpublished or a pre-register row is absent from the read entirely, see Store visibility and status.Ad events
Ad measurement lives on the same service and follows the same shape: one unit row plus a counter event per interaction.
POST /api/ads/:id/impression
POST /api/ads/:id/click
POST /api/ads/:id/completed
POST /api/ads/:id/skipped
PATCH /api/ads/:id/rate // eCPM in cents, 0 .. 100000
GET /api/ads/active
GET /api/ads/statsFrequently asked questions
Can I get analytics per day?
No. The response is a lifetime aggregate: totals, top twenty sources and top fifty country-and-type pairs. There is no date parameter and no time series.
Is there an export?
No CSV or JSON export endpoint exists in Studio. Calling this endpoint yourself and storing the response is the only route.
Why did my download count change when I only opened the page?
Because reading analytics recomputes and writes the counters back onto the listing document. The number is derived from event rows, and the read refreshes the stored copy.
Do ad and play numbers reach the same table?
No. Traffic in listing_events, play sessions in web_game_sessions, ratings in listing_reviews, ad counters on the ad unit rows.
Developer publishing support: [email protected]
General support: [email protected]
Partnerships: [email protected]
APPSINITY