A reverse-engineered system design of Slack's web application, built from live network traffic analysis of the authenticated Enterprise Grid experience. 200+ API calls captured across boot, search, messaging, reactions, and navigation. Every backend service named.
┌─────────────────────────────────────────────────────────────────────────────┐
│ BROWSER (Gantry v2 SPA) │
│ │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ ┌───────────────┐ │
│ │ React UI │ │ Quip/Canvas │ │ Real-time │ │ Sonic │ │
│ │ (Gantry v2) │ │ Engine │ │ (WebSocket) │ │ Boot Client │ │
│ └──┬───────────┘ └──┬───────────┘ └──────┬───────┘ └───────┬───────┘ │
│ │ │ │ │ │
└─────┼──────────────────┼─────────────────────┼──────────────────┼───────────┘
│ │ │ │
│ │ │ │
┌─────┼──────────────────┼─────────────────────┼──────────────────┼───────────┐
│ EDGE │ │ │ │
│ ▼ ▼ │ │ │
│ ┌──────────────────────────────────┐ │ │ │
│ │ Envoy Edge Proxy │ │ │ │
│ │ (pdx / iad regions) │ │ │ │
│ └──┬──────────────┬────────────────┘ │ │ │
└─────┼──────────────┼────────────────────────┼──────────────────┼───────────┘
│ │ │ │
┌─────┼──────────────┼────────────────────────┼──────────────────┼───────────┐
│ BACKEND SERVICES │ │ │ │
│ │ │ │ │ │
│ ▼ ▼ ▼ ▼ │
│ ┌─────────┐ ┌──────────┐ ┌──────────────────┐ ┌───────────────────┐ │
│ │Workspace│ │ Flannel │ │ ListenWeb │ │ HHVM Callbacks │ │
│ │ API │ │ (Edge │ │ (WebSocket via │ │ (client.userBoot │ │
│ │ (HHVM) │ │ Cache) │ │ Quip/onquip) │ │ deferred data) │ │
│ └────┬────┘ └────┬─────┘ └──────────────────┘ └───────────────────┘ │
│ │ │ │
│ │ ┌────▼──────────────────────────────┐ │
│ │ │ Loom (Distributed Data Index) │ │
│ │ │ ├─ Channel model │ │
│ │ │ ├─ ChannelMembershipByUserId │ │
│ │ │ ├─ ESCMembershipByUserId │ │
│ │ │ └─ (loom-index / loom-router) │ │
│ │ └───────────────────────────────────┘ │
│ │ │
│ ┌────▼────────────────────────────────────────────────────────────────┐ │
│ │ Quip Backend (Canvas/Docs) │ │
│ │ ├─ slack-prod.onquip.com (API + real-time collab) │ │
│ │ ├─ listenweb3.slack-prod.onquip.com (WebSocket listener) │ │
│ │ └─ api.slack-prod.onquip.com │ │
│ └────────────────────────────────────────────────────────────────────┘ │
└───────────────────────────────────────────────────────────────────────────┘
┌───────────────────────────────────────────────────────────────────────────┐
│ CDN / STATIC │
│ ┌──────────────────────┐ ┌──────────────────────────────────────────┐ │
│ │ a.slack-edge.com │ │ canvas_blob (a.slack-edge.com) │ │
│ │ (JS/CSS/fonts) │ │ (Quip CRDT engine chunks) │ │
│ └──────────────────────┘ └──────────────────────────────────────────┘ │
└───────────────────────────────────────────────────────────────────────────┘
Slack runs two distinct API services with different routing, backends, and data models.
Endpoint: POST https://<workspace>.enterprise.slack.com/api/<method>
This is the traditional Slack API -- every method is a separate POST endpoint using multipart form-data with a token field. All 40+ API calls during boot go through this path.
Every request includes query params that reveal the client framework:
?_x_id=912a4723-1772325609.377 # request correlation ID
&_x_csid=8GdRRm8e6Es # client session ID
&slack_route=E03C83N442J:E03C83N442J # enterprise:enterprise routing
&_x_version_ts=1772314064 # JS bundle version (Unix timestamp)
&_x_frontend_build_type=current # build type
&_x_desktop_ia=4 # desktop info architecture version
&_x_gantry=true # Gantry v2 framework flag
&fp=95 # feature parity version
&_x_num_retries=0 # retry count
The _x_sonic=true form field in boot requests signals the client supports the Sonic boot protocol (Slack's optimized cold-start system).
Response headers reveal the backend:
x-server: slack-www-hhvm-callbacks-iad-wfwr1kv7kl7i-- runs on HHVM (HipHop Virtual Machine, Facebook's PHP runtime)x-slack-backend: r-- backend type identifierx-backend: callbacks_normalorapi_normal-- request class routingvia: 1.1 slack-prod.tinyspeck.com, envoy-www-iad-*-- Tinyspeck origin + Envoy proxy in IAD (us-east)x-edge-backend: envoy-www-- edge layer routes toenvoy-www
Endpoint: POST https://edgeapi.slack.com/cache/<enterprise_id>/<resource>/<action>
This is Slack's edge caching layer called Flannel. It uses JSON request bodies (not form-data) and is backed by a distributed index called Loom.
Resources and actions observed:
/cache/E03C83N442J/users/info # batch-fetch user profiles by ID
/cache/E03C83N442J/users/list # list users
/cache/E03C83N442J/users/counts # user count stats
/cache/E03C83N442J/channels/info # batch-fetch channel metadata
/cache/E03C83N442J/channels/membership # channel membership check
/cache/E03C83N442J/permissions/info # permission checks (5 calls!)
/cache/E03C83N442J/huddles/info # huddle/call state
/cache/E03C83N442J/emojis/info # custom emoji data
The channels/info response headers expose the full Loom topology:
x-backend: flannel-prod-pdx-6d7b8f8579-7mh6b,
loom-index-prod-pdx-108#slack.loom.model.Channel,
loom-index-prod-pdx-108#slack.loom.model.ChannelMembershipByUserId,
loom-index-prod-pdx-108#slack.loom.model.ESCMembershipByUserId,
loom-router-prod-pdx-65755785d8-4jg58#slack.loom.model.Channel
This reveals Loom's architecture:
- loom-router pods route queries to the correct index shard
- loom-index pods hold the actual data, partitioned by model type
- Models include
Channel,ChannelMembershipByUserId,ESCMembershipByUserId(Enterprise Shared Channels) - Each model instance is on a numbered pod (e.g.,
loom-index-prod-pdx-108)
Key difference: The Edge API uses conditional fetching -- the client sends updated_ids with timestamps for each entity it already has cached, and the server only returns entities that have changed since those timestamps. This is how Slack avoids re-fetching the entire user/channel list on every load.
Slack's boot is a carefully staged pipeline. Here's the actual order from the network trace:
1. client.shouldReload -- check if client version is stale
2. features.access.policies.list -- org-level access policies
3. experiments.getByUser -- A/B test assignments (36KB! ~100 experiments)
4. api.features -- feature flags (~60 flags)
5. enterprise.prefs.get -- enterprise-level preferences
6. conversations.view -- current channel metadata
7. client.userBoot -- the big one: user data, DND state, channel list,
channel priority scores, team membership
8-12. users.prefs.get, drafts.list, client.extras,
search.autocomplete.topEmojis, workflows.triggers.*
13-18. Edge API: users/info, permissions/info, huddles/info,
emojis/info (all using conditional timestamps)
19. users.priority.list -- ranked contacts for @-mention autocomplete
20-25. conversations.history, bookmarks.list, saved.list,
conversations.listPrefs, conversations.bulkReacjiTriggers,
users.channelSections.list
26+. megaphone.notifications.list, aiApps.list,
slackAi.permissions.getForUser, calendar.*, sfdc.*,
help.issues.ticketStats, sharedInvites.canGetLink,
canvas/collab/controller-init
The client.userBoot response includes a channels_priority map that assigns every channel a relevance score between 0 and 1:
channel_id → score (0.0 to 1.0)
These scores determine sidebar ordering. Scores approaching 1.0 are channels the user interacts with most frequently; scores near 0 are rarely-visited channels. The scoring clearly uses interaction frequency, recency, and possibly message volume. DMs with close collaborators score as high as the most-used channels.
The experiments.getByUser response is one of the largest boot payloads (~36KB). Each experiment includes:
{
"experiment_name": {
"experiment_id": "10549498611876",
"type": "team" | "user",
"group": "on" | "off" | "treatment" | "control" | "aa_treatment_c",
"trigger": "hash_team" | "hash_user" | "finished" | "launch_team" | "launch_user",
"schedule_ts": 1772217597,
"log_exposures": true | false,
"exposure_id": "<team_or_user_id>"
}
}Interesting experiments visible in the response:
agent_sunroof-- AI agent featuresslackbot_ai_next,slackbot_ai_client_context-- Slack AI iterationshuddles_dual_udp_support,huddles_agc_browser_default-- audio/video calling improvementssplitview_child_windows,split_view_nux-- multi-panel UIoptimize_sidebar_latests-- sidebar performance experimentchannel_store_stable_updates,members_store_stable_updates-- data layer experimentsomniswitcher_memoization-- Cmd+K switcher performancesvg_rendering_svgr-- SVG rendering approach testlists_grid_todos,lists_records_list_5k-- Slack Lists scalingnew_joiner_workspace_switcher_prompt-- onboarding flow
The trigger field reveals the randomization strategy:
hash_team/hash_user-- deterministic hash-based assignment (stable across sessions)launch_team/launch_user-- explicit rollout (not randomized)finished-- experiment completed, now at 100%
Slack Canvas runs on Quip's infrastructure (Salesforce acquisition). The controller-init endpoint reveals:
- Real-time collab server:
listenweb3.slack-prod.onquip.com(WebSocket endpoint at/-/listen/3on port 443) - API server:
api.slack-prod.onquip.com - CDN:
a.slack-edge.com/canvas_blob/for CRDT engine chunks - Quip team ID:
T5J4Q04QG-- Slack's internal Quip org - OAuth token: Canvas has its own OAuth token with a separate expiry (12h TTL)
- Build version:
quipbuild_2026_02_26T21_00_d87d87e-- daily builds with git hash
The controller-init response includes a serialized protobuf (init_options) containing the full CRDT document state and user context, plus a massive js_initializer config with Quip-specific settings (emoji CDN hashes, Salesforce integration config, folder depth limits, max list sizes).
Canvas chunks are loaded separately from the main Slack JS bundle -- 6 blobs with content-hash-based URLs (canvas_blob/<hash>-module-loader, canvas_blob/<hash>-ancillary, etc.).
Slack's web client framework is called Gantry v2. The JS bundle structure:
gantry-v2-vendors.js -- third-party deps (React, etc.)
gantry-v2-vendors-client.js -- client-specific vendor deps
gantry-v2-shared.js -- shared framework code
client-boot.js -- boot orchestration
├─ async-client-boot-apis.js -- API client layer
├─ async-client-boot-data.js -- data stores
├─ async-client-boot-render.js -- initial render
├─ async-client-boot-deferred.js -- deferred initialization
├─ async-gantry-v2-shared-boot-async.js
├─ async-gantry-v2-default-boot-shared.js
└─ async-gantry-v2-vendors-async*.js
Route-specific lazy chunks:
├─ async-client-channel-list-view.js -- sidebar
└─ async-client-channel-view.js -- message pane
All JS/CSS is served from a.slack-edge.com with content-hash filenames and a cacheKey=gantry-<version_ts> query param for cache busting. The version timestamp (1772314064) changes with each deployment.
| Token | Format | Purpose |
|---|---|---|
xoxc-* |
Cookie-bound client token | Primary API auth -- sent in every request body as token field |
xoxd-* |
Cookie d |
Session token -- HttpOnly, stored as cookie |
b cookie |
.cd5ce331... |
Browser identifier (persistent) |
x cookie |
<hash>.<ts> |
Short-lived session binding (15min TTL, refreshed) |
d-s cookie |
Unix timestamp | Session start time |
The xoxc-* token is notable -- it's sent in the request body (not as a header or cookie), which means it can be rotated per-request without cookie management. The xoxd-* token in the d cookie is the persistent auth credential.
The URL pattern <workspace>.enterprise.slack.com and the slack_route=E03C83N442J:E03C83N442J param reveal Enterprise Grid routing. The enterprise ID appears in both the URL path and the Edge API path (/cache/E03C83N442J/), enabling multi-org routing.
The Canvas system connects via WebSocket to listenweb3.slack-prod.onquip.com/-/listen/3 for real-time document collaboration.
For message real-time updates, Slack uses a combination of:
- Long-lived WebSocket connections (for push notifications and message delivery)
- The
client.countsAPI for periodic unread count reconciliation - The
conversations.historyAPI witholdesttimestamp for loading message history
Push notifications are managed via notifications.netflix.com/push-style long-polling (Slack's equivalent appears to be through the WebSocket or the megaphone.notifications.list API).
| System | Purpose | Evidence |
|---|---|---|
| Gantry v2 | Web client SPA framework | _x_gantry=true param, gantry-v2-* JS bundles |
| Sonic | Optimized cold-start boot protocol | _x_sonic=true form field in boot requests |
| Flannel | Edge caching/proxy layer | x-backend: flannel-prod-pdx-*, x-edge-backend: flannel |
| Loom | Distributed data index (channels, users, memberships) | loom-index-prod-pdx-*, loom-router-prod-pdx-* in x-backend |
| Quip | Canvas/document collaboration engine | slack-prod.onquip.com, x-quip-tracer-id header |
| HHVM | PHP runtime for Workspace API | x-server: slack-www-hhvm-* |
| Tinyspeck | Slack's original company name, still in infra | via: 1.1 slack-prod.tinyspeck.com |
| Envoy | Edge proxy (multi-region) | envoy-www-iad-*, envoy-edge-pdx-* |
| Megaphone | In-app notification/announcement system | megaphone.notifications.list API |
| ListenWeb | WebSocket real-time server (for Canvas) | listenweb3.slack-prod.onquip.com |
- Messaging Service -- conversation history, threading, reactions, message formatting (Block Kit)
- Channel Service -- channel CRUD, membership, sections/folders, channel priority scoring
- User Service -- profiles, presence/DND, status, preferences, custom status
- Auth Service -- token management (xoxc/xoxd), enterprise routing, session binding
- Search Service -- full-text search, autocomplete, emoji ranking
- Notification Service -- unread counts, mentions, @-channel alerts, megaphone announcements
- Drafts Service -- per-channel draft persistence
- Bookmarks/Saved Service -- pinned items, saved messages
- Experimentation Service -- A/B test assignment, hash-based bucketing, exposure logging
- WebSocket Gateway -- persistent connections, message fan-out, presence updates
- Edge Cache (Flannel) -- conditional fetch with timestamps, sharded by entity type
- Distributed Index (Loom) -- channel/user/membership data, partitioned by model, routed by entity
- Document Engine -- CRDT-based collaborative editing (Canvas/Quip)
- Huddles Service -- audio/video calling, participant tracking
- Workflow Engine -- triggers, functions, automation
- App Platform -- bot users, slash commands, interactive messages, app profiles
- Feature Flag Service -- feature gates + experiment assignments in a single boot payload
- Multi-region Envoy -- IAD (us-east) for API origin, PDX (us-west) for edge
- HHVM -- PHP runtime for legacy API layer
- Chunk-based JS delivery -- 13+ async chunks with content-hash filenames
- Cookie-bound token auth -- xoxc token in body, xoxd in HttpOnly cookie, short-lived
xcookie for session binding
Search is one of Slack's most complex subsystems. A single search fires 7 parallel API calls across two API tiers:
Workspace API:
search.inline -- quick 3-result preview in current channel
search.autocomplete -- suggestion dropdown as you type
search.autocomplete.files -- file-specific autocomplete
Edge API (Flannel):
users/search -- user name matching
channels/search -- channel name matching
search.modules.messages -- full-text message search (9,551 results, paginated 20/page)
search.modules.files -- file search (called twice: different file types)
search.modules.channels -- channel name/topic search
search.modules.people -- people search
search.modules.dms -- DM-specific search
search.save -- persists the query to search history
search.precache -- pre-warms results for likely next queries
The search.modules.messages request reveals the search engine internals:
spell_correction: FUZZY_MATCH-- fuzzy matching is built insort: score/sort_dir: desc-- relevance scoring, not chronological by defaultfacets_result_count: 5-- returns top 5 filter facets (from, in, date)query_refinement_suggestions_version: 1-- the server can suggest query rewritessearch_context: desktop_messages_tab-- search knows which UI context triggered itrecent_channels-- passes recent channel IDs for context-aware rankingrequest_context-- includesactive_cid,recent_filter_in,recent_filter_fromfor personalizationextract_len: 200-- server-side snippet extraction with highlight markers (\ue000..\ue001)- Highlighting uses Unicode private-use characters --
\ue000marks highlight start,\ue001marks end. The client renders these as bold/highlight.
Search results include client_highlight styling in Block Kit -- the server returns the original Block Kit structure but with an extra "client_highlight": true style on matched text spans.
The slack.com/beacon/error endpoint receives client-side error reports. Multiple error beacons fired during search, suggesting error boundary monitoring is aggressive.
Messages are sent via the Workspace API as multipart form-data. The key fields:
channel: C09KB2J6QNN # channel ID
ts: 1772325870.xxxxx2 # client-generated timestamp (optimistic)
type: message
client_msg_id: a789f1c4-... # UUID for dedup
_x_reason: webapp_message_send
_x_mode: online
blocks: [{"type":"rich_text","elements":[{"type":"rich_text_section",
"elements":[{"type":"text","text":"..."}]}]}]
The response returns the server-assigned timestamp (ts: "1772325870.309319") which becomes the message's permanent ID. The client_msg_id UUID enables deduplication -- if the request is retried, the server recognizes it as the same message.
Messages use Block Kit format -- even plain text is wrapped in rich_text > rich_text_section > text blocks. This structure supports inline formatting (bold, italic, code), mentions, links, and emoji as first-class block elements.
channel: C09KB2J6QNN
timestamp: 1772325870.309319 # message ts (the message ID)
name: eyes # emoji name (no colons)
_x_reason: changeReactionFromUserAction
Reactions reference messages by channel + timestamp -- timestamps are message IDs in Slack. The response is simply {"ok":true}.
Every user interaction (clicking a reaction, opening a channel) fires a clog/track/ POST for analytics. This is Slack's client-side event logging system, separate from the _x_reason field on API calls (which is server-side request attribution).
The Edge API uses conditional timestamp fetching. Every request to edgeapi.slack.com includes updated_ids -- a map of entity IDs to the last-known update timestamp. The server only returns entities that changed after those timestamps. This is how Slack avoids fetching 100K+ users on every page load in large orgs.
Permissions are checked 5 times during boot. The permissions/info endpoint is called 5 separate times with different permission sets. This suggests permission checks are decoupled from entity fetching and evaluated lazily per-feature.
Slack still runs on HHVM. The x-server header reveals hostnames like slack-www-hhvm-api-iad-* and slack-www-hhvm-callbacks-iad-*. HHVM (Facebook's PHP runtime) is the execution engine for the Workspace API, with separate pools for "api" (synchronous requests) and "callbacks" (boot data, potentially heavier queries).
Tinyspeck is still in production. The via header includes slack-prod.tinyspeck.com -- Tinyspeck was Slack's original company name (from the game Glitch). It persists in production routing infrastructure.
Canvas is essentially a separate app. It has its own OAuth token, its own WebSocket connection, its own CDN path (canvas_blob/), its own build versioning, and its own real-time infrastructure on Quip's domain. It's bolted onto Slack's UI but architecturally independent.
The boot payload carries ~100 A/B experiments. At 36KB, the experiment assignment is one of the largest single payloads during boot. Every experiment includes its randomization strategy, schedule timestamp, and exposure logging config.
Channel sections are a first-class concept. users.channelSections.list reveals that the sidebar's channel groupings (favorites, DMs, channels) are a server-side data model, not just client-side UI state.
The client proactively detects ad blockers. The ublockworkaround.history API endpoint is explicitly named after uBlock Origin -- Slack detects when ad blockers interfere with their requests and has a workaround path.
Salesforce integration is deeply embedded. The sfdc.integration.listOrgs API and extensive Salesforce config in the Canvas js_initializer (Steam types, STMF types, vPod hosts) show deep platform integration beyond just the Slack-Salesforce connector.
Feature parity tracking. The fp=95 query param on every request likely tracks "feature parity" -- how close the web client is to the desktop client's feature set. This number presumably increments as web catches up.
DOMContentLoaded in 177ms. Slack achieves extremely fast DCL because the initial HTML is a minimal shell -- all real rendering happens after the JS boots and the client.userBoot data arrives. First contentful paint takes 2,160ms (the real "loaded" moment).
Two deployment regions are visible. API origin servers are in iad (us-east / Virginia) while edge proxies run in pdx (us-west / Portland). The via header shows requests traverse both: edge in PDX, origin in IAD.
All API tokens are sent in request bodies, not headers. Unlike most APIs that use Authorization headers, Slack sends the xoxc-* token as a form field or JSON body property. This is a legacy design from when the API was primarily used by web forms.
Timestamps are message IDs. Slack's ts field (e.g., 1772325870.309319) serves as both a timestamp and a unique message identifier. This is a core design choice -- messages are ordered and addressed by their creation time, and the decimal portion ensures uniqueness within a second.
Client generates an optimistic timestamp. When sending a message, the client sends its own ts estimate, but the server returns the authoritative one. The client_msg_id UUID provides deduplication if the network retries.
Every API call includes a reason tag. The _x_reason field on every request (webapp_message_send, changeReactionFromUserAction, deferred-data) gives the backend attribution for why the call was made. This enables per-feature performance tracking and cost attribution.
Block Kit is the universal message format. Even a plain text message like "hello" is encoded as rich_text > rich_text_section > text blocks. This isn't overhead -- it's what enables inline formatting, mentions, emoji, and links to be first-class elements in the message structure rather than parsed from markdown.
Quick reactions are server-personalized. The hover toolbar shows "React with white_check_mark / eyes / raised_hands" -- these are the user's most-used reactions, fetched at boot via search.autocomplete.topEmojis.
B3 distributed tracing appears selectively. Most API calls don't include trace headers, but some Edge API calls include _x_b3_traceid, _x_b3_spanid, and _x_b3_sampled as query params. This suggests sampling-based tracing rather than universal instrumentation.
The _x_mode field reveals offline capability. Every write API call includes _x_mode: online. This implies Slack's client has an offline mode where operations are queued and replayed -- the mode field tells the server whether this is a live or replayed request.
Link unfurling is entirely server-side. When you send a message containing a URL, chat.postMessage includes an unfurl: [] field (empty -- the client doesn't prefetch). The unfurl card (title, description, thumbnail) appears asynchronously as the server fetches the URL metadata and pushes an update via the real-time connection. For GitHub links, the GitHub Slack app handles the unfurl via Slack's app unfurl API rather than generic OGP scraping.
Search precaching is real. After displaying results, the client fires search.precache -- likely pre-warming the next page or related queries. This explains why paginating search results feels instant.
Cmd+K search runs on the Edge API. The quick-switcher (Cmd+K) uses edgeapi.slack.com/cache/.../users/search and channels/search rather than the Workspace API. This means user/channel search is backed by Loom (the distributed index), not the main search backend. Faster, but different indexing.
Every search has a session ID. The search_session_id UUID persists across pagination and refinements within a single search session. This enables the backend to track search funnels: did the user find what they wanted, or did they refine the query?
users.prefs.set fires on navigation. When you switch channels, Slack saves the current channel to your preferences via users.prefs.set. This is how it knows which channel to reopen when you return.
Error reporting goes to a separate domain. Client errors are reported to slack.com/beacon/error -- not the workspace domain or edge API. This separate path ensures error reporting works even when the main API is down.
Reacji triggers are checked on every channel load. conversations.bulkReacjiTriggers fires whenever you open a channel -- any emoji reaction in that channel could trigger a workflow automation. The client needs to know which emoji are "live" so it can show trigger indicators.
8 thumbnail sizes are pre-generated per uploaded image. Search results revealed sizes at 64, 80, 160, 360, 480, 720, 800, 960, and 1024px. These are generated server-side on upload, not on-the-fly -- the URLs use files-tmb/ (thumbnails) vs files-pri/ (originals).
client.shouldReload is the deployment mechanism. The very first boot call asks the server "is my JS version stale?" This is how Slack ships without forcing hard refreshes -- the server compares _x_version_ts against the current deployment and tells the client to reload if needed.
All unfurled images are proxied through slack-imgs.com. External image URLs in link previews are rewritten to slack-imgs.com/?c=1&o1=wi32.he32.si&url=<encoded_url> -- Slack never loads images directly from third-party origins. This prevents IP leakage, enables resizing, and provides a cache layer.
OAuth scope headers appear on every response. Both x-accepted-oauth-scopes (what the endpoint requires) and x-oauth-scopes (what the token has) are returned on every API call. The client can detect permission mismatches without making a separate authz call.
Client session ID rotates per view. The _x_csid parameter changes when you navigate between channels or views -- each "session" gets its own correlation ID separate from the boot session. This enables per-view performance tracking and error attribution.