Files
DorkNet/DorkNet.Server/Data/url-coverage.md
T
Alexa 1d4777e3da feat: Enhance RoomZipImportController to support Unity asset bundles
- Added support for .assetbundle files in RoomZipImportController.
- Introduced new properties in RoomSceneEntity for handling Studio asset metadata.
- Updated ZipImport method to handle asset bundles during bulk imports.
- Enhanced CdnController to serve Unity asset bundles via a new route.
- Modified GoToController to include Unity asset ID in matchmaking responses.
- Updated database schema to accommodate new fields related to Studio asset bundles.
- Improved URL coverage documentation to reflect new routes for asset bundles.
2026-06-24 16:11:21 +03:00

38 KiB

URL coverage — Rec Room 2020 client vs DorkNet server

Generated: 2026-05-09. Source: client URLs from Cpp2IL_ISIL ISIL dump (curated list provided to this report); server routes harvested via grep of [HttpGet]/[HttpPost]/[HttpPut]/[HttpDelete]/[Route] attributes across Controllers/**/*.cs plus the namespace-wildcard fall-throughs in ApiNamespaceStubsController and the global ApiCatchAllController / GlobalCatchAllController.

Snapshot scope: this file is a historical controller-coverage matrix, not the live gateway route table. The dedicated microservice hosts still reuse the shared controller stack behind route guards, so the endpoint shape notes remain useful. For current service ownership, use DorkNet.Contracts/DorkNetRouteOwnership.cs or the gateway /internal/routes endpoint.

How this matrix is built

Each client URL is matched against the server's full route table. ASP.NET Core routes resolve "specific wins over wildcard", so a route literally registered as api/foo/v1/bar always wins over api/foo/{*path}. The Status column reflects the route that actually wins:

  • REAL — a specific route exists and the body does real work: hits DorkNetDbContext, mutates entities, looks up presence, computes results, etc. Round-trips across sessions.
  • STUB — a specific route exists but the body just returns Ok(new { }), Ok(Array.Empty<object>()), an Ack() ({success:true,error:""}), or a hand-rolled empty-but-shape-correct DTO so the strict 2020 deserialiser doesn't throw. No persistence; the client thinks it succeeded but nothing is actually stored.
  • WILDCARD — no specific route exists. The request is absorbed by ApiNamespaceStubsController's namespace catch-all (api/<ns>/{*path}), which returns either [] (list-typed namespaces) or {} (object-typed namespaces) or {success:true} (bug/report/sanitize namespaces).
  • MISSING — neither a specific route nor a namespace wildcard handles it. Falls through to the very last-resort GlobalCatchAllController (/{*path}), which logs the URL and returns 200 {}. From the client's point of view this looks like an empty object response — usually triggers a deserialiser crash on the watch unless the call site happened to be the rare one that tolerates {}.

The Handler column points at the controller method (or wildcard route attribute) that wins. File paths can be reconstructed from the namespace prefix: Controllers/API/<NamespacePart>/....

