Category: webapp (catalog read)
Entity: PlanetActivity (structs.planet_activity)
Base URL: ${webappBaseUrl} (default: http://localhost:8080, public guild webapp: http://crew.oh.energy)
Last Updated: September 14, 2026
Per-planet activity log — raids, attacks, builds, defender changes, struct health updates, etc. Categories include raid_status, block_raid_start, shield_change, struct_attack, struct_health, fleet_arrive, fleet_depart, and others; see .cursor/skills/structs-streaming/SKILL.md for the GRASS-side category names. block_raid_start records the block height at which a raid began, and shield_change records planet shield adjustments. Use this REST surface for historical browsing; use GRASS for real-time reaction.
The per-player routes read structs.planet_activity_player (attribution written at insert time, ownership as-of-event). seq is a per-planet counter and is not a cursor across planets — use ?since_height= (block_height > :since_height) as the incremental high-water mark. since_seq is 400 since_seq_unsupported on the player feed.
| Method | Path | Description | Auth Required |
|---|---|---|---|
| GET | /api/planet-activity/all/page/{page} |
List every planet activity row | Yes |
| GET | /api/planet-activity/planet/{planet_id}/page/{page} |
List activity for a planet | Yes |
| GET | /api/planet-activity/category/{category}/page/{page} |
List activity by category | Yes |
| GET | /api/planet-activity/player/{player_id}/page/{page} |
Activity attributed to a player (?category=, ?role=, ?since_height=, ?order=) |
Yes |
| GET | /api/planet-activity/stats |
30-day counts by category (?category=, ?bucket=1h\|1d) |
Yes |
| GET | /api/planet-activity/player/{player_id}/stats |
Per-player daily counts by category and role | Yes |
Bucketed stats for charts: analytics.md.
/api/planet-activity/all/page/{page}webapp-planet-activity-all| Name | Type | Required | Format | Description |
|---|---|---|---|---|
page |
integer | Yes | \d+ |
Page number |
?since_height= is not supported here (400 since_height_unsupported). Track one seq per planet, or scan globally on block_height.
/api/planet-activity/planet/{planet_id}/page/{page}webapp-planet-activity-by-planet| Name | Type | Required | Format | Description |
|---|---|---|---|---|
planet_id |
string | Yes | planet-id | Planet identifier |
page |
integer | Yes | \d+ |
Page number |
/api/planet-activity/category/{category}/page/{page}webapp-planet-activity-by-category| Name | Type | Required | Format | Description |
|---|---|---|---|---|
category |
string | Yes | – | Activity category (e.g. raid_status, block_raid_start, shield_change, struct_attack, struct_health, fleet_arrive, fleet_depart) |
page |
integer | Yes | \d+ |
Page number |
/api/planet-activity/player/{player_id}/page/{page}webapp-planet-activity-by-playerPer-player feed via structs.planet_activity_player joined back to the parent planet_activity row. Dual-role rows on the same parent are collapsed with DISTINCT ON (block_height, time, planet_id, seq) unless role is set.
| Name | Type | Required | Format | Description |
|---|---|---|---|---|
player_id |
string | Yes | player-id | Player identifier |
page |
integer | Yes | \d+ |
Page number |
category |
string | No | allowlisted | Restrict to one category |
role |
string | No | allowlisted | attacker, target, owner, planet_owner, defender, protected, fleet_owner |
since_height |
integer | No | int | Filter block_height > since_height. Filter, not a keyset — OFFSET still applies |
order |
string | No | asc | desc |
Default desc (block_height, then time, planet_id, seq) |
include_total |
0 | 1 |
No | – | When role is omitted, total is count(DISTINCT (time, planet_id, seq)) |
Allowlisted categories: struct_attack, raid_status, fleet_arrive, fleet_depart, struct_status, struct_health, struct_move, struct_block_build_start, struct_block_ore_mine_start, struct_block_ore_refine_start, struct_defense_add, struct_defense_remove, shield_change, block_raid_start. Anything else is 400 category_invalid. Unknown role is 400 role_invalid. ?order= other than asc/desc is 400 order_invalid.
Rows: time, seq, planet_id, block_height, category, detail (JSON string) plus detail_json (parsed object, or null if detail is not JSON).
/api/planet-activity/statswebapp-planet-activity-statsOptional category, bucket (1h or 1d; omit = daily). Last 30 days from the planet_activity_hourly / planet_activity_daily continuous aggregates. Rows: bucket, category, count.
/api/planet-activity/player/{player_id}/statswebapp-planet-activity-player-statsDaily counts from structs.planet_activity_player_daily. Optional category, role (same allowlist as the feed). Last 30 days. Rows: bucket, category, role, count.
?bucket=1h is 400 bucket_invalid — this endpoint is daily only.
Responses use the shared envelope ({ "success": true, "errors": {}, "data": [ ...rows ] }); catalog rows are returned directly in data as a flat array with a default page size of 100 — if data.length === 100, request the next page. See protocols/webapp-api-protocol.md.