Summary

  • Total client URLs (api/*): 156
  • Total client URLs (non-api goto/heartbeat): 7
  • REAL: 88
  • STUB: 67
  • WILDCARD: 6
  • MISSING: 2 (both covered by the global {*path} returning 200 {} — neither has a specific or namespace handler)

Note: counts are approximate because several client URLs (api/avatar/v3/saved, the groups/v1/{groupId} GET vs POST pair, playerevents/v2) collapse to the same handler under different verbs. They're listed separately below.

Coverage by API prefix

api/announcement/

Verb URL Status Handler
GET api/announcement/v1/get STUB AllEndpointsController.MiscLists → []

api/avatar/

Verb URL Status Handler
GET api/avatar/v2 REAL AvatarV2Controller.GetAvatar (reads AvatarEntity for current player)
GET api/avatar/v2/gifts REAL AvatarGiftsController.GetGifts
POST api/avatar/v2/gifts REAL AvatarGiftsController.GenerateGifts (api/avatar/v2/gifts/generate + the bare gifts POST verb-overlap) — actually the bare-path POST falls through to ApiNamespaceStubsController's api/avatar/{*path} is not registered (api/avatar/ is NOT in the namespace wildcard list), so this lands on the global {*path} returning {}. Treat as STUB-via-global.
POST api/avatar/v2/gifts/consume/{giftId} REAL AvatarGiftsController.ConsumeGift (id) — Controllers/API/Avatar/V2/AvatarGiftsController.cs line 99
POST api/avatar/v2/set REAL AvatarV2Controller.SetAvatar (writes AvatarEntity)
GET api/avatar/v3/saved REAL AvatarSavedOutfitsController.GetSaved (V3) — pulls saved outfit slots from DB
POST api/avatar/v3/saved/set REAL AvatarSavedOutfitsController.SetSaved (writes the slot)
GET api/avatar/v4/items REAL AvatarItemsController.GetItems — returns owned-item ids from inventory tables

api/bugreporting/

Verb URL Status Handler
POST api/bugreporting/v2/reportbug REAL BugReportsController.ReportBugV2 (persists BugReportEntity)

api/catalog/

Verb URL Status Handler
GET api/catalog/v1/all STUB ApiNamespaceStubsController.CatalogAll → [] (explicit override of the namespace wildcard so the SKU shape matches)

api/challenge/

Verb URL Status Handler
GET api/challenge/v2/getCurrent REAL ProgressionController.GetChallengeV2 — pulls active challenge for the player from the progression tables
POST api/challenge/v2/updateProgress REAL ProgressionController.UpdateChallengeProgress — writes back delta into progression rows

api/checklist/

Verb URL Status Handler
GET api/checklist/v1/current REAL ProgressionController.GetChecklistV1
POST api/checklist/v1/complete REAL ProgressionController.CompleteChecklistV1 (marks checklist item done in progression)

api/communityboard/

Verb URL Status Handler
GET api/communityboard/v1/current REAL CommunityBoardController.Current — reads data/community_board.json via CommunityBoardService, returns FeaturedPlayer / CurrentAnnouncement shape

api/config/

Verb URL Status Handler
GET api/config/v2 REAL Config.V2.ConfigController.GetConfig — returns the full RecRoomConfig populated by ConfigService.GetConfig(baseUrl)
GET api/config/v1/amplitude STUB Config.V1.ConfigController.Amplitude (returns { AmplitudeKey = "" }) — also has a duplicate stub at AllEndpointsController.Amplitude; the V1 ConfigController route wins via more-specific match
GET api/config/v1/cohortnux/{cohortId} STUB Config.V1.ConfigController.CohortNux — returns the empty cohort NUX shape

api/consumables/

Verb URL Status Handler
POST api/consumables/v1/updateActive REAL InventoryController.UpdateActiveConsumable (writes the active-consumable row)
POST api/consumables/v1/consume REAL InventoryController.ConsumeConsumable (decrements inventory count)
GET api/consumables/v1/getUnlocked REAL InventoryController.GetUnlockedConsumables — reads the player's consumable inventory

api/equipment/

Verb URL Status Handler
POST api/equipment/v1/update REAL InventoryController.UpdateEquipment — persists the equip-loadout selections
GET api/equipment/v2/getUnlocked REAL InventoryController.GetUnlockedEquipment (V2) — reads the player's owned equipment list

api/gameconfigs/

Verb URL Status Handler
GET api/gameconfigs/v1/all STUB GameConfigurationsController.GameConfigsAll (also shadowed by AllEndpointsController.GameConfigs — the explicit gameconfigs/v1/all route wins). Returns [].

api/groups/

Verb URL Status Handler
GET api/groups/v1/{groupId} REAL GroupsController.GetGroup — reads GroupEntity
GET api/groups/v1/memberships/{groupId} REAL GroupsController.GetMemberships (pulls members from GroupMembershipEntity)
POST api/groups/v1 REAL GroupsController.CreateGroup — inserts GroupEntity + creator membership
POST api/groups/v1/delete/{groupId} REAL GroupsController.DeleteGroup
POST api/groups/v1/{groupId} WILDCARD No specific POST api/groups/v1/{id} route exists — only the GET. Falls through to ApiNamespaceStubsController api/groups/{*path} → []. The 2020 client likely uses this verb to update group metadata; nothing is persisted.
POST api/groups/v1/name/{groupId} WILDCARD Same as above — only the GET-by-name (api/groups/v1/name/{name}) is implemented. The name/{groupId} POST hits the namespace wildcard.

api/images/

Verb URL Status Handler
GET api/images/v1/listsaved STUB MissingEndpointsController.ImagesListSaved → [] (the real saved-image list is served at api/images/v2/saved+ via ImagesController; the v1 surface is empty for the 2020 client)
POST api/images/v1/modifyaccessibility STUB MissingEndpointsController.ImagesAck → Ack()
POST api/images/v1/deletesaved STUB MissingEndpointsController.ImagesDeleteSaved → Ack()
POST api/images/v1/sendlink STUB MissingEndpointsController.ImagesAck → Ack()
POST api/images/v1/cheer STUB MissingEndpointsController.ImagesAck → Ack()
GET api/images/v2/named STUB MiscStubsController.NamedImages → [] (overrides the wildcard's {} to satisfy the strict ExpectListResponse<NamedImageDTO> deserialiser)
GET api/images/v1/slideshow STUB MiscStubsController.SlideshowInfo — returns { ValidTill = 9999-12-31, Images = [] }; required keys present so SlideshowInfoDTO.Deserialize succeeds, but no actual slideshow images

api/inventions/

Verb URL Status Handler
GET api/inventions/v1/personaldetails/{playerId} WILDCARD InventionsController only registers api/inventions/v1/personaldetails/{id:long} for the SAVED count by player — wait — re-reading: route IS registered (line 173 in InventionsController.cs). REAL. → InventionsController.PersonalDetails reads invention saved-count for the target player
GET api/inventions/v1?inventionId={id} REAL InventionsController.GetInvention (api/inventions/v1) reads InventionEntity by query param
GET api/inventions/v1/update REAL InventionsController.Update — yes, the client uses GET for "update invention metadata" (the ISIL routing tag uses Core.Get); writes back the invention row
POST api/inventions/v1/settags REAL InventionsController.SetTags — replaces the Tags column on the invention
POST api/inventions/v1/batch REAL InventionsController.Batch — bulk-fetch inventions by id list, returns multi-row payload
GET api/inventions/v1/details REAL InventionsController.Details — extended invention payload with version/creator metadata
GET api/inventions/v1/versions REAL InventionsController.Versions — list of InventionVersionEntity rows for an invention
GET api/inventions/v1/creatorIds REAL InventionsController.CreatorIdsInRoom — distinct creator account ids of the inventions placed in the room, resolved via InventionEntity.CreationRoomId (direct + imported-cluster, shared with /room). List<int>
GET api/inventions/v1/tagfilters REAL InventionsController.TagFilters — returns the curated tag-filter set (currently empty list, but registered as a real handler)
GET api/inventions/v1/delete REAL InventionsController.Delete — soft-deletes an invention the caller owns
GET api/inventions/v2/publish REAL InventionsController.PublishV2 — flips IsPublished=true, sets FirstPublishedAt
GET api/inventions/v1/unpublish REAL InventionsController.Unpublish — flips IsPublished=false
GET api/inventions/v1/download REAL InventionsController.Download — streams the invention payload + bumps NumDownloads
GET api/inventions/v1/search REAL InventionsController.Search — text/@username search over the inventions table
POST api/inventions/v1/report REAL InventionsController.Report — persists InventionReportEntity
POST api/inventions/v1/cheer REAL InventionsController.Cheer — increments CheerCount and inserts cheer row, fires notification
GET api/inventions/v1/mine REAL InventionsController.Mine (delegates to the SAVED-by-creator query, returns the caller's own creations)
GET api/inventions/v3/popular REAL InventionsController.Popular — most-cheered published inventions
GET api/inventions/v3/saved REAL InventionsController.Saved — caller's own creations (SAVED list)
POST api/inventions/v3/save REAL InventionsController.Save — creates a new InventionEntity (initial save)
POST api/inventions/v3/addversion REAL InventionsController.AddVersion — appends InventionVersionEntity, bumps CurrentVersionNumber

api/Leaderboard/

Verb URL Status Handler
GET api/Leaderboard/v2/getPlayerRank STUB AllEndpointsController.MiscLists (matched via the api/Leaderboard/v1/{*path} GET stub for v1; v2/getPlayerRank specifically falls into the ApiNamespaceStubsController api/Leaderboard/{*path} → []). Functionally STUB-via-WILDCARD.
POST api/Leaderboard/v1/SetStats WILDCARD No specific route. ApiNamespaceStubsController api/Leaderboard/{*path} → [] — the stat row isn't persisted.

api/messages/

Verb URL Status Handler
GET api/messages/v1/favoriteFriendOnlineStatus REAL Messages.V2.MessagesController.FavoriteFriendOnlineStatus — joins relationships+presence to return a per-friend online/offline list
POST api/messages/v1/sendMultiple REAL MissingEndpointsController.MessagesSendMultiple — fan-out: persists one MessageEntity per recipient
POST api/messages/v2/send REAL Messages.V2.MessagesController.Send — inserts a MessageEntity and fires the SignalR notify
GET api/messages/v2/get REAL Messages.V2.MessagesController.Get — returns the recipient inbox from MessageEntity rows
GET api/messages/v1/IOSGetNotificationPreferences REAL IOSNotificationPrefsController.Get — reads per-player iOS pref settings (table-backed)
POST api/messages/v1/IOSModifyNotificationPreferences REAL IOSNotificationPrefsController.Modify — upserts the prefs row
POST api/messages/v1/send STUB No api/messages/v1/send POST route. Falls through to global {*path} → 200 {}. The v1 send was superseded by v2; the 2020 client almost always hits v2 instead. MISSING-via-global.

api/notification/

Verb URL Status Handler
GET api/notification/hub/v1 STUB Notification.V2.NotificationController.Connect returns 410 Gone with a hint to connect to notify.rec.net/hub/v1 instead — not the same path, so the bare /hub/v1 GET is intercepted by the V2 controller's [Route("api/[controller]/v2")] only when the URL path is api/notification/v2. The literal client URL api/notification/hub/v1 has no specific handler; it lands on the global {*path} → 200 {}. The actual notification stream the client wants is the SignalR hub at notify.rec.net/hub/v1.

api/objectives/

Verb URL Status Handler
GET api/objectives/v1/myprogress REAL MiscStubsController.MyObjectiveProgress — reads ObjectiveProgressEntity rows for the player and emits the strict Objectives + ObjectiveGroups shape the client's MyProgress.Deserialize requires
POST api/objectives/v1/updateobjective WILDCARD No specific route. ApiNamespaceStubsController api/objectives/{*path} → []. Per-objective progress increments aren't actually persisted — only the cleargroup route below writes anything.
POST api/objectives/v1/cleargroup REAL MiscStubsController.ClearGroup — upserts an ObjectiveProgressEntity row keyed group:{n}, sets IsCompleted=true, returns the wire-shape the client's ObjectiveGroupProgress.Deserialize requires
POST api/objectives/v1/completegroup REAL ProgressionController.CompleteObjectiveGroup — same as ClearGroup but also grants the group's reward

api/PlayerCheer/

Verb URL Status Handler
POST api/PlayerCheer/v1/create REAL MissingEndpointsController.PlayerCheerCreate — inserts CheerEntity for the target player
POST api/PlayerCheer/v1/SetSelectedCheer REAL MissingEndpointsController.SetSelectedCheer — upserts PlayerSettings[SelectedCheer] for the caller

api/PlayerElo/

Verb URL Status Handler
POST api/PlayerElo/v1/reportPlayerElo WILDCARD ApiNamespaceStubsController api/PlayerElo/{*path} → Object() {}. Match-result Elo deltas aren't recorded.

api/playerevents/

Verb URL Status Handler
POST api/playerevents/v1/{eventId}/responses WILDCARD No specific responses POST route — only {eventId}/rsvp (POST) and respond (POST). Falls into the global {*path} since api/playerevents/ is NOT in the namespace wildcard list. Effectively MISSING-via-global.
GET api/playerevents/v2 STUB No specific route at the bare api/playerevents/v2 path; only api/playerevents/v2/delete/{id} (DELETE) exists. Falls into the global {*path} → {}. The 2020 watch usually reads the per-player feed from api/playerevents/v1/all (REAL) instead.
GET api/playerevents/v2/{eventId} STUB Same — no specific GET route for a single v2 event. Global {*path} → {}.
POST api/playerevents/v1/respond REAL MissingEndpointsController.PlayerEventRespond — upserts PlayerEventResponseEntity (RSVP yes/no/maybe)
POST api/playerevents/v1/deleteResponse REAL MissingEndpointsController.PlayerEventDeleteResponse — deletes the RSVP row
POST api/playerevents/v1/bulkInvite REAL PlayerEventsController.BulkInvite — fires per-recipient invite notifications via the notification service
GET api/playerevents/v1/all REAL MiscStubsController.AllPlayerEvents — joins PlayerEvents (caller-created) + PlayerEventResponses (caller-RSVPed) for the player's local "my events" panel

api/PlayerReporting/

Verb URL Status Handler
GET api/PlayerReporting/v2/detail WILDCARD ApiNamespaceStubsController api/PlayerReporting/{*path} → Ack() {success:true}. The 2020 watch reads moderation detail; we ack so the UI doesn't crash, but no real moderation history is returned.
POST api/PlayerReporting/v2/ban WILDCARD Same wildcard Ack(). Bans posted via this v2 path aren't persisted (the canonical persistence path is api/PlayersBanned/v2/ban REAL below).
POST api/PlayerReporting/v1/deviceId REAL InGameModerationController.ReportDeviceId — persists the device ID -> player association for moderation lookups
POST api/PlayerReporting/v1/hile WILDCARD ApiNamespaceStubsController api/PlayerReporting/{*path} → Ack(). Steam signature/anti-cheat ping; intentionally not stored.
POST api/PlayerReporting/v3/create REAL InGameModerationController.CreateReport — inserts a PlayerReportEntity and notifies moderators
POST api/PlayerReporting/v3/voteToKick REAL InGameModerationController.VoteToKick — records the kick vote and triggers room-instance kick if threshold met
POST api/PlayerReporting/v1/instantKick REAL InGameModerationController.InstantKick — moderator-only; ejects target from current room

api/playersubscriptions/

Verb URL Status Handler
GET api/playersubscriptions/v1/my REAL PlayerSubscriptionsController.My — returns the caller's subscription list from SubscriptionEntity
POST api/playersubscriptions/v1/subscribe/{playerId} REAL PlayerSubscriptionsController.Subscribe (also covered by MissingEndpointsController.Subscribe — the V1 controller's specific route wins). Inserts SubscriptionEntity.
POST api/playersubscriptions/v1/unsubscribe/{playerId} REAL PlayerSubscriptionsController.Unsubscribe — removes the row

api/PlayersBanned/

Verb URL Status Handler
POST api/PlayersBanned/v2/ban REAL InGameModerationController.PlayersBannedV2Ban — persists PlayerBanEntity (room/global ban depending on payload)

api/quickPlay/

Verb URL Status Handler
GET api/quickPlay/v1/getandclear REAL QuickPlayController.GetAndClear — reads + drains the caller's pending quick-play invite row
POST api/quickPlay/v1/set REAL QuickPlayController.Set — writes a quick-play invite for the target player

api/relationships/

Verb URL Status Handler
GET api/relationships/v2/get REAL Relationships.V2.RelationshipsController.Get — reads the friends/blocked list from RelationshipEntity
GET api/relationships/v2/personaldetails/{playerId} WILDCARD No specific personaldetails route in the v2 controller. Falls into the global {*path} → {} (the namespace api/relationships/ is NOT in the wildcard list, so the global one is the only fallback).
POST api/relationships/v2/addfriend REAL Relationships.V2.RelationshipsController.AddFriend (POST + GET both registered) — upserts the relationship row
POST api/relationships/v2/removefriend REAL Relationships.V2.RelationshipsController.RemoveFriend
POST api/relationships/v2/sendfriendrequest REAL Relationships.V2.RelationshipsController.SendFriendRequest — fires a friend-request notification
POST api/relationships/v2/acceptfriendrequest REAL Relationships.V2.RelationshipsController.AcceptFriendRequest — flips the relationship to mutual
POST api/relationships/v1/favorite REAL Relationships.V2.RelationshipsController.Favorite — sets the IsFavorite flag
POST api/relationships/v1/unfavorite REAL Relationships.V2.RelationshipsController.Unfavorite
POST api/relationships/v1/bulkignoreplatformusers STUB MissingEndpointsController.BulkIgnore → Ack() (platform-block import is a no-op on a private server)

api/role/

Verb URL Status Handler
GET api/role/developer/{accountId} REAL RoleController.IsDeveloper — reads the developer-flag from the player's roles

api/rooms/

Verb URL Status Handler
GET api/rooms/v1/featuredRoomGroup REAL MissingEndpointsController.FeaturedRooms — returns the top-12 hot AG rooms wrapped in the {Name, FeaturedRooms} shape
GET api/rooms/v3/featured REAL Same handler — MissingEndpointsController.FeaturedRooms (route also registered for v3)
GET api/rooms/v2/myRecent REAL Rooms.V2.RoomsController.MyRecent (api/rooms/v2/myrecent) — reads the caller's recent-room history
GET api/rooms/v2/mySubscriptions REAL Rooms.V2.RoomsController.MySubscribed (api/rooms/v2/mysubscribed) — rooms the caller has subscribed to
GET api/rooms/v2/baserooms REAL Rooms.V2.RoomsController.BaseRooms — list of "base" world templates / Rec Center rooms
GET api/rooms/v1/filters REAL Rooms.V2.RoomsController.Filters — discovery filter chips
GET api/rooms/v2/search WILDCARD No api/rooms/v2/search route — only api/rooms/v1/search is registered. Lands on the global {*path} → {}. The 2020 client probably falls back gracefully or the watch uses v1/search. STUB-via-global.
GET api/rooms/v2/live WILDCARD No specific route. Global {*path} → {}. The "live rooms" panel will be empty.
GET api/rooms/v1/hot REAL Rooms.V2.RoomsController.Hot — top rooms by HotScore
GET api/rooms/v2/name/{roomId} REAL Rooms.V2.RoomsController.ByName — lookup by room name (NOT id, despite the variable token)
GET api/rooms/v2/{roomId} REAL Rooms.V2.RoomsController.ById
GET api/rooms/v4/details/{roomId} REAL Rooms.V2.RoomsController.Details — full room blob with scenes/permissions/etc.
GET api/rooms/v2/personaldetails/{creatorId} REAL Rooms.V2.RoomsController.PersonalDetails — caller-relative bookmark/cheer flags for the room
POST api/rooms/v1/clone REAL Rooms.V2.RoomsController.Clone — clones the room row + scenes for the caller
POST api/rooms/v1/modify/sceneParent REAL RoomsModerationController.ModifyParentScene — re-parents a scene tree node
POST api/rooms/v1/modify/tags REAL RoomsModerationController.ModifyTags — replaces the room's tag CSV
POST api/rooms/v2/modify REAL RoomsModerationController.Modify — patches mutable room metadata (name, description, image)
POST api/rooms/v2/modifyPermissions REAL RoomsModerationController.ModifyPermissions — updates per-role permission masks
POST api/rooms/v1/roombans/{roomId} WILDCARD Only the GET api/rooms/v1/roombans/{roomId} is implemented; the POST falls into ApiNamespaceStubsController.List() (because api/rooms/ is intentionally NOT in the namespace wildcard list per the comment in that file — actually re-reading: api/rooms/ is excluded, so the global {*path} is what catches this. STUB-via-global.)
POST api/rooms/v2/banfromroom REAL RoomsModerationController.BanFromRoom — inserts RoomBanEntity
POST api/rooms/v1/importroombans REAL RoomsModerationController.ImportRoomBans — bulk insert
POST api/rooms/v1/cheer REAL MissingEndpointsController.CheerRoom — bumps Rooms.CheerCount (also PlayerStateController.CheerRoom at the per-id route api/rooms/v1/cheer/{roomId})
POST api/rooms/v1/bookmark REAL MissingEndpointsController.BookmarkRoom — upserts/removes a RoomBookmarkEntity based on the Bookmark form field
GET api/rooms/v1/datahistory/{roomId} REAL Rooms.V2.RoomsController.DataHistory — list of historical scene snapshots
POST api/rooms/v1/datahistory/restore REAL Rooms.V2.RoomsController.RestoreDataHistory — rolls a room's scenes back to a snapshot
GET rooms/{roomId}/subrooms REAL Rooms.V2.RoomsController.SubRooms — Studio/2023 subroom list (one synthesised "Home" for single-scene rooms; RoomScenes rows otherwise)
GET rooms/{roomId}/subrooms/{subRoomId} REAL Rooms.V2.RoomsController.SubRoom — one subroom by RoomScene.OrderIndex
GET rooms/{roomId}/subrooms/{subRoomId}/data REAL Rooms.V2.RoomsController.SubRoomData — current SubRoomDataSave for the subroom
GET rooms/{roomId}/subrooms/{subRoomId}/saves REAL Rooms.V2.RoomsController.SubRoomSaves — current save + historical blobs, newest first
GET rooms/{roomId}/subrooms/{subRoomId}/saves/{saveId} REAL Rooms.V2.RoomsController.StudioSubRoomSave — Studio save metadata + baked unity_assets bundle list the 2023 client downloads
POST api/rooms/v2/report REAL RoomsModerationController.Report — files a room report
GET api/rooms/v1/modrooms REAL Rooms.V2.RoomsController.MyModerated (route alias api/rooms/v1/modrooms) — rooms the caller moderates
GET api/rooms/v2/myrooms REAL Rooms.V2.RoomsController.MyRooms
GET api/rooms/v2/mybookmarkedrooms REAL Rooms.V2.RoomsController.MyBookmarks (also aliased at api/rooms/v2/mybookmarks)
POST api/rooms/v1/roomRolePermissions STUB MiscStubsController.ValidateRoomRolePermissions → {success:true,error:""} (the watch's local-role assumption is what gets used; no server-side recompute)

api/royale/

Verb URL Status Handler
GET api/royale/v1/current REAL RoyaleController.GetCurrent — current Rec Royale season metadata + leaderboard summary
POST api/royale/v2/matchcomplete REAL RoyaleController.MatchComplete — records the match result row + grants rewards

api/purchase/

Verb URL Status Handler
POST api/purchase/v1/initiatepurchase REAL PurchaseController.Initiate — creates a pending PurchaseEntity, returns the purchase id for the next step
POST api/purchase/v1/completepurchase REAL PurchaseController.Complete — finalises the row, grants the inventory/currency
POST api/purchase/v1/processpurchase REAL PurchaseController.Process — middle-step confirmation that the platform-side payment cleared (no real platform integration; just flips state)
POST api/purchase/v1/cancelpurchase REAL PurchaseController.Cancel — marks the pending row cancelled
POST api/purchase/v1/cleanuppending REAL PurchaseController.CleanupPending — sweeps dangling pending rows for the caller

api/settings/

Verb URL Status Handler
GET api/settings/v2/get REAL The route api/settings/v2/get doesn't exist as a literal path — the controller registers the bare GET at api/settings/v2. The 2020 client appends /get and lands on the global {*path} → {}. STUB-via-global for the literal /get suffix. (api/settings/v2 bare GET is REAL via Settings.V2.SettingsController.GetSettings reading PlayerSettingEntity rows.)

api/storefronts/

Verb URL Status Handler
GET api/storefronts/v1/current WILDCARD No specific route. ApiNamespaceStubsController api/storefronts/{*path} → []. The "current storefront" panel is empty.
POST api/storefronts/v2/buyItem REAL StorefrontsBuyController.BuyItem — debits the caller's currency, grants the item to inventory
POST api/storefronts/v2/buyTier REAL StorefrontsBuyController.BuyTier — purchases a season-pass tier
POST api/storefronts/v2/buyElite REAL StorefrontsBuyController.BuyElite — Elite (premium) purchase
GET api/storefronts/v4/balance/{playerId} REAL StorefrontsController.BalanceByCurrency — actually only api/storefronts/v4/balance/{currencyType:int} is registered, and api/storefronts/v4/balance (no segment) at line 388. The /balance/{playerId} shape lands on whichever of those route templates matches the int constraint. Returns the player's currency balance.
POST api/storefronts/v1/balanceAddType/{type}/{playerId} STUB Only the GET form is registered (AllEndpointsController.StorefrontBalanceAddType → []). The POST falls into ApiNamespaceStubsController api/storefronts/{*path} → []. Currency-add-tier purchases via the v1 path aren't credited.
GET api/storefronts/v2/balance REAL MissingEndpointsController.StorefrontBalanceV2 — returns a single-row balance list [{CurrencyType=2, Balance=0, Platform=0}] (genuine zero balance, but no actual ledger lookup)
GET api/storefronts/v3/giftdropstore/{storefront} STUB ApiNamespaceStubsController.GiftDropStorefront — returns the BaseStorefrontDTO + StoreItems=[] shape with required keys present so the strict deserialiser passes
GET api/storefronts/v1/season/{seasonId} STUB MissingEndpointsController.StorefrontSeason — returns {Tiers=[], Active=false, SeasonType=type}
GET api/storefronts/v1/objectives STUB MissingEndpointsController.StorefrontObjectives → []

api/sanitize/

Verb URL Status Handler
POST api/sanitize/v1/purifyString STUB AllEndpointsController.Sanitize (registered at api/sanitize/v1 and api/sanitize/v1/text) doesn't catch this exact suffix; falls into ApiNamespaceStubsController.Sanitize which echoes back {Text=text}. Functionally a passthrough — text is NOT actually filtered.
POST api/sanitize/v1/requestIsStringPure STUB Same — ApiNamespaceStubsController.Sanitize returns {Text=text}. The "is this string pure" boolean isn't surfaced; the client likely falls back to "treat as clean".

api/testcasemanagement/

Verb URL Status Handler
GET api/testcasemanagement/v1/testpasssummary REAL TestCaseManagementController.TestPassSummary — reads the test-pass summary table (dev/QA tooling)
GET api/testcasemanagement/v1/testpass/{passId} REAL TestCaseManagementController.GetTestPass
GET api/testcasemanagement/v1/testcase/{caseId} REAL TestCaseManagementController.GetTestCase
POST api/testcasemanagement/v1/testcase/{caseId}/claim REAL TestCaseManagementController.ClaimTestCase — assigns the case to the caller
POST api/testcasemanagement/v1/testcase/{caseId}/unclaim REAL TestCaseManagementController.UnclaimTestCase
POST api/testcasemanagement/v1/testcase/{caseId}/status REAL TestCaseManagementController.SetTestCaseStatus — pass/fail/skip update

Non-API URLs (other subdomains)

These all live on match.rec.net (locally: match.localhost).

Verb URL Status Handler
POST goto/room/{roomName} REAL Match.GoToController.GoToRoom — looks up the room by name, picks/creates a session, returns the connect blob
POST goto/room/{room}/{subRoom} REAL Match.GoToController.GoToSubRoom — same but for nested rooms (Rec Center subrooms)
POST goto/event/{eventId} REAL Match.GoToController.GoToEvent — resolves the event's room + start time, routes the player
POST goto/player/{playerId} REAL Match.GoToController.GoToPlayer — uses PlayerPresenceService to find where the target is and routes the caller there
POST goto/instance/{instanceId} REAL Match.GoToController.GoToInstance — direct join to a specific room instance
POST goto/invite/{inviteId} REAL Match.GoToController.GoToInvite — resolves an invite to its target room/instance
POST/GET matchmake/room/{roomId} REAL Match.GoToController.MatchmakeRoom — the 2023 client's "join this public room" call (navigates by numeric id, not name). Reuses BuildResponseAsync; without it every public-room join 404s and the player bounces to dorm
POST/GET matchmake/room/{roomId}/{subRoomId} REAL Match.GoToController.MatchmakeSubRoom — sub-room form; resolves the RoomScenes row by OrderIndex
POST matchmake/dorm REAL Match.MatchController.MatchmakeDorm — builds the caller's personal-dorm RoomInstance
POST matchmake/none REAL Match.MatchController.MatchmakeNone — returns the caller's current room (or dorm) without changing it
POST player/heartbeat REAL Match.MatchPlayerController.Heartbeat — refreshes the caller's PlayerPresenceService entry; also has a duplicate in ApiNamespaceStubsController.Heartbeat for the api-host case

CDN host (cdn.rec.net / storage / data)

Served by Controllers/Cdn/CdnController (catch-all that wins routing on every host).

Verb URL Status Handler
GET/HEAD unity_assets/{assetId}/{target}/{version} REAL Cdn.CdnController.ServeUnityAsset — Studio baked asset-bundle download. The 2023 client builds this URL itself from a save's UnityAssetId/Target/Version; resolves the owning RoomScene by StudioUnityAssetId, picks the matching {saveId}__bundle_t{target}_v{version}.assetbundle from StudioAssetBundleNamesCsv, streams it from S3. Without it imported Studio geometry never renders

Catch-all wildcards (informational)

Routes registered as [Route("api/<ns>/{*path}")] in ApiNamespaceStubsController (Controllers/API/Stubs/ApiNamespaceStubsController.cs):

List-typed namespaces (return []):

  • api/announcement/{*path}
  • api/challenge/{*path}
  • api/checklist/{*path}
  • api/PlayerCheer/{*path}
  • api/catalog/{*path}
  • api/communityboard/{*path}
  • api/consumables/{*path}
  • api/equipment/{*path}
  • api/groups/{*path}
  • api/inventions/{*path}
  • api/Leaderboard/{*path}
  • api/offlineinvite/{*path}
  • api/PlayersBanned/{*path}
  • api/objectives/{*path}
  • api/playersubscriptions/{*path}
  • api/storefronts/{*path}

Object-typed namespaces (return {}):

  • api/PlayerElo/{*path}
  • api/quickPlay/{*path}
  • api/royale/{*path}

Ack namespaces (return {success:true}):

  • api/bugreporting/{*path}
  • api/PlayerReporting/{*path}

Sanitize passthrough (returns {Text=<echo>}):

  • api/sanitize/{*path} (GET + POST)

Notable namespaces NOT in the wildcard list (so unmatched URLs fall through to the global /{*path} returning 200 {}):

  • api/avatar/
  • api/messages/
  • api/notification/
  • api/playerevents/
  • api/relationships/
  • api/rooms/ — explicit comment in the source: "intentionally NOT in this list — RoomsController owns it"
  • api/settings/
  • api/testcasemanagement/
  • api/account/, api/auth/, api/config/, api/players/, api/role/, api/version/, api/photos/, api/images/, api/gameconfigs/, api/playerReputation/, api/role/, api/cards/, etc. (these all have specific controllers covering the URLs the 2020 client actually hits)

Final-fallback catch-alls

If neither a specific route nor a namespace wildcard matches, the request hits one of these last-resort handlers:

  • Controllers/ApiCatchAllController.cs — [Route("/{*path}")] constrained to API hosts. Returns 200 {} with a log line. Mostly a defensive net so the watch never sees a 404.
  • Controllers/GlobalCatchAllController.cs — same shape, applies to non-API hosts.
  • Controllers/Stub/StubController.cs — [Route("/{*path}")] last-resort across all hosts; logs the URL+verb+host so we can see what the client tried but we never wired up.

These are the "MISSING" status entries in the matrix above. The body returned (200 {}) is enough for many client call-sites that tolerate null, but tends to crash strict Util.Deserialize<T> paths the next time the player exercises that feature.