From 98d61a6090c6539a07f2033c4e746a7ed048aca0 Mon Sep 17 00:00:00 2001
From: tudor <7089284+tudddorrr@users.noreply.github.com>
Date: Sun, 23 Aug 2026 21:07:45 +0100
Subject: [PATCH 1/6] add versioned docs with 1.x / pre-1.0 layout tabs
---
app/app.css | 17 +
content/docs/{ => 1.x}/godot/channels.mdx | 6 +-
content/docs/{ => 1.x}/godot/continuity.mdx | 0
content/docs/{ => 1.x}/godot/events.mdx | 0
content/docs/{ => 1.x}/godot/exporting.mdx | 0
content/docs/{ => 1.x}/godot/feedback.mdx | 2 +-
content/docs/{ => 1.x}/godot/groups.mdx | 0
content/docs/{ => 1.x}/godot/identifying.mdx | 10 +-
content/docs/{ => 1.x}/godot/install.mdx | 7 +-
content/docs/{ => 1.x}/godot/leaderboards.mdx | 2 +-
content/docs/{ => 1.x}/godot/live-config.mdx | 2 +-
content/docs/{ => 1.x}/godot/meta.json | 1 -
.../{ => 1.x}/godot/player-authentication.mdx | 6 +-
.../docs/{ => 1.x}/godot/player-presence.mdx | 0
content/docs/{ => 1.x}/godot/player-props.mdx | 2 +-
.../{ => 1.x}/godot/player-relationships.mdx | 2 +-
.../{ => 1.x}/godot/request-verification.mdx | 0
content/docs/{ => 1.x}/godot/saves.mdx | 0
.../{ => 1.x}/godot/settings-reference.mdx | 8 +-
content/docs/{ => 1.x}/godot/socket.mdx | 10 +-
content/docs/{ => 1.x}/godot/stats.mdx | 0
.../docs/{ => 1.x}/http/authentication.mdx | 6 +-
.../docs/{ => 1.x}/http/bruno-collection.mdx | 6 +-
content/docs/{ => 1.x}/http/common-errors.mdx | 0
content/docs/{ => 1.x}/http/dev-data.mdx | 0
content/docs/{ => 1.x}/http/event-api.mdx | 0
.../docs/{ => 1.x}/http/game-channel-api.mdx | 0
.../docs/{ => 1.x}/http/game-config-api.mdx | 0
.../docs/{ => 1.x}/http/game-feedback-api.mdx | 0
content/docs/{ => 1.x}/http/game-save-api.mdx | 0
content/docs/{ => 1.x}/http/game-stat-api.mdx | 0
.../docs/{ => 1.x}/http/leaderboard-api.mdx | 0
content/docs/{ => 1.x}/http/meta.json | 0
content/docs/{ => 1.x}/http/player-api.mdx | 0
.../docs/{ => 1.x}/http/player-auth-api.mdx | 4 +-
.../docs/{ => 1.x}/http/player-group-api.mdx | 0
.../{ => 1.x}/http/player-presence-api.mdx | 0
.../http/player-relationships-api.mdx | 0
.../{ => 1.x}/http/request-verification.mdx | 2 +-
.../docs/{ => 1.x}/http/socket-ticket-api.mdx | 2 +-
content/docs/1.x/index.mdx | 28 ++
.../integrations/apple-game-center.mdx | 2 +-
.../integrations/google-play-games.mdx | 2 +-
content/docs/{ => 1.x}/integrations/meta.json | 0
.../{ => 1.x}/integrations/steamworks.mdx | 2 +-
content/docs/1.x/meta.json | 6 +
.../{ => 1.x}/selfhosting/configuration.mdx | 0
content/docs/{ => 1.x}/selfhosting/meta.json | 0
.../docs/{ => 1.x}/selfhosting/overview.mdx | 0
.../docs/{ => 1.x}/sockets/common-errors.mdx | 4 +-
.../{ => 1.x}/sockets/custom-ping-pongs.mdx | 0
content/docs/{ => 1.x}/sockets/intro.mdx | 8 +-
content/docs/{ => 1.x}/sockets/meta.json | 0
.../sockets/request-verification.mdx | 0
content/docs/{ => 1.x}/sockets/requests.mdx | 0
content/docs/{ => 1.x}/sockets/responses.mdx | 0
content/docs/{ => 1.x}/unity/channels.mdx | 6 +-
content/docs/{ => 1.x}/unity/continuity.mdx | 0
content/docs/{ => 1.x}/unity/dev-data.mdx | 0
content/docs/{ => 1.x}/unity/events.mdx | 0
content/docs/{ => 1.x}/unity/feedback.mdx | 2 +-
content/docs/{ => 1.x}/unity/groups.mdx | 0
content/docs/{ => 1.x}/unity/identifying.mdx | 10 +-
content/docs/{ => 1.x}/unity/install.mdx | 7 +-
content/docs/{ => 1.x}/unity/leaderboards.mdx | 2 +-
content/docs/{ => 1.x}/unity/live-config.mdx | 2 +-
content/docs/{ => 1.x}/unity/meta.json | 1 -
.../{ => 1.x}/unity/player-authentication.mdx | 6 +-
.../docs/{ => 1.x}/unity/player-presence.mdx | 0
content/docs/{ => 1.x}/unity/player-props.mdx | 2 +-
.../{ => 1.x}/unity/player-relationships.mdx | 2 +-
.../{ => 1.x}/unity/request-verification.mdx | 0
content/docs/{ => 1.x}/unity/saves.mdx | 0
.../{ => 1.x}/unity/settings-reference.mdx | 10 +-
content/docs/{ => 1.x}/unity/socket.mdx | 10 +-
content/docs/{ => 1.x}/unity/stats.mdx | 0
content/docs/godot/upgrading-to-1.0.mdx | 269 ----------
content/docs/index.mdx | 12 +-
content/docs/meta.json | 4 +-
content/docs/pre-1.0/godot/channels.mdx | 476 ++++++++++++++++++
content/docs/pre-1.0/godot/continuity.mdx | 109 ++++
content/docs/pre-1.0/godot/events.mdx | 49 ++
content/docs/pre-1.0/godot/exporting.mdx | 42 ++
content/docs/pre-1.0/godot/feedback.mdx | 80 +++
content/docs/pre-1.0/godot/groups.mdx | 52 ++
content/docs/pre-1.0/godot/identifying.mdx | 313 ++++++++++++
content/docs/pre-1.0/godot/install.mdx | 65 +++
content/docs/pre-1.0/godot/leaderboards.mdx | 278 ++++++++++
content/docs/pre-1.0/godot/live-config.mdx | 61 +++
content/docs/pre-1.0/godot/meta.json | 25 +
.../pre-1.0/godot/player-authentication.mdx | 287 +++++++++++
.../docs/pre-1.0/godot/player-presence.mdx | 65 +++
content/docs/pre-1.0/godot/player-props.mdx | 184 +++++++
.../pre-1.0/godot/player-relationships.mdx | 280 +++++++++++
.../pre-1.0/godot/request-verification.mdx | 49 ++
content/docs/pre-1.0/godot/saves.mdx | 187 +++++++
.../docs/pre-1.0/godot/settings-reference.mdx | 114 +++++
content/docs/pre-1.0/godot/socket.mdx | 95 ++++
content/docs/pre-1.0/godot/stats.mdx | 177 +++++++
content/docs/pre-1.0/http/authentication.mdx | 42 ++
.../docs/pre-1.0/http/bruno-collection.mdx | 55 ++
content/docs/pre-1.0/http/common-errors.mdx | 58 +++
content/docs/pre-1.0/http/dev-data.mdx | 23 +
content/docs/pre-1.0/http/event-api.mdx | 49 ++
.../docs/pre-1.0/http/game-channel-api.mdx | 41 ++
content/docs/pre-1.0/http/game-config-api.mdx | 30 ++
.../docs/pre-1.0/http/game-feedback-api.mdx | 28 ++
content/docs/pre-1.0/http/game-save-api.mdx | 52 ++
content/docs/pre-1.0/http/game-stat-api.mdx | 28 ++
content/docs/pre-1.0/http/leaderboard-api.mdx | 29 ++
content/docs/pre-1.0/http/meta.json | 24 +
content/docs/pre-1.0/http/player-api.mdx | 43 ++
content/docs/pre-1.0/http/player-auth-api.mdx | 213 ++++++++
.../docs/pre-1.0/http/player-group-api.mdx | 28 ++
.../docs/pre-1.0/http/player-presence-api.mdx | 28 ++
.../pre-1.0/http/player-relationships-api.mdx | 26 +
.../pre-1.0/http/request-verification.mdx | 106 ++++
.../docs/pre-1.0/http/socket-ticket-api.mdx | 28 ++
content/docs/pre-1.0/index.mdx | 33 ++
.../integrations/apple-game-center.mdx | 20 +
.../integrations/google-play-games.mdx | 22 +
content/docs/pre-1.0/integrations/meta.json | 9 +
.../docs/pre-1.0/integrations/steamworks.mdx | 94 ++++
content/docs/pre-1.0/meta.json | 5 +
.../pre-1.0/selfhosting/configuration.mdx | 109 ++++
content/docs/pre-1.0/selfhosting/meta.json | 8 +
content/docs/pre-1.0/selfhosting/overview.mdx | 28 ++
.../docs/pre-1.0/sockets/common-errors.mdx | 147 ++++++
.../pre-1.0/sockets/custom-ping-pongs.mdx | 64 +++
content/docs/pre-1.0/sockets/intro.mdx | 106 ++++
content/docs/pre-1.0/sockets/meta.json | 12 +
.../pre-1.0/sockets/request-verification.mdx | 111 ++++
content/docs/pre-1.0/sockets/requests.mdx | 47 ++
content/docs/pre-1.0/sockets/responses.mdx | 279 ++++++++++
content/docs/pre-1.0/unity/channels.mdx | 475 +++++++++++++++++
content/docs/pre-1.0/unity/continuity.mdx | 120 +++++
content/docs/pre-1.0/unity/dev-data.mdx | 23 +
content/docs/pre-1.0/unity/events.mdx | 91 ++++
content/docs/pre-1.0/unity/feedback.mdx | 110 ++++
content/docs/pre-1.0/unity/groups.mdx | 56 +++
content/docs/pre-1.0/unity/identifying.mdx | 359 +++++++++++++
content/docs/pre-1.0/unity/install.mdx | 52 ++
content/docs/pre-1.0/unity/leaderboards.mdx | 317 ++++++++++++
content/docs/pre-1.0/unity/live-config.mdx | 80 +++
content/docs/pre-1.0/unity/meta.json | 25 +
.../pre-1.0/unity/player-authentication.mdx | 331 ++++++++++++
.../docs/pre-1.0/unity/player-presence.mdx | 79 +++
content/docs/pre-1.0/unity/player-props.mdx | 218 ++++++++
.../pre-1.0/unity/player-relationships.mdx | 315 ++++++++++++
.../pre-1.0/unity/request-verification.mdx | 42 ++
content/docs/pre-1.0/unity/saves.mdx | 194 +++++++
.../docs/pre-1.0/unity/settings-reference.mdx | 82 +++
content/docs/pre-1.0/unity/socket.mdx | 120 +++++
content/docs/pre-1.0/unity/stats.mdx | 196 ++++++++
content/docs/unity/upgrading-to-1.0.mdx | 278 ----------
155 files changed, 8429 insertions(+), 639 deletions(-)
rename content/docs/{ => 1.x}/godot/channels.mdx (97%)
rename content/docs/{ => 1.x}/godot/continuity.mdx (100%)
rename content/docs/{ => 1.x}/godot/events.mdx (100%)
rename content/docs/{ => 1.x}/godot/exporting.mdx (100%)
rename content/docs/{ => 1.x}/godot/feedback.mdx (94%)
rename content/docs/{ => 1.x}/godot/groups.mdx (100%)
rename content/docs/{ => 1.x}/godot/identifying.mdx (95%)
rename content/docs/{ => 1.x}/godot/install.mdx (87%)
rename content/docs/{ => 1.x}/godot/leaderboards.mdx (98%)
rename content/docs/{ => 1.x}/godot/live-config.mdx (97%)
rename content/docs/{ => 1.x}/godot/meta.json (94%)
rename content/docs/{ => 1.x}/godot/player-authentication.mdx (97%)
rename content/docs/{ => 1.x}/godot/player-presence.mdx (100%)
rename content/docs/{ => 1.x}/godot/player-props.mdx (98%)
rename content/docs/{ => 1.x}/godot/player-relationships.mdx (98%)
rename content/docs/{ => 1.x}/godot/request-verification.mdx (100%)
rename content/docs/{ => 1.x}/godot/saves.mdx (100%)
rename content/docs/{ => 1.x}/godot/settings-reference.mdx (92%)
rename content/docs/{ => 1.x}/godot/socket.mdx (82%)
rename content/docs/{ => 1.x}/godot/stats.mdx (100%)
rename content/docs/{ => 1.x}/http/authentication.mdx (76%)
rename content/docs/{ => 1.x}/http/bruno-collection.mdx (83%)
rename content/docs/{ => 1.x}/http/common-errors.mdx (100%)
rename content/docs/{ => 1.x}/http/dev-data.mdx (100%)
rename content/docs/{ => 1.x}/http/event-api.mdx (100%)
rename content/docs/{ => 1.x}/http/game-channel-api.mdx (100%)
rename content/docs/{ => 1.x}/http/game-config-api.mdx (100%)
rename content/docs/{ => 1.x}/http/game-feedback-api.mdx (100%)
rename content/docs/{ => 1.x}/http/game-save-api.mdx (100%)
rename content/docs/{ => 1.x}/http/game-stat-api.mdx (100%)
rename content/docs/{ => 1.x}/http/leaderboard-api.mdx (100%)
rename content/docs/{ => 1.x}/http/meta.json (100%)
rename content/docs/{ => 1.x}/http/player-api.mdx (100%)
rename content/docs/{ => 1.x}/http/player-auth-api.mdx (97%)
rename content/docs/{ => 1.x}/http/player-group-api.mdx (100%)
rename content/docs/{ => 1.x}/http/player-presence-api.mdx (100%)
rename content/docs/{ => 1.x}/http/player-relationships-api.mdx (100%)
rename content/docs/{ => 1.x}/http/request-verification.mdx (96%)
rename content/docs/{ => 1.x}/http/socket-ticket-api.mdx (91%)
create mode 100644 content/docs/1.x/index.mdx
rename content/docs/{ => 1.x}/integrations/apple-game-center.mdx (82%)
rename content/docs/{ => 1.x}/integrations/google-play-games.mdx (85%)
rename content/docs/{ => 1.x}/integrations/meta.json (100%)
rename content/docs/{ => 1.x}/integrations/steamworks.mdx (96%)
create mode 100644 content/docs/1.x/meta.json
rename content/docs/{ => 1.x}/selfhosting/configuration.mdx (100%)
rename content/docs/{ => 1.x}/selfhosting/meta.json (100%)
rename content/docs/{ => 1.x}/selfhosting/overview.mdx (100%)
rename content/docs/{ => 1.x}/sockets/common-errors.mdx (91%)
rename content/docs/{ => 1.x}/sockets/custom-ping-pongs.mdx (100%)
rename content/docs/{ => 1.x}/sockets/intro.mdx (85%)
rename content/docs/{ => 1.x}/sockets/meta.json (100%)
rename content/docs/{ => 1.x}/sockets/request-verification.mdx (100%)
rename content/docs/{ => 1.x}/sockets/requests.mdx (100%)
rename content/docs/{ => 1.x}/sockets/responses.mdx (100%)
rename content/docs/{ => 1.x}/unity/channels.mdx (97%)
rename content/docs/{ => 1.x}/unity/continuity.mdx (100%)
rename content/docs/{ => 1.x}/unity/dev-data.mdx (100%)
rename content/docs/{ => 1.x}/unity/events.mdx (100%)
rename content/docs/{ => 1.x}/unity/feedback.mdx (95%)
rename content/docs/{ => 1.x}/unity/groups.mdx (100%)
rename content/docs/{ => 1.x}/unity/identifying.mdx (94%)
rename content/docs/{ => 1.x}/unity/install.mdx (87%)
rename content/docs/{ => 1.x}/unity/leaderboards.mdx (98%)
rename content/docs/{ => 1.x}/unity/live-config.mdx (97%)
rename content/docs/{ => 1.x}/unity/meta.json (94%)
rename content/docs/{ => 1.x}/unity/player-authentication.mdx (97%)
rename content/docs/{ => 1.x}/unity/player-presence.mdx (100%)
rename content/docs/{ => 1.x}/unity/player-props.mdx (98%)
rename content/docs/{ => 1.x}/unity/player-relationships.mdx (98%)
rename content/docs/{ => 1.x}/unity/request-verification.mdx (100%)
rename content/docs/{ => 1.x}/unity/saves.mdx (100%)
rename content/docs/{ => 1.x}/unity/settings-reference.mdx (88%)
rename content/docs/{ => 1.x}/unity/socket.mdx (83%)
rename content/docs/{ => 1.x}/unity/stats.mdx (100%)
delete mode 100644 content/docs/godot/upgrading-to-1.0.mdx
create mode 100644 content/docs/pre-1.0/godot/channels.mdx
create mode 100644 content/docs/pre-1.0/godot/continuity.mdx
create mode 100644 content/docs/pre-1.0/godot/events.mdx
create mode 100644 content/docs/pre-1.0/godot/exporting.mdx
create mode 100644 content/docs/pre-1.0/godot/feedback.mdx
create mode 100644 content/docs/pre-1.0/godot/groups.mdx
create mode 100644 content/docs/pre-1.0/godot/identifying.mdx
create mode 100644 content/docs/pre-1.0/godot/install.mdx
create mode 100644 content/docs/pre-1.0/godot/leaderboards.mdx
create mode 100644 content/docs/pre-1.0/godot/live-config.mdx
create mode 100644 content/docs/pre-1.0/godot/meta.json
create mode 100644 content/docs/pre-1.0/godot/player-authentication.mdx
create mode 100644 content/docs/pre-1.0/godot/player-presence.mdx
create mode 100644 content/docs/pre-1.0/godot/player-props.mdx
create mode 100644 content/docs/pre-1.0/godot/player-relationships.mdx
create mode 100644 content/docs/pre-1.0/godot/request-verification.mdx
create mode 100644 content/docs/pre-1.0/godot/saves.mdx
create mode 100644 content/docs/pre-1.0/godot/settings-reference.mdx
create mode 100644 content/docs/pre-1.0/godot/socket.mdx
create mode 100644 content/docs/pre-1.0/godot/stats.mdx
create mode 100644 content/docs/pre-1.0/http/authentication.mdx
create mode 100644 content/docs/pre-1.0/http/bruno-collection.mdx
create mode 100644 content/docs/pre-1.0/http/common-errors.mdx
create mode 100644 content/docs/pre-1.0/http/dev-data.mdx
create mode 100644 content/docs/pre-1.0/http/event-api.mdx
create mode 100644 content/docs/pre-1.0/http/game-channel-api.mdx
create mode 100644 content/docs/pre-1.0/http/game-config-api.mdx
create mode 100644 content/docs/pre-1.0/http/game-feedback-api.mdx
create mode 100644 content/docs/pre-1.0/http/game-save-api.mdx
create mode 100644 content/docs/pre-1.0/http/game-stat-api.mdx
create mode 100644 content/docs/pre-1.0/http/leaderboard-api.mdx
create mode 100644 content/docs/pre-1.0/http/meta.json
create mode 100644 content/docs/pre-1.0/http/player-api.mdx
create mode 100644 content/docs/pre-1.0/http/player-auth-api.mdx
create mode 100644 content/docs/pre-1.0/http/player-group-api.mdx
create mode 100644 content/docs/pre-1.0/http/player-presence-api.mdx
create mode 100644 content/docs/pre-1.0/http/player-relationships-api.mdx
create mode 100644 content/docs/pre-1.0/http/request-verification.mdx
create mode 100644 content/docs/pre-1.0/http/socket-ticket-api.mdx
create mode 100644 content/docs/pre-1.0/index.mdx
create mode 100644 content/docs/pre-1.0/integrations/apple-game-center.mdx
create mode 100644 content/docs/pre-1.0/integrations/google-play-games.mdx
create mode 100644 content/docs/pre-1.0/integrations/meta.json
create mode 100644 content/docs/pre-1.0/integrations/steamworks.mdx
create mode 100644 content/docs/pre-1.0/meta.json
create mode 100644 content/docs/pre-1.0/selfhosting/configuration.mdx
create mode 100644 content/docs/pre-1.0/selfhosting/meta.json
create mode 100644 content/docs/pre-1.0/selfhosting/overview.mdx
create mode 100644 content/docs/pre-1.0/sockets/common-errors.mdx
create mode 100644 content/docs/pre-1.0/sockets/custom-ping-pongs.mdx
create mode 100644 content/docs/pre-1.0/sockets/intro.mdx
create mode 100644 content/docs/pre-1.0/sockets/meta.json
create mode 100644 content/docs/pre-1.0/sockets/request-verification.mdx
create mode 100644 content/docs/pre-1.0/sockets/requests.mdx
create mode 100644 content/docs/pre-1.0/sockets/responses.mdx
create mode 100644 content/docs/pre-1.0/unity/channels.mdx
create mode 100644 content/docs/pre-1.0/unity/continuity.mdx
create mode 100644 content/docs/pre-1.0/unity/dev-data.mdx
create mode 100644 content/docs/pre-1.0/unity/events.mdx
create mode 100644 content/docs/pre-1.0/unity/feedback.mdx
create mode 100644 content/docs/pre-1.0/unity/groups.mdx
create mode 100644 content/docs/pre-1.0/unity/identifying.mdx
create mode 100644 content/docs/pre-1.0/unity/install.mdx
create mode 100644 content/docs/pre-1.0/unity/leaderboards.mdx
create mode 100644 content/docs/pre-1.0/unity/live-config.mdx
create mode 100644 content/docs/pre-1.0/unity/meta.json
create mode 100644 content/docs/pre-1.0/unity/player-authentication.mdx
create mode 100644 content/docs/pre-1.0/unity/player-presence.mdx
create mode 100644 content/docs/pre-1.0/unity/player-props.mdx
create mode 100644 content/docs/pre-1.0/unity/player-relationships.mdx
create mode 100644 content/docs/pre-1.0/unity/request-verification.mdx
create mode 100644 content/docs/pre-1.0/unity/saves.mdx
create mode 100644 content/docs/pre-1.0/unity/settings-reference.mdx
create mode 100644 content/docs/pre-1.0/unity/socket.mdx
create mode 100644 content/docs/pre-1.0/unity/stats.mdx
delete mode 100644 content/docs/unity/upgrading-to-1.0.mdx
diff --git a/app/app.css b/app/app.css
index cf6f374..0be3cbc 100644
--- a/app/app.css
+++ b/app/app.css
@@ -43,3 +43,20 @@ html > body[data-scroll-locked] {
code {
tab-size: 4;
}
+
+/* version switcher: match the search field, placed above it */
+#nd-sidebar button:has(> .lucide-chevrons-up-down) {
+ @apply inline-flex items-center gap-2 rounded-lg border p-1.5 ps-2 text-sm text-fd-muted-foreground;
+ background-color: var(--color-gray-900);
+ order: 1;
+ margin-bottom: -0.375rem;
+}
+
+#nd-sidebar button[data-search-full] {
+ order: 2;
+}
+
+#nd-sidebar button:has(> .lucide-chevrons-up-down):hover,
+#nd-sidebar button:has(> .lucide-chevrons-up-down)[data-state='open'] {
+ @apply bg-fd-accent text-fd-accent-foreground;
+}
diff --git a/content/docs/godot/channels.mdx b/content/docs/1.x/godot/channels.mdx
similarity index 97%
rename from content/docs/godot/channels.mdx
rename to content/docs/1.x/godot/channels.mdx
index b5fb4ee..45428f0 100644
--- a/content/docs/godot/channels.mdx
+++ b/content/docs/1.x/godot/channels.mdx
@@ -285,7 +285,7 @@ func _on_channel_props_rejected(rejected_props: Array[TaloRejectedProp]) -> void
**storage** prop errors, use `channel_storage_props_failed_to_set` instead.
-See [Players and props - Prop rejection reasons](/docs/godot/player-props#prop-rejection-reasons) for the full list of rejection reason codes.
+See [Players and props - Prop rejection reasons](/docs/1.x/godot/player-props#prop-rejection-reasons) for the full list of rejection reason codes.
## Channel storage
@@ -324,7 +324,7 @@ func _ready() -> void:
)
```
-`TaloChannelStoragePropError` includes a `message` with a human-readable description of the error, alongside the `error` code. See [Players and props - Prop rejection reasons](/docs/godot/player-props#prop-rejection-reasons) for the full list of rejection reason codes.
+`TaloChannelStoragePropError` includes a `message` with a human-readable description of the error, alongside the `error` code. See [Players and props - Prop rejection reasons](/docs/1.x/godot/player-props#prop-rejection-reasons) for the full list of rejection reason codes.
### Setting storage prop arrays
@@ -442,7 +442,7 @@ This function will return a `TaloChannelStorageProp` array, allowing you to iter
#### Prop arrays
-If any of the keys you pass are [prop arrays](/docs/godot/channels#getting-storage-prop-arrays), append `[]` to the key to fetch every item stored under it. Scalar and array keys can be mixed in a single call:
+If any of the keys you pass are [prop arrays](/docs/1.x/godot/channels#getting-storage-prop-arrays), append `[]` to the key to fetch every item stored under it. Scalar and array keys can be mixed in a single call:
```gdscript
# storage_prop_1 is a scalar, world_items[] is a prop array
diff --git a/content/docs/godot/continuity.mdx b/content/docs/1.x/godot/continuity.mdx
similarity index 100%
rename from content/docs/godot/continuity.mdx
rename to content/docs/1.x/godot/continuity.mdx
diff --git a/content/docs/godot/events.mdx b/content/docs/1.x/godot/events.mdx
similarity index 100%
rename from content/docs/godot/events.mdx
rename to content/docs/1.x/godot/events.mdx
diff --git a/content/docs/godot/exporting.mdx b/content/docs/1.x/godot/exporting.mdx
similarity index 100%
rename from content/docs/godot/exporting.mdx
rename to content/docs/1.x/godot/exporting.mdx
diff --git a/content/docs/godot/feedback.mdx b/content/docs/1.x/godot/feedback.mdx
similarity index 94%
rename from content/docs/godot/feedback.mdx
rename to content/docs/1.x/godot/feedback.mdx
index eadd7b3..470addf 100644
--- a/content/docs/godot/feedback.mdx
+++ b/content/docs/1.x/godot/feedback.mdx
@@ -77,4 +77,4 @@ func _on_props_rejected(rejected_props: Array[TaloRejectedProp]) -> void:
print("Rejected prop '%s': %s (%s)" % [prop.key, prop.message, prop.error])
```
-See [Players and props - Prop rejection reasons](/docs/godot/player-props#prop-rejection-reasons) for the full list of rejection reason codes.
+See [Players and props - Prop rejection reasons](/docs/1.x/godot/player-props#prop-rejection-reasons) for the full list of rejection reason codes.
diff --git a/content/docs/godot/groups.mdx b/content/docs/1.x/godot/groups.mdx
similarity index 100%
rename from content/docs/godot/groups.mdx
rename to content/docs/1.x/godot/groups.mdx
diff --git a/content/docs/godot/identifying.mdx b/content/docs/1.x/godot/identifying.mdx
similarity index 95%
rename from content/docs/godot/identifying.mdx
rename to content/docs/1.x/godot/identifying.mdx
index 35a39e6..531f237 100644
--- a/content/docs/godot/identifying.mdx
+++ b/content/docs/1.x/godot/identifying.mdx
@@ -34,7 +34,7 @@ You cannot use "Talo" for the `service` parameter as this is reserved for **Talo
If you are using Talo Player Authentication, `Talo.players.identify()` will be invoked automatically and the `Talo.players.identified` signal will also emit as normal.
-Visit the [Player authentication docs](/docs/godot/player-authentication) to learn more about identifying players with authentication enabled.
+Visit the [Player authentication docs](/docs/1.x/godot/player-authentication) to learn more about identifying players with authentication enabled.
@@ -130,7 +130,7 @@ In the example above, the two players created with `Talo.players.identify()` are
page](https://dashboard.trytalo.com/integrations).
-If you have the Steamworks integration enabled, Talo can sync a Steam player ([as described here](/docs/integrations/steamworks#authentication)). You can do this via the `Talo.players.identify_steam` function. Here's an example using [GodotSteam](https://godotsteam.com):
+If you have the Steamworks integration enabled, Talo can sync a Steam player ([as described here](/docs/1.x/integrations/steamworks#authentication)). You can do this via the `Talo.players.identify_steam` function. Here's an example using [GodotSteam](https://godotsteam.com):
```gdscript
extends Node
@@ -153,7 +153,7 @@ The `identity` parameter is optional but strongly recommended as it ensures prop
### Steamworks player props
-After successfully authenticating the player, several [props](/docs/godot/player-props) will automatically be created for them:
+After successfully authenticating the player, several [props](/docs/1.x/godot/player-props) will automatically be created for them:
- `META_STEAMWORKS_VAC_BANNED` - "true" or "false"
- `META_STEAMWORKS_PUBLISHER_BANNED` - "true" or "false"
@@ -205,7 +205,7 @@ Replace "yourid.apps.googleusercontent.com" with the client ID you generated ear
### Google Play Games player props
-After successfully authenticating the player, these [props](/docs/godot/player-props) will automatically be created for them:
+After successfully authenticating the player, these [props](/docs/1.x/godot/player-props) will automatically be created for them:
- `META_GOOGLE_PLAY_GAMES_DISPLAY_NAME` - The player's chosen display name
- `META_GOOGLE_PLAY_GAMES_AVATAR_URL` - A direct link to the player's avatar
@@ -292,7 +292,7 @@ This function will return the identifier (if one exists) or an empty string. If
## Display names
-Every player alias has a `display_name` that provides a human-readable name for the player. You can read it from `Talo.current_alias.display_name` after a player has been identified. Display names are resolved from a [player's props](/docs/godot/player-props) - arbitrary properties persisted across all of a player's aliases.
+Every player alias has a `display_name` that provides a human-readable name for the player. You can read it from `Talo.current_alias.display_name` after a player has been identified. Display names are resolved from a [player's props](/docs/1.x/godot/player-props) - arbitrary properties persisted across all of a player's aliases.
```gdscript title="welcome_label.gd"
extends Label
diff --git a/content/docs/godot/install.mdx b/content/docs/1.x/godot/install.mdx
similarity index 87%
rename from content/docs/godot/install.mdx
rename to content/docs/1.x/godot/install.mdx
index 3b29235..83215a9 100644
--- a/content/docs/godot/install.mdx
+++ b/content/docs/1.x/godot/install.mdx
@@ -21,11 +21,6 @@ The Godot Asset Library is the recommended way to download Godot plugins. Open t
You can download the latest version of the Godot plugin from our [itch.io page](https://sleepystudios.itch.io/talo-godot). Simply unzip the plugin and copy the `addons/talo` folder into your project.
-
- Upgrading from an older version of the plugin? Read the [upgrading to 1.0
- guide](/docs/godot/upgrading-to-1.0) for the breaking changes.
-
-
## Enable the plugin
@@ -67,4 +62,4 @@ If you're self-hosting Talo, you can configure your custom endpoint here in the
### Settings reference
-You can visit the [settings reference](/docs/godot/settings-reference) for a detailed overview of Talo's configuration options.
+You can visit the [settings reference](/docs/1.x/godot/settings-reference) for a detailed overview of Talo's configuration options.
diff --git a/content/docs/godot/leaderboards.mdx b/content/docs/1.x/godot/leaderboards.mdx
similarity index 98%
rename from content/docs/godot/leaderboards.mdx
rename to content/docs/1.x/godot/leaderboards.mdx
index fe4d055..16235bf 100644
--- a/content/docs/godot/leaderboards.mdx
+++ b/content/docs/1.x/godot/leaderboards.mdx
@@ -108,7 +108,7 @@ func _on_props_rejected(rejected_props: Array[TaloRejectedProp]) -> void:
print("Rejected prop '%s': %s (%s)" % [prop.key, prop.message, prop.error])
```
-See [Players and props - Prop rejection reasons](/docs/godot/player-props#prop-rejection-reasons) for the full list of rejection reason codes.
+See [Players and props - Prop rejection reasons](/docs/1.x/godot/player-props#prop-rejection-reasons) for the full list of rejection reason codes.
## Getting entries
diff --git a/content/docs/godot/live-config.mdx b/content/docs/1.x/godot/live-config.mdx
similarity index 97%
rename from content/docs/godot/live-config.mdx
rename to content/docs/1.x/godot/live-config.mdx
index 267fb5c..7115684 100644
--- a/content/docs/godot/live-config.mdx
+++ b/content/docs/1.x/godot/live-config.mdx
@@ -58,4 +58,4 @@ func _on_live_config_updated(live_config: TaloLiveConfig) -> void:
label.text = live_config.get_prop("live_string", "Not set!")
```
-The `live_config_updated` signal is emitted (via the [Talo Socket](/docs/socket)) whenever the live config is updated.
+The `live_config_updated` signal is emitted (via the [Talo Socket](/docs/1.x/socket)) whenever the live config is updated.
diff --git a/content/docs/godot/meta.json b/content/docs/1.x/godot/meta.json
similarity index 94%
rename from content/docs/godot/meta.json
rename to content/docs/1.x/godot/meta.json
index 08d9cba..8232a00 100644
--- a/content/docs/godot/meta.json
+++ b/content/docs/1.x/godot/meta.json
@@ -1,7 +1,6 @@
{
"pages": [
"install",
- "upgrading-to-1.0",
"settings-reference",
"exporting",
"request-verification",
diff --git a/content/docs/godot/player-authentication.mdx b/content/docs/1.x/godot/player-authentication.mdx
similarity index 97%
rename from content/docs/godot/player-authentication.mdx
rename to content/docs/1.x/godot/player-authentication.mdx
index 1f44fe4..73a9f25 100644
--- a/content/docs/godot/player-authentication.mdx
+++ b/content/docs/1.x/godot/player-authentication.mdx
@@ -249,7 +249,7 @@ You can migrate a player's account to a different service and identifier using `
The original player auth account will be deleted and they will only be able to use their new identifier.
-Here is an example of migrating to a [Steam alias](/docs/godot/identifying#steamworks-integration):
+Here is an example of migrating to a [Steam alias](/docs/1.x/godot/identifying#steamworks-integration):
```gdscript
@onready var current_password: TextEdit = %CurrentPassword
@@ -274,7 +274,7 @@ func _migrate_account(ticket: String) -> void:
validation_label.text = Talo.player_auth.last_error.get_string()
```
-You can also migrate to the `google_play_games` service by providing an [auth code](/docs/godot/identifying#google-play-games-integration).
+You can also migrate to the `google_play_games` service by providing an [auth code](/docs/1.x/godot/identifying#google-play-games-integration).
When a migration is successful, the current session is cleared, the player's alias is updated to the new service and identifier, and the `Talo.players.identified` signal is emitted.
@@ -284,4 +284,4 @@ As shown in the examples above, whenever a request fails you can query the last
You can get the `TaloAuthError.ErrorCode` enum value using the `get_code()` function or the raw string using the `get_string()` function.
-You can view all the authentication errors and their descriptions [here](/docs/http/player-auth-api#error-codes).
+You can view all the authentication errors and their descriptions [here](/docs/1.x/http/player-auth-api#error-codes).
diff --git a/content/docs/godot/player-presence.mdx b/content/docs/1.x/godot/player-presence.mdx
similarity index 100%
rename from content/docs/godot/player-presence.mdx
rename to content/docs/1.x/godot/player-presence.mdx
diff --git a/content/docs/godot/player-props.mdx b/content/docs/1.x/godot/player-props.mdx
similarity index 98%
rename from content/docs/godot/player-props.mdx
rename to content/docs/1.x/godot/player-props.mdx
index 70111a9..9c0bb4b 100644
--- a/content/docs/godot/player-props.mdx
+++ b/content/docs/1.x/godot/player-props.mdx
@@ -37,7 +37,7 @@ print("Found %s results: %s" % [search_page.count, ", ".join(identifiers)])
Players can have a list of arbitrary properties that are persisted across all of their aliases. These props are identified by their unique key and can have any string value. Keys can be up to 128 characters long and values can be up to 512 characters long.
-All functions that modify props accept an optional `update` parameter (default `true`) that controls whether the player is synced with Talo after the change. Set it to `false` to batch multiple changes and avoid redundant [debounces](/docs/godot/settings-reference#debounce_timer_seconds).
+All functions that modify props accept an optional `update` parameter (default `true`) that controls whether the player is synced with Talo after the change. Set it to `false` to batch multiple changes and avoid redundant [debounces](/docs/1.x/godot/settings-reference#debounce_timer_seconds).
### Getting props
diff --git a/content/docs/godot/player-relationships.mdx b/content/docs/1.x/godot/player-relationships.mdx
similarity index 98%
rename from content/docs/godot/player-relationships.mdx
rename to content/docs/1.x/godot/player-relationships.mdx
index 27489e4..b61d813 100644
--- a/content/docs/godot/player-relationships.mdx
+++ b/content/docs/1.x/godot/player-relationships.mdx
@@ -33,7 +33,7 @@ A complete sample is available in the Godot plugin at `addons/talo/samples/frien
To subscribe to a player, you need to first decide what type of relationship you want to create: unidirectional or bidirectional. Unidirectional relationships create a subscription to the target player and bidirectional relationships will create a reciprocal subscription between both players.
-You also need to know the target player alias' ID. You can get this through [player presence updates](/docs/godot/player-presence), or by searching for players using [player search](/docs/godot/player-props#searching-for-players).
+You also need to know the target player alias' ID. You can get this through [player presence updates](/docs/1.x/godot/player-presence), or by searching for players using [player search](/docs/1.x/godot/player-props#searching-for-players).
Once you have both of these pieces of information, you can create the relationship:
diff --git a/content/docs/godot/request-verification.mdx b/content/docs/1.x/godot/request-verification.mdx
similarity index 100%
rename from content/docs/godot/request-verification.mdx
rename to content/docs/1.x/godot/request-verification.mdx
diff --git a/content/docs/godot/saves.mdx b/content/docs/1.x/godot/saves.mdx
similarity index 100%
rename from content/docs/godot/saves.mdx
rename to content/docs/1.x/godot/saves.mdx
diff --git a/content/docs/godot/settings-reference.mdx b/content/docs/1.x/godot/settings-reference.mdx
similarity index 92%
rename from content/docs/godot/settings-reference.mdx
rename to content/docs/1.x/godot/settings-reference.mdx
index e4384a8..410713e 100644
--- a/content/docs/godot/settings-reference.mdx
+++ b/content/docs/1.x/godot/settings-reference.mdx
@@ -38,7 +38,7 @@ At a minimum, the Talo Godot plugin requires the `read:players` and `write:playe
### api_url
-This is the location of the Talo API. The default cloud version is `https://api.trytalo.com`. If you're [self-hosting Talo](/docs/selfhosting/overview), this should be the address of your `backend` container.
+This is the location of the Talo API. The default cloud version is `https://api.trytalo.com`. If you're [self-hosting Talo](/docs/1.x/selfhosting/overview), this should be the address of your `backend` container.
### socket_url
@@ -77,11 +77,11 @@ If enabled, Talo will automatically cache the player after a successful online i
### continuity.enabled
-If enabled, Talo will try to automatically replay failed network requests - [learn more about Continuity here](/docs/godot/continuity).
+If enabled, Talo will try to automatically replay failed network requests - [learn more about Continuity here](/docs/1.x/godot/continuity).
### player_auth.auto_start_session
-If enabled and a valid session token is found, the player will be automatically authenticated. This is documented on the [Player Authentication page](/docs/godot/player-authentication#automatic-logins).
+If enabled and a valid session token is found, the player will be automatically authenticated. This is documented on the [Player Authentication page](/docs/1.x/godot/player-authentication#automatic-logins).
### logging.requests
@@ -103,7 +103,7 @@ It is strongly recommended that you do not set this value below the default. A l
### verification.enabled
-Enable request verification to cryptographically sign requests and socket messages. This must also be enabled in the Talo dashboard. Learn more on the [Request verification page](/docs/godot/request-verification).
+Enable request verification to cryptographically sign requests and socket messages. This must also be enabled in the Talo dashboard. Learn more on the [Request verification page](/docs/1.x/godot/request-verification).
### verification.key_version
diff --git a/content/docs/godot/socket.mdx b/content/docs/1.x/godot/socket.mdx
similarity index 82%
rename from content/docs/godot/socket.mdx
rename to content/docs/1.x/godot/socket.mdx
index 34e724e..b9c7983 100644
--- a/content/docs/godot/socket.mdx
+++ b/content/docs/1.x/godot/socket.mdx
@@ -11,7 +11,7 @@ Ideally you should never need to use the socket directly because individual serv
The socket connection is automatically established (this can disabled by setting `auto_connect_socket` to `false` in your config). When a player gets identified, they also get automatically identified with the socket server.
-You can learn more about how the socket works [here](/docs/sockets/intro).
+You can learn more about how the socket works [here](/docs/1.x/sockets/intro).
## Obtaining a socket ticket
@@ -23,7 +23,7 @@ Opening a socket connection using `Talo.socket.open_connection()` will automatic
## Receiving messages
-The `message_received` signal provides you with a [response](/docs/sockets/responses) and payload. For example, if you were building a chat system, you would connect a function similar to the one below to listen for new chat messages:
+The `message_received` signal provides you with a [response](/docs/1.x/sockets/responses) and payload. For example, if you were building a chat system, you would connect a function similar to the one below to listen for new chat messages:
```gdscript
func _ready() -> void:
@@ -38,7 +38,7 @@ func _on_message_received(res: String, data: Dictionary) -> void:
## Sending messages
-Sending messages is handled by functions in services like `Talo.channels.send_message()` where the correct data is prepared and sent for you. You can also use the `Talo.socket.send(req, data)` function to send your own [requests](/docs/sockets/requests):
+Sending messages is handled by functions in services like `Talo.channels.send_message()` where the correct data is prepared and sent for you. You can also use the `Talo.socket.send(req, data)` function to send your own [requests](/docs/1.x/sockets/requests):
```gdscript
func send_message(channel_id: int, message: String) -> void:
@@ -57,7 +57,7 @@ func send_message(channel_id: int, message: String) -> void:
-The socket server can disconnect for a number of reasons such as the player going offline or being [rate limited](/docs/sockets/common-errors#rate-limit-exceeded). The socket will emit a `connection_closed` signal with a status code and reason.
+The socket server can disconnect for a number of reasons such as the player going offline or being [rate limited](/docs/1.x/sockets/common-errors#rate-limit-exceeded). The socket will emit a `connection_closed` signal with a status code and reason.
The Talo socket automatically reconnects when the [connection is restored](continuity#connection_restored). If you need to manually re-open the socket, you can establish a connection and re-identify the player like this:
@@ -75,7 +75,7 @@ You can choose to manually end the socket connection using `Talo.socket.close_co
## Request verification
-If your game has [request verification](/docs/godot/request-verification) enabled, socket messages are automatically signed for players. The server validates these signatures to prevent replay attacks and tampering.
+If your game has [request verification](/docs/1.x/godot/request-verification) enabled, socket messages are automatically signed for players. The server validates these signatures to prevent replay attacks and tampering.
## Error handling
diff --git a/content/docs/godot/stats.mdx b/content/docs/1.x/godot/stats.mdx
similarity index 100%
rename from content/docs/godot/stats.mdx
rename to content/docs/1.x/godot/stats.mdx
diff --git a/content/docs/http/authentication.mdx b/content/docs/1.x/http/authentication.mdx
similarity index 76%
rename from content/docs/http/authentication.mdx
rename to content/docs/1.x/http/authentication.mdx
index 30b4a07..5bfffbc 100644
--- a/content/docs/http/authentication.mdx
+++ b/content/docs/1.x/http/authentication.mdx
@@ -28,15 +28,15 @@ Most endpoints perform actions on behalf of players.If you are using [player aut
The Godot plugin and Unity package will automatically populate these headers for you. If you're
receiving session-related errors, visit the [Common
- errors](/docs/http/common-errors#missing-or-invalid-session) page for more information.
+ errors](/docs/1.x/http/common-errors#missing-or-invalid-session) page for more information.
### Session token lifetimes
-By default, session tokens returned by [register, login, verify, and refresh](/docs/http/player-auth-api) are long-lived and do not need to be renewed. Your game can store the token on the device and reuse it for as long as the player keeps playing.
+By default, session tokens returned by [register, login, verify, and refresh](/docs/1.x/http/player-auth-api) are long-lived and do not need to be renewed. Your game can store the token on the device and reuse it for as long as the player keeps playing.
If you prefer shorter-lived sessions, you can opt in to the refresh token flow using the `withRefresh` parameter. When enabled:
- The session token expires after **15 minutes**.
- A **30-day refresh token** is returned alongside it.
-- Call the [refresh session](/docs/http/player-auth-api) endpoint with the refresh token to receive a new session and refresh token.
+- Call the [refresh session](/docs/1.x/http/player-auth-api) endpoint with the refresh token to receive a new session and refresh token.
diff --git a/content/docs/http/bruno-collection.mdx b/content/docs/1.x/http/bruno-collection.mdx
similarity index 83%
rename from content/docs/http/bruno-collection.mdx
rename to content/docs/1.x/http/bruno-collection.mdx
index 4d14fdf..173dbec 100644
--- a/content/docs/http/bruno-collection.mdx
+++ b/content/docs/1.x/http/bruno-collection.mdx
@@ -44,12 +44,12 @@ Most player-scoped endpoints expect two headers in addition to the API key:
1. `x-talo-player` - the player's ID
2. `x-talo-alias` - the ID of one of the player's identified alias
-To get these, run the [Identify](/docs/http/player-api) endpoint first (found in the **Players** folder of the collection). This will automatically set the variables for both of those headers.
+To get these, run the [Identify](/docs/1.x/http/player-api) endpoint first (found in the **Players** folder of the collection). This will automatically set the variables for both of those headers.
### Talo player authentication
-If your game uses [Talo's player authentication](/docs/http/player-auth-api), you'll also need the `x-talo-session` header.
+If your game uses [Talo's player authentication](/docs/1.x/http/player-auth-api), you'll also need the `x-talo-session` header.
Run the **register** and **login** requests (from the **Player auth** folder). This will set the `x-talo-session` variable and refresh token variable (for refreshing the short-lived session token).
-See [Player authentication](/docs/http/authentication#player-authentication) for the full flow.
+See [Player authentication](/docs/1.x/http/authentication#player-authentication) for the full flow.
diff --git a/content/docs/http/common-errors.mdx b/content/docs/1.x/http/common-errors.mdx
similarity index 100%
rename from content/docs/http/common-errors.mdx
rename to content/docs/1.x/http/common-errors.mdx
diff --git a/content/docs/http/dev-data.mdx b/content/docs/1.x/http/dev-data.mdx
similarity index 100%
rename from content/docs/http/dev-data.mdx
rename to content/docs/1.x/http/dev-data.mdx
diff --git a/content/docs/http/event-api.mdx b/content/docs/1.x/http/event-api.mdx
similarity index 100%
rename from content/docs/http/event-api.mdx
rename to content/docs/1.x/http/event-api.mdx
diff --git a/content/docs/http/game-channel-api.mdx b/content/docs/1.x/http/game-channel-api.mdx
similarity index 100%
rename from content/docs/http/game-channel-api.mdx
rename to content/docs/1.x/http/game-channel-api.mdx
diff --git a/content/docs/http/game-config-api.mdx b/content/docs/1.x/http/game-config-api.mdx
similarity index 100%
rename from content/docs/http/game-config-api.mdx
rename to content/docs/1.x/http/game-config-api.mdx
diff --git a/content/docs/http/game-feedback-api.mdx b/content/docs/1.x/http/game-feedback-api.mdx
similarity index 100%
rename from content/docs/http/game-feedback-api.mdx
rename to content/docs/1.x/http/game-feedback-api.mdx
diff --git a/content/docs/http/game-save-api.mdx b/content/docs/1.x/http/game-save-api.mdx
similarity index 100%
rename from content/docs/http/game-save-api.mdx
rename to content/docs/1.x/http/game-save-api.mdx
diff --git a/content/docs/http/game-stat-api.mdx b/content/docs/1.x/http/game-stat-api.mdx
similarity index 100%
rename from content/docs/http/game-stat-api.mdx
rename to content/docs/1.x/http/game-stat-api.mdx
diff --git a/content/docs/http/leaderboard-api.mdx b/content/docs/1.x/http/leaderboard-api.mdx
similarity index 100%
rename from content/docs/http/leaderboard-api.mdx
rename to content/docs/1.x/http/leaderboard-api.mdx
diff --git a/content/docs/http/meta.json b/content/docs/1.x/http/meta.json
similarity index 100%
rename from content/docs/http/meta.json
rename to content/docs/1.x/http/meta.json
diff --git a/content/docs/http/player-api.mdx b/content/docs/1.x/http/player-api.mdx
similarity index 100%
rename from content/docs/http/player-api.mdx
rename to content/docs/1.x/http/player-api.mdx
diff --git a/content/docs/http/player-auth-api.mdx b/content/docs/1.x/http/player-auth-api.mdx
similarity index 97%
rename from content/docs/http/player-auth-api.mdx
rename to content/docs/1.x/http/player-auth-api.mdx
index f32756e..be5d8e9 100644
--- a/content/docs/http/player-auth-api.mdx
+++ b/content/docs/1.x/http/player-auth-api.mdx
@@ -28,7 +28,7 @@ Learn more about player authentication [here](https://trytalo.com/players#authen
## Identifying players using Talo Authentication
-Once you have registered a player, you can identify them using the [identify](/docs/http/player-api) endpoint. You will need to ensure that:
+Once you have registered a player, you can identify them using the [identify](/docs/1.x/http/player-api) endpoint. You will need to ensure that:
1. The `identifier` is the one you used to register the player (email, username, etc.)
2. The `service` is set to `talo`
@@ -39,7 +39,7 @@ To create a new session token, you will need to go through the login flow again
By default session tokens are long-lived. You can opt in to short-lived (15-minute) session tokens
paired with a 30-day refresh token using the `withRefresh` parameter. See [Session token
- lifetimes](/docs/http/authentication#session-token-lifetimes) for the full details.
+ lifetimes](/docs/1.x/http/authentication#session-token-lifetimes) for the full details.
## Endpoints
diff --git a/content/docs/http/player-group-api.mdx b/content/docs/1.x/http/player-group-api.mdx
similarity index 100%
rename from content/docs/http/player-group-api.mdx
rename to content/docs/1.x/http/player-group-api.mdx
diff --git a/content/docs/http/player-presence-api.mdx b/content/docs/1.x/http/player-presence-api.mdx
similarity index 100%
rename from content/docs/http/player-presence-api.mdx
rename to content/docs/1.x/http/player-presence-api.mdx
diff --git a/content/docs/http/player-relationships-api.mdx b/content/docs/1.x/http/player-relationships-api.mdx
similarity index 100%
rename from content/docs/http/player-relationships-api.mdx
rename to content/docs/1.x/http/player-relationships-api.mdx
diff --git a/content/docs/http/request-verification.mdx b/content/docs/1.x/http/request-verification.mdx
similarity index 96%
rename from content/docs/http/request-verification.mdx
rename to content/docs/1.x/http/request-verification.mdx
index a79dad7..c6b7767 100644
--- a/content/docs/http/request-verification.mdx
+++ b/content/docs/1.x/http/request-verification.mdx
@@ -94,7 +94,7 @@ You can then send the signature in the `x-talo-signature` header alongside your
Enabling request verification also enables verification for socket messages.
-See the [socket request verification docs](/docs/sockets/request-verification) for details on how to prepend the signature to the message payload.
+See the [socket request verification docs](/docs/1.x/sockets/request-verification) for details on how to prepend the signature to the message payload.
## When verification applies
diff --git a/content/docs/http/socket-ticket-api.mdx b/content/docs/1.x/http/socket-ticket-api.mdx
similarity index 91%
rename from content/docs/http/socket-ticket-api.mdx
rename to content/docs/1.x/http/socket-ticket-api.mdx
index 4844793..0e33cb8 100644
--- a/content/docs/http/socket-ticket-api.mdx
+++ b/content/docs/1.x/http/socket-ticket-api.mdx
@@ -18,7 +18,7 @@ export const pageToc = [
Talo provides a secure socket system for real-time communication between your game and players. The Talo Socket powers services like [channels](https://trytalo.com/channels?utm_source=socket-docs&utm_medium=api-docs) and [player relationships](https://trytalo.com/player-relationships?utm_source=socket-docs&utm_medium=api-docs).
-To connect to the socket server, you need to obtain a socket ticket. Learn more about socket authentication [here](/docs/sockets/intro#authentication).
+To connect to the socket server, you need to obtain a socket ticket. Learn more about socket authentication [here](/docs/1.x/sockets/intro#authentication).
## Endpoints
diff --git a/content/docs/1.x/index.mdx b/content/docs/1.x/index.mdx
new file mode 100644
index 0000000..f992b4d
--- /dev/null
+++ b/content/docs/1.x/index.mdx
@@ -0,0 +1,28 @@
+---
+description: Talo is an open source game backend that makes it easy to add leaderboards, saves, analytics and more to your Godot or Unity game.
+title: Talo overview
+---
+
+## Choose your adventure
+
+
+
+
+
+
+
+
+
+
+## Everything you need in one place
+
+- **Gameplay** - Talo can manage leaderboards, save files, live config, player feedback (and more) for you.
+- **Game analytics** - track and analyse player behaviour to see what players are engaging with.
+- **Multiplayer** - create multiplayer interactions and subscribe to real-time updates using websockets.
+- **Players and data** - easily create, authenticate and manage player data in your game.
+
+## Open source first
+
+We're building a game backend that any developer can use and contribute to. Pull requests welcome!
+
+[Join the community on Discord](https://trytalo.com/discord).
\ No newline at end of file
diff --git a/content/docs/integrations/apple-game-center.mdx b/content/docs/1.x/integrations/apple-game-center.mdx
similarity index 82%
rename from content/docs/integrations/apple-game-center.mdx
rename to content/docs/1.x/integrations/apple-game-center.mdx
index 070e0fb..5e4c09e 100644
--- a/content/docs/integrations/apple-game-center.mdx
+++ b/content/docs/1.x/integrations/apple-game-center.mdx
@@ -17,4 +17,4 @@ To get started, enter your app's Bundle ID into the dashboard. Talo will use thi
To identify a player, fetch the local player's identity verification signature from Game Center and pass the values to Talo. Talo will cryptographically verify the signature against Apple's public key and automatically identify the player without them needing to create an account.
-Check out the [Godot plugin docs](/docs/godot/identifying#apple-game-center-integration) or [Unity package docs](/docs/unity/identifying#apple-game-center-integration) for more details.
+Check out the [Godot plugin docs](/docs/1.x/godot/identifying#apple-game-center-integration) or [Unity package docs](/docs/1.x/unity/identifying#apple-game-center-integration) for more details.
diff --git a/content/docs/integrations/google-play-games.mdx b/content/docs/1.x/integrations/google-play-games.mdx
similarity index 85%
rename from content/docs/integrations/google-play-games.mdx
rename to content/docs/1.x/integrations/google-play-games.mdx
index bf72155..4ed2055 100644
--- a/content/docs/integrations/google-play-games.mdx
+++ b/content/docs/1.x/integrations/google-play-games.mdx
@@ -19,4 +19,4 @@ To identify a player, [request a server auth code](https://developer.android.com
Talo will also add helpful `props` to your player such as their display name and avatar URL.
-Check out the [Godot plugin docs](/docs/godot/identifying#google-play-games-integration) or [Unity package docs](/docs/unity/identifying#google-play-games-integration) for more details.
+Check out the [Godot plugin docs](/docs/1.x/godot/identifying#google-play-games-integration) or [Unity package docs](/docs/1.x/unity/identifying#google-play-games-integration) for more details.
diff --git a/content/docs/integrations/meta.json b/content/docs/1.x/integrations/meta.json
similarity index 100%
rename from content/docs/integrations/meta.json
rename to content/docs/1.x/integrations/meta.json
diff --git a/content/docs/integrations/steamworks.mdx b/content/docs/1.x/integrations/steamworks.mdx
similarity index 96%
rename from content/docs/integrations/steamworks.mdx
rename to content/docs/1.x/integrations/steamworks.mdx
index 3c1f8a6..31d7c93 100644
--- a/content/docs/integrations/steamworks.mdx
+++ b/content/docs/1.x/integrations/steamworks.mdx
@@ -19,7 +19,7 @@ You'll need to pass the ticket generated by the [GetAuthTicketForWebApi](https:/
Talo will also add helpful `props` to your player such as app ownership details, profile names and avatars.
-Check out the [Godot plugin docs](/docs/godot/identifying#steamworks-integration) or [Unity package docs](/docs/unity/identifying#steamworks-integration) for more details.
+Check out the [Godot plugin docs](/docs/1.x/godot/identifying#steamworks-integration) or [Unity package docs](/docs/1.x/unity/identifying#steamworks-integration) for more details.
## Leaderboards
diff --git a/content/docs/1.x/meta.json b/content/docs/1.x/meta.json
new file mode 100644
index 0000000..bf64b72
--- /dev/null
+++ b/content/docs/1.x/meta.json
@@ -0,0 +1,6 @@
+{
+ "pages": ["index", "godot", "unity", "http", "sockets", "selfhosting", "integrations"],
+ "title": "1.x (latest)",
+ "root": true,
+ "defaultOpen": true
+}
\ No newline at end of file
diff --git a/content/docs/selfhosting/configuration.mdx b/content/docs/1.x/selfhosting/configuration.mdx
similarity index 100%
rename from content/docs/selfhosting/configuration.mdx
rename to content/docs/1.x/selfhosting/configuration.mdx
diff --git a/content/docs/selfhosting/meta.json b/content/docs/1.x/selfhosting/meta.json
similarity index 100%
rename from content/docs/selfhosting/meta.json
rename to content/docs/1.x/selfhosting/meta.json
diff --git a/content/docs/selfhosting/overview.mdx b/content/docs/1.x/selfhosting/overview.mdx
similarity index 100%
rename from content/docs/selfhosting/overview.mdx
rename to content/docs/1.x/selfhosting/overview.mdx
diff --git a/content/docs/sockets/common-errors.mdx b/content/docs/1.x/sockets/common-errors.mdx
similarity index 91%
rename from content/docs/sockets/common-errors.mdx
rename to content/docs/1.x/sockets/common-errors.mdx
index 280d777..34fa354 100644
--- a/content/docs/sockets/common-errors.mdx
+++ b/content/docs/1.x/sockets/common-errors.mdx
@@ -4,7 +4,7 @@ title: Common errors
## Missing authentication
-A valid socket ticket is required when opening a socket connection. Learn more about how to obtain a socket ticket and connect to the socket server [here](/docs/intro#connecting-to-the-socket-server).
+A valid socket ticket is required when opening a socket connection. Learn more about how to obtain a socket ticket and connect to the socket server [here](/docs/1.x/intro#connecting-to-the-socket-server).
Connections without a valid ticket will be closed immediately.
@@ -129,7 +129,7 @@ A request listener can throw an error specific to its own internal logic. These
}
```
-If your game has [request verification](/docs/sockets/request-verification) enabled, all socket messages sent after identifying a player must include a valid signature. If the signature is missing, malformed or fails Talo's validation, you will receive this error.
+If your game has [request verification](/docs/1.x/sockets/request-verification) enabled, all socket messages sent after identifying a player must include a valid signature. If the signature is missing, malformed or fails Talo's validation, you will receive this error.
## Rate limit exceeded
diff --git a/content/docs/sockets/custom-ping-pongs.mdx b/content/docs/1.x/sockets/custom-ping-pongs.mdx
similarity index 100%
rename from content/docs/sockets/custom-ping-pongs.mdx
rename to content/docs/1.x/sockets/custom-ping-pongs.mdx
diff --git a/content/docs/sockets/intro.mdx b/content/docs/1.x/sockets/intro.mdx
similarity index 85%
rename from content/docs/sockets/intro.mdx
rename to content/docs/1.x/sockets/intro.mdx
index 4222f8a..0df3f4b 100644
--- a/content/docs/sockets/intro.mdx
+++ b/content/docs/1.x/sockets/intro.mdx
@@ -7,7 +7,7 @@ title: Socket basics
The socket used by Talo follows the web socket standard and is compatible with libraries that also do the same.
-One major difference is that the Talo Socket will validate messages against a request type and payload structure. For example, if you send a message with the `v1.players.identified` request, your payload's data must also match the expected schema. The list of available [requests](/docs/requests) and [responses](/docs/responses) are available on their respective pages.
+One major difference is that the Talo Socket will validate messages against a request type and payload structure. For example, if you send a message with the `v1.players.identified` request, your payload's data must also match the expected schema. The list of available [requests](/docs/1.x/requests) and [responses](/docs/1.x/responses) are available on their respective pages.
Using the identification example, here is the JSON expected by the Talo Socket when identifying a player:
@@ -51,7 +51,7 @@ Responses use a similar message structure, except that `req` is replaced by `res
The Talo Socket provides two layers of authentication:
-1. You must provide a [socket ticket](/docs/http/socket-ticket-api) when opening a socket connection. This identifies your game and the key's scopes are used to determine whether a client is eligible to receive specific responses.
+1. You must provide a [socket ticket](/docs/1.x/http/socket-ticket-api) when opening a socket connection. This identifies your game and the key's scopes are used to determine whether a client is eligible to receive specific responses.
2. When players are identified, a `socketToken` is stored against the player alias for 1 hour. While the socket token is valid, you can use it to authenticate your player with the socket.
@@ -72,7 +72,7 @@ The default socket URL is `wss://api.trytalo.com`. When opening a socket connect
wss://api.trytalo.com/socket?ticket=your-ticket-here
```
-Your ticket should be a valid socket ticket, i.e. it was created within the last 5 minutes. Visit the [API docs](/docs/http/socket-ticket-api) for more information on how to obtain a socket ticket.
+Your ticket should be a valid socket ticket, i.e. it was created within the last 5 minutes. Visit the [API docs](/docs/1.x/http/socket-ticket-api) for more information on how to obtain a socket ticket.
## Error responses
@@ -103,4 +103,4 @@ Some errors may also return a "cause" key which drills down into why a request f
}
```
-For a more detailed look into the available error codes, visit the [common errors](/docs/common-errors) page.
+For a more detailed look into the available error codes, visit the [common errors](/docs/1.x/common-errors) page.
diff --git a/content/docs/sockets/meta.json b/content/docs/1.x/sockets/meta.json
similarity index 100%
rename from content/docs/sockets/meta.json
rename to content/docs/1.x/sockets/meta.json
diff --git a/content/docs/sockets/request-verification.mdx b/content/docs/1.x/sockets/request-verification.mdx
similarity index 100%
rename from content/docs/sockets/request-verification.mdx
rename to content/docs/1.x/sockets/request-verification.mdx
diff --git a/content/docs/sockets/requests.mdx b/content/docs/1.x/sockets/requests.mdx
similarity index 100%
rename from content/docs/sockets/requests.mdx
rename to content/docs/1.x/sockets/requests.mdx
diff --git a/content/docs/sockets/responses.mdx b/content/docs/1.x/sockets/responses.mdx
similarity index 100%
rename from content/docs/sockets/responses.mdx
rename to content/docs/1.x/sockets/responses.mdx
diff --git a/content/docs/unity/channels.mdx b/content/docs/1.x/unity/channels.mdx
similarity index 97%
rename from content/docs/unity/channels.mdx
rename to content/docs/1.x/unity/channels.mdx
index d378353..12553d9 100644
--- a/content/docs/unity/channels.mdx
+++ b/content/docs/1.x/unity/channels.mdx
@@ -268,7 +268,7 @@ private void OnChannelPropsRejected(RejectedProp[] rejectedProps)
**storage** prop errors, use `OnChannelStoragePropsFailedToSet` instead.
-See [Players and props - Prop rejection reasons](/docs/unity/player-props#prop-rejection-reasons) for the full list of rejection reason codes.
+See [Players and props - Prop rejection reasons](/docs/1.x/unity/player-props#prop-rejection-reasons) for the full list of rejection reason codes.
## Channel storage
@@ -311,7 +311,7 @@ void Start()
}
```
-`ChannelStoragePropError` includes a `message` with a human-readable description of the error, alongside the `error` code. See [Players and props - Prop rejection reasons](/docs/unity/player-props#prop-rejection-reasons) for the full list of rejection reason codes.
+`ChannelStoragePropError` includes a `message` with a human-readable description of the error, alongside the `error` code. See [Players and props - Prop rejection reasons](/docs/1.x/unity/player-props#prop-rejection-reasons) for the full list of rejection reason codes.
### Setting storage prop arrays
@@ -434,7 +434,7 @@ This function will return a `ChannelStorageProp` array, allowing you to iterate
#### Prop arrays
-If any of the keys you pass are [prop arrays](/docs/unity/channels#getting-storage-prop-arrays), append `[]` to the key to fetch every item stored under it. Scalar and array keys can be mixed in a single call:
+If any of the keys you pass are [prop arrays](/docs/1.x/unity/channels#getting-storage-prop-arrays), append `[]` to the key to fetch every item stored under it. Scalar and array keys can be mixed in a single call:
```csharp
// storage_prop_1 is a scalar, world_items[] is a prop array
diff --git a/content/docs/unity/continuity.mdx b/content/docs/1.x/unity/continuity.mdx
similarity index 100%
rename from content/docs/unity/continuity.mdx
rename to content/docs/1.x/unity/continuity.mdx
diff --git a/content/docs/unity/dev-data.mdx b/content/docs/1.x/unity/dev-data.mdx
similarity index 100%
rename from content/docs/unity/dev-data.mdx
rename to content/docs/1.x/unity/dev-data.mdx
diff --git a/content/docs/unity/events.mdx b/content/docs/1.x/unity/events.mdx
similarity index 100%
rename from content/docs/unity/events.mdx
rename to content/docs/1.x/unity/events.mdx
diff --git a/content/docs/unity/feedback.mdx b/content/docs/1.x/unity/feedback.mdx
similarity index 95%
rename from content/docs/unity/feedback.mdx
rename to content/docs/1.x/unity/feedback.mdx
index 1ba011b..7257669 100644
--- a/content/docs/unity/feedback.mdx
+++ b/content/docs/1.x/unity/feedback.mdx
@@ -107,4 +107,4 @@ private void OnPropsRejected(RejectedProp[] rejectedProps)
}
```
-See [Players and props - Prop rejection reasons](/docs/unity/player-props#prop-rejection-reasons) for the full list of rejection reason codes.
+See [Players and props - Prop rejection reasons](/docs/1.x/unity/player-props#prop-rejection-reasons) for the full list of rejection reason codes.
diff --git a/content/docs/unity/groups.mdx b/content/docs/1.x/unity/groups.mdx
similarity index 100%
rename from content/docs/unity/groups.mdx
rename to content/docs/1.x/unity/groups.mdx
diff --git a/content/docs/unity/identifying.mdx b/content/docs/1.x/unity/identifying.mdx
similarity index 94%
rename from content/docs/unity/identifying.mdx
rename to content/docs/1.x/unity/identifying.mdx
index 9a1b978..1250a08 100644
--- a/content/docs/unity/identifying.mdx
+++ b/content/docs/1.x/unity/identifying.mdx
@@ -50,7 +50,7 @@ You cannot use "Talo" for the `service` parameter as this is reserved for **Talo
If you are using Talo Player Authentication, `Talo.Players.Identify()` will be invoked automatically and the `Talo.Players.OnIdentified` event will also invoke as normal.
-Visit the [Player authentication docs](/docs/unity/player-authentication) to learn more about identifying players with authentication enabled.
+Visit the [Player authentication docs](/docs/1.x/unity/player-authentication) to learn more about identifying players with authentication enabled.
@@ -166,7 +166,7 @@ In the example above, the two players created with `Talo.Players.Identify()` are
page](https://dashboard.trytalo.com/integrations).
-If you have the Steamworks integration enabled, Talo can sync a Steam player ([as described here](/docs/integrations/steamworks#authentication)). You can do this via the `Talo.Players.IdentifySteam` function. Here's a modified version of an example [provided by Unity](https://docs.unity.com/ugs/en-us/manual/authentication/manual/platform-signin-steam) using Steamworks.NET:
+If you have the Steamworks integration enabled, Talo can sync a Steam player ([as described here](/docs/1.x/integrations/steamworks#authentication)). You can do this via the `Talo.Players.IdentifySteam` function. Here's a modified version of an example [provided by Unity](https://docs.unity.com/ugs/en-us/manual/authentication/manual/platform-signin-steam) using Steamworks.NET:
```csharp
Callback m_AuthTicketForWebApiResponseCallback;
@@ -201,7 +201,7 @@ The `identity` parameter is optional but strongly recommended as it ensures prop
### Steamworks player props
-After successfully authenticating the player, several [props](/docs/unity/player-props) will automatically be created for them:
+After successfully authenticating the player, several [props](/docs/1.x/unity/player-props) will automatically be created for them:
- `META_STEAMWORKS_VAC_BANNED` - "true" or "false"
- `META_STEAMWORKS_PUBLISHER_BANNED` - "true" or "false"
@@ -256,7 +256,7 @@ PlayGamesPlatform.Instance.RequestServerSideAccess(
### Google Play Games player props
-After successfully authenticating the player, these [props](/docs/unity/player-props) will automatically be created for them:
+After successfully authenticating the player, these [props](/docs/1.x/unity/player-props) will automatically be created for them:
- `META_GOOGLE_PLAY_GAMES_DISPLAY_NAME` - The player's chosen display name
- `META_GOOGLE_PLAY_GAMES_AVATAR_URL` - A direct link to the player's avatar
@@ -333,7 +333,7 @@ If the `cachePlayerOnIdentify` setting is enabled (default `true`), Talo will st
## Display names
-Every player alias has a `displayName` that provides a human-readable name for the player. You can read it from `Talo.CurrentAlias.displayName` after a player has been identified. Display names are resolved from a [player's props](/docs/unity/player-props) - arbitrary properties persisted across all of a player's aliases.
+Every player alias has a `displayName` that provides a human-readable name for the player. You can read it from `Talo.CurrentAlias.displayName` after a player has been identified. Display names are resolved from a [player's props](/docs/1.x/unity/player-props) - arbitrary properties persisted across all of a player's aliases.
```csharp title="WelcomeLabel.cs"
using TaloGameServices;
diff --git a/content/docs/unity/install.mdx b/content/docs/1.x/unity/install.mdx
similarity index 87%
rename from content/docs/unity/install.mdx
rename to content/docs/1.x/unity/install.mdx
index 8fdf67f..6a01c85 100644
--- a/content/docs/unity/install.mdx
+++ b/content/docs/1.x/unity/install.mdx
@@ -27,11 +27,6 @@ You can download the latest version of the Unity package from our [itch.io page]
Once downloaded, you can open the `talo.unitypackage` file to import it into your project.
-
- Upgrading from an older version of the package? Read the [upgrading to 1.0
- guide](/docs/unity/upgrading-to-1.0) for the breaking changes.
-
-
## Generate an API key
Visit [the Talo dashboard](https://dashboard.trytalo.com), login or create an account (and confirm your email address), and visit the API Keys page.
@@ -54,4 +49,4 @@ If you're self-hosting Talo, you can configure your custom endpoint here in the
### Settings reference
-You can visit the [settings reference](/docs/unity/settings-reference) for a detailed overview of Talo's configuration options.
+You can visit the [settings reference](/docs/1.x/unity/settings-reference) for a detailed overview of Talo's configuration options.
diff --git a/content/docs/unity/leaderboards.mdx b/content/docs/1.x/unity/leaderboards.mdx
similarity index 98%
rename from content/docs/unity/leaderboards.mdx
rename to content/docs/1.x/unity/leaderboards.mdx
index 9acdc55..58bcc0c 100644
--- a/content/docs/unity/leaderboards.mdx
+++ b/content/docs/1.x/unity/leaderboards.mdx
@@ -136,7 +136,7 @@ private void OnPropsRejected(RejectedProp[] rejectedProps)
}
```
-See [Players and props - Prop rejection reasons](/docs/unity/player-props#prop-rejection-reasons) for the full list of rejection reason codes.
+See [Players and props - Prop rejection reasons](/docs/1.x/unity/player-props#prop-rejection-reasons) for the full list of rejection reason codes.
## Getting entries
diff --git a/content/docs/unity/live-config.mdx b/content/docs/1.x/unity/live-config.mdx
similarity index 97%
rename from content/docs/unity/live-config.mdx
rename to content/docs/1.x/unity/live-config.mdx
index 46325f1..fdaf24a 100644
--- a/content/docs/unity/live-config.mdx
+++ b/content/docs/1.x/unity/live-config.mdx
@@ -77,4 +77,4 @@ void Start()
}
```
-The `OnLiveConfigUpdated` event is invoked (via the [Talo Socket](/docs/socket)) whenever the live config is updated.
+The `OnLiveConfigUpdated` event is invoked (via the [Talo Socket](/docs/1.x/socket)) whenever the live config is updated.
diff --git a/content/docs/unity/meta.json b/content/docs/1.x/unity/meta.json
similarity index 94%
rename from content/docs/unity/meta.json
rename to content/docs/1.x/unity/meta.json
index 3d283d0..8b881cd 100644
--- a/content/docs/unity/meta.json
+++ b/content/docs/1.x/unity/meta.json
@@ -1,7 +1,6 @@
{
"pages": [
"install",
- "upgrading-to-1.0",
"settings-reference",
"dev-data",
"request-verification",
diff --git a/content/docs/unity/player-authentication.mdx b/content/docs/1.x/unity/player-authentication.mdx
similarity index 97%
rename from content/docs/unity/player-authentication.mdx
rename to content/docs/1.x/unity/player-authentication.mdx
index 60b309e..976c24a 100644
--- a/content/docs/unity/player-authentication.mdx
+++ b/content/docs/1.x/unity/player-authentication.mdx
@@ -289,7 +289,7 @@ You can migrate a player's account to a different service and identifier using `
The original player auth account will be deleted and they will only be able to use their new identifier.
-Here is an example of migrating to a [Steam alias](/docs/unity/identifying#steamworks-integration):
+Here is an example of migrating to a [Steam alias](/docs/1.x/unity/identifying#steamworks-integration):
```csharp
Callback m_AuthTicketForWebApiResponseCallback;
@@ -318,7 +318,7 @@ async void OnAuthCallback(GetTicketForWebApiResponse_t callback)
}
```
-You can also migrate to the `google_play_games` service by providing an [auth code](/docs/unity/identifying#google-play-games-integration).
+You can also migrate to the `google_play_games` service by providing an [auth code](/docs/1.x/unity/identifying#google-play-games-integration).
When a migration is successful, the current session is cleared, the player's alias is updated to the new service and identifier, and the `OnPlayerIdentified` event is invoked.
@@ -328,4 +328,4 @@ As shown in the examples above, whenever a request fails with an authentication-
The exception provides a `ErrorCode` property that returns a matching `PlayerAuthErrorCode` enum value for the error response.
-You can view all the authentication errors and their descriptions [here](/docs/http/player-auth-api#error-codes).
+You can view all the authentication errors and their descriptions [here](/docs/1.x/http/player-auth-api#error-codes).
diff --git a/content/docs/unity/player-presence.mdx b/content/docs/1.x/unity/player-presence.mdx
similarity index 100%
rename from content/docs/unity/player-presence.mdx
rename to content/docs/1.x/unity/player-presence.mdx
diff --git a/content/docs/unity/player-props.mdx b/content/docs/1.x/unity/player-props.mdx
similarity index 98%
rename from content/docs/unity/player-props.mdx
rename to content/docs/1.x/unity/player-props.mdx
index 1c6d448..c33eda1 100644
--- a/content/docs/unity/player-props.mdx
+++ b/content/docs/1.x/unity/player-props.mdx
@@ -51,7 +51,7 @@ private async void SearchPlayers()
Players can have a list of arbitrary properties that are persisted across all of their aliases. These props are identified by their unique key and can have any string value. Keys can be up to 128 characters long and values can be up to 512 characters long.
-All functions that modify props accept an optional `update` parameter (default `true`) that controls whether the player is synced with Talo after the change. Set it to `false` to batch multiple changes and avoid redundant [debounces](/docs/unity/settings-reference#debouncetimerseconds).
+All functions that modify props accept an optional `update` parameter (default `true`) that controls whether the player is synced with Talo after the change. Set it to `false` to batch multiple changes and avoid redundant [debounces](/docs/1.x/unity/settings-reference#debouncetimerseconds).
### Getting props
diff --git a/content/docs/unity/player-relationships.mdx b/content/docs/1.x/unity/player-relationships.mdx
similarity index 98%
rename from content/docs/unity/player-relationships.mdx
rename to content/docs/1.x/unity/player-relationships.mdx
index cff2ba1..c8164db 100644
--- a/content/docs/unity/player-relationships.mdx
+++ b/content/docs/1.x/unity/player-relationships.mdx
@@ -33,7 +33,7 @@ A complete sample is available in the Unity package at `Assets/Talo Game Service
To subscribe to a player, you need to first decide what type of relationship you want to create: unidirectional or bidirectional. Unidirectional relationships create a subscription to the target player and bidirectional relationships will create a reciprocal subscription between both players.
-You also need to know the target player alias' ID. You can get this through [player presence updates](/docs/unity/player-presence), or by searching for players using [player search](/docs/unity/player-props#searching-for-players).
+You also need to know the target player alias' ID. You can get this through [player presence updates](/docs/1.x/unity/player-presence), or by searching for players using [player search](/docs/1.x/unity/player-props#searching-for-players).
Once you have both of these pieces of information, you can create the relationship:
diff --git a/content/docs/unity/request-verification.mdx b/content/docs/1.x/unity/request-verification.mdx
similarity index 100%
rename from content/docs/unity/request-verification.mdx
rename to content/docs/1.x/unity/request-verification.mdx
diff --git a/content/docs/unity/saves.mdx b/content/docs/1.x/unity/saves.mdx
similarity index 100%
rename from content/docs/unity/saves.mdx
rename to content/docs/1.x/unity/saves.mdx
diff --git a/content/docs/unity/settings-reference.mdx b/content/docs/1.x/unity/settings-reference.mdx
similarity index 88%
rename from content/docs/unity/settings-reference.mdx
rename to content/docs/1.x/unity/settings-reference.mdx
index 9da7043..ad489eb 100644
--- a/content/docs/unity/settings-reference.mdx
+++ b/content/docs/1.x/unity/settings-reference.mdx
@@ -23,7 +23,7 @@ At a minimum, the Talo Unity package requires the `read:players` and `write:play
### apiUrl
-This is the location of the Talo API. The default cloud version is `https://api.trytalo.com`. If you're [self-hosting Talo](/docs/selfhosting/overview), this should be the address of your `backend` container.
+This is the location of the Talo API. The default cloud version is `https://api.trytalo.com`. If you're [self-hosting Talo](/docs/1.x/selfhosting/overview), this should be the address of your `backend` container.
### socketUrl
@@ -37,7 +37,7 @@ If enabled, the package will automatically connect to the Talo Socket when the g
Talo uses the `OnApplicationQuit()` message to flush events before the game is closed. This is not available on the WebGL platform so a timer is used to flush events periodically instead. This setting controls how often events are flushed (in seconds).
-Learn more about [event flushing here](/docs/unity/events#flushing).
+Learn more about [event flushing here](/docs/1.x/unity/events#flushing).
### cachePlayerOnIdentify
@@ -45,11 +45,11 @@ If enabled, Talo will automatically cache the player after a successful online i
### continuityEnabled
-If enabled, Talo will try to automatically replay failed network requests - [learn more about Continuity here](/docs/unity/continuity).
+If enabled, Talo will try to automatically replay failed network requests - [learn more about Continuity here](/docs/1.x/unity/continuity).
### autoStartSession
-If enabled and a valid session token is found, the player will be automatically authenticated. This is documented on the [Player Authentication page](/docs/unity/player-authentication#automatic-logins).
+If enabled and a valid session token is found, the player will be automatically authenticated. This is documented on the [Player Authentication page](/docs/1.x/unity/player-authentication#automatic-logins).
### logRequests
@@ -71,7 +71,7 @@ It is strongly recommended that you do not set this value below the default. A l
### verificationEnabled
-Enable request verification to cryptographically sign requests and socket messages. This must also be enabled in the Talo dashboard. Learn more on the [Request verification page](/docs/unity/request-verification).
+Enable request verification to cryptographically sign requests and socket messages. This must also be enabled in the Talo dashboard. Learn more on the [Request verification page](/docs/1.x/unity/request-verification).
### verificationKeyVersion
diff --git a/content/docs/unity/socket.mdx b/content/docs/1.x/unity/socket.mdx
similarity index 83%
rename from content/docs/unity/socket.mdx
rename to content/docs/1.x/unity/socket.mdx
index 05f847d..abee849 100644
--- a/content/docs/unity/socket.mdx
+++ b/content/docs/1.x/unity/socket.mdx
@@ -11,7 +11,7 @@ Ideally you should never need to use the socket directly because individual serv
The socket connection is automatically established (this can disabled by setting `autoConnectSocket` to `false` in your config). When a player gets identified, they also get automatically identified with the socket server.
-You can learn more about how the socket works [here](/docs/sockets/intro).
+You can learn more about how the socket works [here](/docs/1.x/sockets/intro).
## Obtaining a socket ticket
@@ -23,7 +23,7 @@ Opening a socket connection using `Talo.Socket.OpenConnection()` will automatica
## Receiving messages
-The `OnMessageReceived` event provides you with a [response](/docs/sockets/responses) and payload. For example, if you were building a chat system, you would connect a function similar to the one below to listen for new chat messages:
+The `OnMessageReceived` event provides you with a [response](/docs/1.x/sockets/responses) and payload. For example, if you were building a chat system, you would connect a function similar to the one below to listen for new chat messages:
```csharp
private void Start()
@@ -51,7 +51,7 @@ private void OnMessageReceived(SocketResponse response)
## Sending messages
-Sending messages is handled by functions in services like `Talo.Channels.SendMessage()` where the correct data is prepared and sent for you. You can also use the `Talo.Socket.Send()` function to send your own [requests](/docs/sockets/requests):
+Sending messages is handled by functions in services like `Talo.Channels.SendMessage()` where the correct data is prepared and sent for you. You can also use the `Talo.Socket.Send()` function to send your own [requests](/docs/1.x/sockets/requests):
```csharp
public void SendMessage(int channelId, string message)
@@ -72,7 +72,7 @@ public void SendMessage(int channelId, string message)
-The socket server can disconnect for a number of reasons such as the player going offline or being [rate limited](/docs/sockets/common-errors#rate-limit-exceeded). The socket will invoke a `OnConnectionClosed` event with a status code and reason.
+The socket server can disconnect for a number of reasons such as the player going offline or being [rate limited](/docs/1.x/sockets/common-errors#rate-limit-exceeded). The socket will invoke a `OnConnectionClosed` event with a status code and reason.
The Talo socket automatically reconnects when the [connection is restored](continuity#onconnectionrestored). If you need to manually re-open the socket, you can establish a connection and re-identify the player like this:
@@ -94,7 +94,7 @@ You can choose to manually end the socket connection using `Talo.Socket.CloseCon
## Request verification
-If your game has [request verification](/docs/unity/request-verification) enabled, socket messages are automatically signed for players. The server validates these signatures to prevent replay attacks and tampering.
+If your game has [request verification](/docs/1.x/unity/request-verification) enabled, socket messages are automatically signed for players. The server validates these signatures to prevent replay attacks and tampering.
## Error handling
diff --git a/content/docs/unity/stats.mdx b/content/docs/1.x/unity/stats.mdx
similarity index 100%
rename from content/docs/unity/stats.mdx
rename to content/docs/1.x/unity/stats.mdx
diff --git a/content/docs/godot/upgrading-to-1.0.mdx b/content/docs/godot/upgrading-to-1.0.mdx
deleted file mode 100644
index ba9f681..0000000
--- a/content/docs/godot/upgrading-to-1.0.mdx
+++ /dev/null
@@ -1,269 +0,0 @@
----
-description: Upgrade the Talo Godot plugin to 1.0 and migrate code from v0.49.1 and earlier.
-title: Upgrading to 1.0
----
-## How to upgrade
-
-1. Download the latest release from the [Godot Asset Library](https://godotengine.org/asset-library/asset/2936), [itch.io](https://sleepystudios.itch.io/talo-godot) or [GitHub releases](https://github.com/TaloDev/godot/releases).
-2. Replace your existing `addons/talo` folder with the new one.
-3. Go through each section below and update any code that uses the affected APIs.
-
-## Identifying players
-
-### Player aliases
-
-`identify()`, `identify_steam()`, `identify_google_play_games()`, `identify_game_center()` and `identify_offline()` return a `TaloPlayerAlias` instead of a `TaloPlayer` ([#223](https://github.com/TaloDev/godot/pull/223)).
-
-You can access the underlying player via `alias.player`:
-
-```gdscript
-# before
-var player: TaloPlayer = await Talo.players.identify("username", "bob")
-func _on_identified(_player: TaloPlayer) -> void: ...
-
-# after
-var alias: TaloPlayerAlias = await Talo.players.identify("username", "bob")
-func _on_identified(player_alias: TaloPlayerAlias) -> void: ...
-```
-
-### Identification errors
-
-The `identification_failed` signal now passes a `TaloIdentifyError` with a `code` enum (`UNKNOWN_ERROR`, `IDENTIFIER_PROFANITY`, `IDENTIFIER_TAKEN`) ([#224](https://github.com/TaloDev/godot/pull/224)):
-
-```gdscript
-Talo.players.identification_failed.connect(func (error: TaloIdentifyError):
- push_error("Identification failed: %s" % error.code)
- go_to_login()
-)
-```
-
-## Player props
-
-### Awaitable prop updates
-
-All six prop mutators (`set_prop`, `delete_prop`, `set_prop_array`, `delete_prop_array`, `insert_into_prop_array`, `remove_from_prop_array`) now return a signal you can `await`, which resolves with a new `PlayerUpdateResult` (`success`, `rejected_props`) after the debounce settles ([#232](https://github.com/TaloDev/godot/pull/232)):
-
-```gdscript
-# fire-and-forget (still works - the return value can be ignored)
-Talo.current_player.set_prop("xp", "100")
-
-# await the settle result
-var result: PlayersAPI.PlayerUpdateResult = await Talo.current_player.set_prop("xp", "100")
-# result.success - whether the update was successful
-# result.rejected_props - which props Talo rejected (if any)
-
-# a local update (no network request); returns a valid result
-var local := Talo.current_player.set_prop("xp", "100", false)
-```
-
-### Rejected props
-
-The `Talo.players.props_rejected` signal has been removed - rejected props come back on the result ([#233](https://github.com/TaloDev/godot/pull/233)).
-
-### Trailing-only debounce
-
-Debounce timers are now **trailing-only** - the leading-edge mode and its `leading` constructor param were removed ([#234](https://github.com/TaloDev/godot/pull/234)). Pending player and save updates are now flushed when the game quits, so queued/in-flight updates are no longer dropped.
-
-## Player authentication
-
-### Result objects
-
-Every auth method (`register`, `login`, `verify`, `change_password`, `forgot_password`, `migrate_account`, etc.) now returns a **result object** instead of a bare `Error`.
-
-Errors now live inside this result object and `Talo.player_auth.last_error` has been removed ([#228](https://github.com/TaloDev/godot/pull/228)):
-
-```gdscript
-# before
-var res := await Talo.player_auth.register(username.text, password.text, email.text)
-if res != OK:
- match Talo.player_auth.last_error.get_code():
- TaloAuthError.ErrorCode.IDENTIFIER_TAKEN:
- validation_label.text = "Username is already taken"
- _:
- validation_label.text = Talo.player_auth.last_error.get_string()
-
-# after
-var res := await Talo.player_auth.register(username.text, password.text, email.text, verification_enabled)
-if not res.success:
- match res.error.code:
- TaloPlayerAuthError.ErrorCode.IDENTIFIER_TAKEN:
- validation_label.text = "Username is already taken"
- _:
- validation_label.text = res.error.message
-```
-
-### Login verification
-
-The `LoginResult` enum has been removed. `login()` returns a `PlayerAuthLoginResult` - check `verification_required` instead:
-
-```gdscript
-# before
-var res := await Talo.player_auth.login(username.text, password.text)
-match res:
- Talo.player_auth.LoginResult.VERIFICATION_REQUIRED:
- verification_required.emit()
- Talo.player_auth.LoginResult.FAILED: ...
-
-# after
-var res := await Talo.player_auth.login(username.text, password.text)
-if res.verification_required:
- verification_required.emit()
-elif not res.success:
- match res.error.code: ...
-```
-
-## Leaderboards
-
-### Current player entries
-
-The deprecated `get_entries_for_current_player()` and `get_cached_entries_for_current_player()` have been removed ([#220](https://github.com/TaloDev/godot/pull/220), [#221](https://github.com/TaloDev/godot/pull/221)).
-
-Use the options-based methods with `player_id` or `alias_id` filtering instead:
-
-```gdscript
-# before
-var entries := Talo.leaderboards.get_entries_for_current_player(internal_name)
-var cached := Talo.leaderboards.get_cached_entries_for_current_player(internal_name)
-
-# after
-var options := Talo.leaderboards.GetEntriesOptions.new()
-options.player_id = Talo.current_player.id
-var res := await Talo.leaderboards.get_entries(internal_name, options)
-
-var cached_options := Talo.leaderboards.GetCachedEntriesOptions.new()
-cached_options.player_id = Talo.current_player.id
-var cached := Talo.leaderboards.get_cached_entries(internal_name, cached_options)
-```
-
-### Rejected entry props
-
-The `props_rejected` signal has been removed. `add_entry()` now returns an `AddEntryResult` with `success`, `entry`, `updated` and `rejected_props` ([#233](https://github.com/TaloDev/godot/pull/233), [#235](https://github.com/TaloDev/godot/pull/235)):
-
-```gdscript
-# before
-Talo.leaderboards.props_rejected.connect(_on_props_rejected)
-func _on_props_rejected(rejected_props: Array[TaloRejectedProp]):
- for prop in rejected_props:
- print("Rejected prop '%s': %s (%s)" % [prop.key, prop.message, prop.error])
-
-# after
-var res := await Talo.leaderboards.add_entry(internal_name, score)
-if not res.success:
- for prop in res.rejected_props:
- print("Rejected prop '%s': %s (%s)" % [prop.key, prop.message, prop.code])
-```
-
-## Channels
-
-### Updating channels
-
-`update()` now takes an `UpdateChannelOptions` object instead of positional arguments. `create()` and `update()` now return a `ChannelUpsertResult` (`success`, `channel`, `rejected_props`) ([#231](https://github.com/TaloDev/godot/pull/231)):
-
-```gdscript
-# before
-var channel := await Talo.channels.create(options)
-await Talo.channels.update(channel_id, "new name", 123, { team = "red" })
-
-# after
-var options := Talo.channels.UpdateChannelOptions.new()
-options.name = "new name"
-options.new_owner_alias_id = 123
-options.props = { team = "red" }
-var result := await Talo.channels.update(channel_id, options)
-if not result.success:
- for prop in result.rejected_props: ...
- print(result.channel.name)
-```
-
-`UpdateChannelOptions` also adds tri-state toggles (`auto_cleanup`, `private`, `temporary_membership`) so you can leave a field unchanged or explicitly set it to `true`/`false`.
-
-### Rejected channel props
-
-The `channel_props_rejected` signal has been removed - rejected props come back on the result object ([#233](https://github.com/TaloDev/godot/pull/233)).
-
-The `channel_storage_props_failed_to_set` signal still exists but now emits `Array[TaloRejectedProp]` instead of `Array[TaloChannelStoragePropError]` ([#222](https://github.com/TaloDev/godot/pull/222)):
-
-```gdscript
-# before
-func _on_failed(channel: TaloChannel, failed_props: Array[TaloChannelStoragePropError]):
- for prop in failed_props:
- print("%s: %s (%s)" % [prop.key, prop.message, prop.error])
-
-# after
-func _on_failed(channel: TaloChannel, failed_props: Array[TaloRejectedProp]):
- for prop in failed_props:
- print("%s: %s (%s)" % [prop.key, prop.message, prop.code])
-```
-
-## Saves
-
-### Updating the current save
-
-`update_current_save()` now returns a `SaveUpdateResult` (`success`, `save`) on **both** the debounced content-sync path and the immediate rename path ([#232](https://github.com/TaloDev/godot/pull/232)).
-
-### Updating a save
-
-`update_save(save, new_name)` is unchanged in signature but now returns `null` on a non-200 response (previously it returned the local save, masking failures) ([#232](https://github.com/TaloDev/godot/pull/232)).
-
-## Feedback
-
-### Sending feedback
-
-`send()` now returns a `FeedbackSendResult` (`success`, `rejected_props`) instead of `void`, and the `props_rejected` signal has been removed ([#233](https://github.com/TaloDev/godot/pull/233)):
-
-```gdscript
-# before
-await Talo.feedback.send(category, comment, props)
-
-# after
-var result := await Talo.feedback.send(category, comment, props)
-if not result.success:
- for prop in result.rejected_props:
- print("Rejected prop '%s': %s (%s)" % [prop.key, prop.message, prop.code])
-```
-
-## Error classes
-
-### Error class renames
-
-- `TaloAuthError` has been renamed to `TaloPlayerAuthError` ([#225](https://github.com/TaloDev/godot/pull/225)).
-- `get_code()` / `get_string()` have been replaced by the typed `code: ErrorCode` and `message: String` properties ([#226](https://github.com/TaloDev/godot/pull/226)).
-- `.error` has been renamed to `.code` on every error class, and `TaloRejectedProp.RejectionReason` is now `TaloRejectedProp.ErrorCode` ([#227](https://github.com/TaloDev/godot/pull/227)).
-
-```gdscript
-# before
-prop.error # TaloRejectedProp.RejectionReason.PROP_VALUE_TOO_LONG
-Talo.player_auth.last_error.get_string()
-
-# after
-prop.code # TaloRejectedProp.ErrorCode.PROP_VALUE_TOO_LONG
-res.error.message # TaloPlayerAuthError.message
-```
-
-### Channel storage errors
-
-`TaloChannelStoragePropError` has been removed - channel storage failures now use the shared `TaloRejectedProp` class ([#222](https://github.com/TaloDev/godot/pull/222)).
-
-## Quick reference
-
-| Removed / renamed | Replacement |
-| ----------------------------------------------------------------- | ------------------------------------------------------------------------- |
-| `Talo.players.identify()` → `TaloPlayer` | `Talo.players.identify()` → `TaloPlayerAlias` (use `.player`) |
-| `Talo.players.props_rejected` | `PlayerUpdateResult.rejected_props` |
-| Prop editing on players, e.g. `set_prop()`, returns `void` | `Talo.current_player.set_prop()` is awaitable → `PlayerUpdateResult` |
-| `TaloDebounceTimer(..., leading)` | leading mode removed (trailing-only) |
-| `Talo.player_auth.last_error` | `.error` on the returned result |
-| `Talo.player_auth.LoginResult` enum | `PlayerAuthLoginResult` + `verification_required` |
-| `Talo.leaderboards.get_entries_for_current_player()` | `Talo.leaderboards.get_entries()` + `player_id`/`alias_id` options |
-| `Talo.leaderboards.get_cached_entries_for_current_player()` | `Talo.leaderboards.get_cached_entries()` + `player_id`/`alias_id` options |
-| `Talo.leaderboards.props_rejected` | `AddEntryResult.rejected_props` |
-| `Talo.channels.update(id, name, owner, props)` | `Talo.channels.update(id, UpdateChannelOptions)` |
-| `Talo.channels.channel_props_rejected` | `ChannelUpsertResult.rejected_props` |
-| `Talo.saves.update_current_save()` → `TaloGameSave` | `Talo.saves.update_current_save()` → `SaveUpdateResult` |
-| `Talo.saves.update_save()` returns local save on failure | `Talo.saves.update_save()` returns `null` on non-200 |
-| `Talo.feedback.props_rejected` | `FeedbackSendResult.rejected_props` |
-| `TaloAuthError` | `TaloPlayerAuthError` |
-| `get_code()` / `get_string()` on `TaloPlayerAuthError` | `.code` / `.message` |
-| `TaloRejectedProp.RejectionReason` | `TaloRejectedProp.ErrorCode` |
-| `.error` property on error classes (e.g. `TaloRejectedProp`) | `.code` (e.g. `prop.error` → `prop.code`) |
-| `TaloChannelStoragePropError` | `TaloRejectedProp` |
\ No newline at end of file
diff --git a/content/docs/index.mdx b/content/docs/index.mdx
index d3832f8..9164656 100644
--- a/content/docs/index.mdx
+++ b/content/docs/index.mdx
@@ -6,12 +6,12 @@ title: Talo overview
## Choose your adventure
-
-
-
-
-
-
+
+
+
+
+
+
## Everything you need in one place
diff --git a/content/docs/meta.json b/content/docs/meta.json
index 7627f58..438faa1 100644
--- a/content/docs/meta.json
+++ b/content/docs/meta.json
@@ -1,3 +1,3 @@
{
- "pages": ["index", "godot", "unity", "http", "sockets", "selfhosting", "integrations"]
-}
+ "pages": ["index", "1.x", "pre-1.0"]
+}
\ No newline at end of file
diff --git a/content/docs/pre-1.0/godot/channels.mdx b/content/docs/pre-1.0/godot/channels.mdx
new file mode 100644
index 0000000..b30d716
--- /dev/null
+++ b/content/docs/pre-1.0/godot/channels.mdx
@@ -0,0 +1,476 @@
+---
+description: Talo Channels can be used for player chats, sending data to specific groups, pushing game updates and storing shared game state.
+title: Channels and storage
+---
+
+import { ScopeBadges } from '@/components/documentation/ScopeBadges'
+
+## How channels work
+
+Channels allow you to send socket messages between players, making it extremely easy to add peer-to-peer (P2P) multiplayer mechanics.
+
+When players join a channel, they get subscribed to that channel's messages. Messages can be anything: plain strings for chat, enums for instructions/events or even JSON data.
+
+Channels can also store data inside special props that all subscribed players can modify.
+
+
+ Visit the [Channels feature page](https://trytalo.com/channels) for more details.
+
+
+## Listing channels
+
+
+
+To get all available channels for your game, use the `Talo.channels.get_channels()` function. This is a paginated function which takes a single `options` parameter.
+
+This function returns an array with 3 items: the channels, the total number of channels (if they weren't paginated) and if this is the last page.
+
+```gdscript
+var options := Talo.channels.GetChannelsOptions.new()
+options.page = 0
+var res := await Talo.channels.get_channels(options)
+
+var channels: Array[GameChannel] = res.channels
+var count: int = res.count
+var is_last_page: bool = res.is_last_page
+```
+
+### Filtering by prop keys and values
+
+The following code will only fetch channels that have the "guildId" key:
+
+```gdscript
+var options := Talo.channels.GetChannelsOptions.new()
+options.page = 0
+options.prop_key = "guildId"
+var res := await Talo.channels.get_channels(options)
+```
+
+You can also filter by a prop value. This code will now make sure there is a "guildId" key and its value is "157":
+
+```gdscript
+var options := Talo.channels.GetChannelsOptions.new()
+options.page = 0
+options.prop_key = "guildId"
+options.prop_value = "157"
+var res := await Talo.channels.get_channels(options)
+```
+
+### Searching for channels
+
+You can search for channels by name or owner identifier using the `search` option:
+
+```gdscript
+var options := Talo.channels.GetChannelsOptions.new()
+options.page = 0
+options.search = "search term"
+var res := await Talo.channels.get_channels(options)
+```
+
+## Listing subscribed channels
+
+
+
+You can use `Talo.channels.get_subscribed_channels()` to find out which channels the current player is subscribed to. This returns an array of `TaloChannel`.
+
+### Filtering by prop keys and values
+
+Similar to `get_channels()`, you can filter by prop keys and values by providing options:
+
+```gdscript
+var options := Talo.channels.GetSubscribedChannelsOptions.new()
+options.prop_key = "guildId"
+options.prop_value = "157"
+var res := await Talo.channels.get_subscribed_channels(options)
+```
+
+## Finding a channel
+
+
+
+You can find a channel by its ID using `Talo.channels.find()`. This function takes a channel ID integer and returns a `TaloChannel` object.
+
+## Creating a channel
+
+
+
+To create a channel, call `Talo.channels.create()` with a channel name and (optionally) the auto cleanup value and/or props.
+
+When auto cleanup is enabled, the channel will be deleted when the owner or the last subscribed member leaves.
+
+Props (a dictionary of string key/value pairs) are a way of adding arbitrary data to your channels in the same way as you would for events, players and leaderboards.
+
+```gdscript
+var options := Talo.channels.CreateChannelOptions.new()
+options.name = "channel name"
+options.auto_cleanup = true
+options.props: Dictionary[String, String] = {
+ prop_key = "prop_value"
+}
+var channel := await Talo.channels.create(options)
+
+print(channel.name) # channel name
+print(channel.auto_cleanup) # true
+print(channel.props) # [{ "prop_key": "prop_value" }]
+```
+
+## Joining and leaving channels
+
+
+
+To join or leave a channel, use `Talo.channels.join()` and `Talo.channels.leave()` respectively. Both functions take the ID of the channel as the only parameter.
+
+If you attempt to join a channel and the current player is already in that channel, nothing will happen. Leaving a channel also follows the same pattern.
+
+## Updating channels
+
+
+
+The owner of a channel can update the channel using `Talo.channels.update()`. The name and owner of the channel can be updated using this function.
+
+To transfer ownership of the channel, you should specify the ID of the player alias that will own the channel. The new owner of the channel must be a member first before ownership can be transferred.
+
+You can also update the props of the channel: keys will be overrided with new values and keys with a value of `null` will be deleted.
+
+## Deleting channels
+
+
+
+The owner of a channel can delete the channel using `Talo.channels.delete()`. All other members of the channel will be unsubscribed automatically.
+
+## Private channels
+
+You can also create invite-only private channels using the `private` option:
+
+```gdscript
+var options := Talo.channels.CreateChannelOptions.new()
+options.name = "channel name"
+options.auto_cleanup = true
+options.private = true
+
+var channel := await Talo.channels.create(options)
+print(channel.private) # true
+```
+
+Private channels will not be listed when using `Talo.channels.get_channels()`. They also cannot be joined in the same way: the channel owner must invite players to a private channel.
+
+### Channel invites
+
+
+
+To create a channel invite, use `Talo.channels.invite()` with a channel ID and player alias ID.
+
+Invited players will automatically join the channel.
+
+```gdscript
+var options := Talo.channels.CreateChannelOptions.new()
+options.name = "channel name"
+options.auto_cleanup = true
+options.private = true
+
+var channel := await Talo.channels.create(options)
+await Talo.channels.invite(channel.id, invitee_player_alias.id)
+```
+
+
+ You can use invites for public channels too.
+
+
+## Temporary membership channels
+
+If players should only be members of a channel while they're online, you can choose to enable the `temporary_membership` option when creating your channel:
+
+```gdscript
+var options := Talo.channels.CreateChannelOptions.new()
+options.name = "channel name"
+options.temporary_membership = true
+
+var channel := await Talo.channels.create(options)
+```
+
+Any player that joins the channel and then goes offline will automatically be removed from the channel. The `Talo.channels.player_left` signal will emit with the reason `Talo.channels.ChannelLeavingReason.TEMPORARY_MEMBERSHIP`.
+
+## Getting channel members
+
+
+
+You can fetch a list of channel members using `Talo.channels.get_members()`. This will return an `Talo.channels.MembersPage`:
+
+```gdscript
+var members_page := await Talo.channels.get_members(channel.id)
+print(members_page.members.map((func (member): return member.identifier)))
+```
+
+
+ The current player can only fetch channel members for channels they are part of.
+
+
+### Filtering channel members
+
+You can provide a `Talo.channels.GetMembersOptions` as the second parameter of `get_members()` to provide extra filters:
+
+```gdscript
+var options := Talo.channels.GetMembersOptions.new()
+options.page = 2
+options.prop_key = "guildId"
+options.prop_value = "5"
+var members_page := await Talo.channels.get_members(channel.id, options)
+
+var filtered_members := members_page.members
+var is_last_page := members_page.is_last_page
+```
+
+You can provide the following filtering options:
+
+- `page`: the current pagination index
+- `player_id`: find channel members with this player ID
+- `alias_id`: find a channel member with this ID
+- `identifier`: find a channel member with this identifier
+- `prop_key`: find channel members with this prop key
+- `prop_value`: find channel members with a matching `prop_key` and `prop_value`
+- `player_group_id`: find channel members that are part of this group
+
+## Listening for messages
+
+
+
+To listen for messages, you can use the `Talo.channels.message_received` signal. This signal will return the `TaloPlayerChannel`, the sender's `TaloPlayerAlias` and the message.
+
+Here's an example of how to use this signal to update a chat UI:
+
+```gdscript
+func _ready():
+ Talo.channels.message_received.connect(_on_message_received)
+
+func _on_message_received(channel: TaloChannel, player_alias: TaloPlayerAlias, message: String) -> void:
+ if channel.id == _active_channel_id:
+ _add_chat_message("[%s] %s: %s" % [channel.name, player_alias.identifier, message])
+
+```
+
+### Listening for other signals
+
+You can also listen for the following signals:
+
+- `Talo.channels.player_joined`: Emitted when a player joins a channel. Returns the `TaloChannel` and the `TaloPlayerAlias` that joined.
+
+- `Talo.channels.player_left`: Emitted when a player leaves a channel. Returns the `TaloChannel`, the `TaloPlayerAlias` that left and a `Talo.channels.ChannelLeavingReason`.
+
+- `Talo.channels.channel_ownership_transferred`: Emitted when channel ownership is transferred. Returns the `TaloChannel` and the new owner's `TaloPlayerAlias`.
+
+- `Talo.channels.channel_deleted`: Emitted when a channel is deleted. Returns the `TaloChannel` that was deleted.
+
+- `Talo.channels.channel_updated`: Emitted when a channel is updated. Returns the `TaloChannel` that was updated and an `Array[String]` of properties that were changed.
+
+- `Talo.channels.channel_props_rejected`: Emitted when channel props are rejected during creation or update. Returns an array of `TaloRejectedProp` objects. See [Handling rejected props](#handling-rejected-channel-props) for more details.
+
+## Handling rejected channel props
+
+Talo validates props when creating or updating channels. If one or more props are invalid, the channel will not be created or updated.
+
+You can listen for rejected props by connecting to the `Talo.channels.channel_props_rejected` signal:
+
+```gdscript
+func _ready() -> void:
+ Talo.channels.channel_props_rejected.connect(_on_channel_props_rejected)
+
+func _on_channel_props_rejected(rejected_props: Array[TaloRejectedProp]) -> void:
+ for prop in rejected_props:
+ # e.g. "Rejected prop 'guild_name': Prop key exceeds 128 characters (PROP_KEY_TOO_LONG)"
+ print("Rejected prop '%s': %s (%s)" % [prop.key, prop.message, prop.error])
+```
+
+
+ This signal only fires for channel props (set during `create()` and `update()`). For channel
+ **storage** prop errors, use `channel_storage_props_failed_to_set` instead.
+
+
+See [Players and props - Prop rejection reasons](/docs/pre-1.0/godot/player-props#prop-rejection-reasons) for the full list of rejection reason codes.
+
+## Channel storage
+
+Channel storage is a shared pool of props (key/value pairs) that can be read, created, updated and deleted by all members of the channel.
+
+In an open world game, you could store a reference to all the gatherable resources using channel storage. When a resource is gathered, other players in the same channel can be automatically notified so their world can be synced with the global world state.
+
+### Setting storage props
+
+
+
+Any player can update the global store using `Talo.channels.set_storage_props()`:
+
+```gdscript
+await Talo.channels.set_storage_props(channel.id, {
+ prop1: "value1",
+ prop2: "value2"
+})
+```
+
+This function accepts a dictionary of prop keys and values. You can set a prop value to `null` to delete it. Storage props that aren't being deleted will be upserted (updated if they exist, otherwise created).
+
+#### Handling failures
+
+Sometimes, setting storage props can fail. This usually happens when you set a prop key with a size over 128 characters or a prop value with a size over 512 characters.
+
+The `Talo.channels.channel_storage_props_failed_to_set` signal lets you listen for these errors:
+
+```gdscript
+func _ready() -> void:
+ Talo.channels.channel_storage_props_failed_to_set.connect(
+ func (channel: TaloChannel, failed_props: Array[TaloChannelStoragePropError]):
+ for prop in failed_props:
+ # shared-gold: Prop value length (596) exceeds 512 characters (PROP_VALUE_TOO_LONG)
+ print("%s: %s (%s)" % [prop.key, prop.message, prop.error])
+ )
+```
+
+`TaloChannelStoragePropError` includes a `message` with a human-readable description of the error, alongside the `error` code. See [Players and props - Prop rejection reasons](/docs/pre-1.0/godot/player-props#prop-rejection-reasons) for the full list of rejection reason codes.
+
+### Setting storage prop arrays
+
+
+
+Prop arrays are a special type of prop that can store multiple values under the same key. This is useful for storing lists of items, player IDs or other data that can have multiple values.
+
+
+Array keys are internally suffixed with [] (e.g. a key of "world_items" is stored as "world_items[]").
+
+When using the prop-array-specific functions, you should reference the key without the [] suffix.
+
+
+
+```gdscript
+var key := "world_items"
+var items: Array[String] = ["sword", "shield", "potion"]
+
+await Talo.channels.set_storage_prop_array(channel.id, key, items)
+```
+
+Passing an empty array will delete all existing values for the prop.
+
+You can handle failures in the same way [described above](#handling-failures). Individual props have the same limitations as regular props and prop arrays have an additional limit of 1000 items.
+
+### Getting storage props
+
+
+
+To get a prop, use `Talo.channels.get_storage_prop()`. In the example below, we're finding a channel for the player's guild and fetching the shared gold pool:
+
+```gdscript
+var options := Talo.channels.GetSubscribedChannelsOptions.new()
+options.prop_key = "guildId"
+options.prop_value = "157"
+var res := await Talo.channels.get_subscribed_channels(options)
+
+var channel := res[0]
+var prop := await Talo.channels.get_storage_prop(channel.id, "shared-gold")
+```
+
+After fetching a prop, you can access the `value`, `created_by_alias`, `last_updated_by_alias` (and more) from the `TaloChannelStorageProp` class.
+
+
+Getting storage props immediately after an update can return stale data. This is because the internal cache is only updated when the `channel_storage_props_updated` signal is emitted.
+
+We recommend using the signal for prop updates, as described in the [Listening for storage updates](#listening-for-storage-updates) section.
+If you need to fetch the latest data immediately, you can skip the internal cache: see the [Cache-busting](#cache-busting) section below.
+
+
+
+#### Cache-busting
+
+Talo keeps an internal cache of storage props which is automatically updated whenever props are created, updated or deleted. By default, Talo will pull from the internal cache which is generally up-to-date.
+
+To guarantee fetching the freshest data, you can skip the internal cache with the final parameter of `get_storage_prop()`:
+
+```gdscript
+# checks the internal cache first,
+# if the key isn't set, fetches the latest data directly from the database
+var prop := await Talo.channels.get_storage_prop(channel.id, "shared-gold", false) # default
+
+# fetches the latest data directly from the database
+var freshProp := await Talo.channels.get_storage_prop(channel.id, "shared-gold", true)
+```
+
+### Getting storage prop arrays
+
+
+
+Prop arrays are made up of multiple props with the same key. To fetch all items in a prop array, use `Talo.channels.get_storage_prop_array()`:
+
+```gdscript
+var prop_key := "world_items"
+
+# checks the internal cache first
+var array_items := await Talo.channels.get_storage_prop_array(channel.id, prop_key, false) # default
+
+# fetches the latest data directly from the database
+var fresh_array_items := await Talo.channels.get_storage_prop_array(channel.id, prop_key, true)
+
+for prop in array_items:
+ print(prop.value)
+```
+
+### Getting multiple storage props
+
+
+
+If you need to fetch multiple storage props, `Talo.channels.list_storage_props()` is much faster and more efficient than fetching them one by one:
+
+```gdscript
+await Talo.channels.set_storage_props(channel.id, {
+ "storage_prop_1": "true",
+ "storage_prop_2": "hello world"
+})
+
+# without cache busting
+var results := await Talo.channels.list_storage_props(channel.id, ["storage_prop_1", "storage_prop_2"])
+for prop in results:
+ print("Prop: %s = %s" % [prop.key, prop.value])
+
+
+# with cache busting
+var busted_results := await Talo.channels.list_storage_props(channel.id, ["storage_prop_1", "storage_prop_2"], true)
+for prop in busted_results:
+ print("Prop: %s = %s" % [prop.key, prop.value])
+```
+
+This function will return a `TaloChannelStorageProp` array, allowing you to iterate through the results.
+
+
+ If a prop cannot be found, it will not appear in the list.
+
+
+#### Prop arrays
+
+If any of the keys you pass are [prop arrays](/docs/pre-1.0/godot/channels#getting-storage-prop-arrays), append `[]` to the key to fetch every item stored under it. Scalar and array keys can be mixed in a single call:
+
+```gdscript
+# storage_prop_1 is a scalar, world_items[] is a prop array
+var results := await Talo.channels.list_storage_props(channel.id, ["storage_prop_1", "world_items[]"])
+for prop in results:
+ print("Prop: %s = %s" % [prop.key, prop.value])
+```
+
+### Listening for storage updates
+
+
+
+The `Talo.channels.channel_storage_props_updated` signal will emit when a storage update is received. It will list the channel, upserted (inserted or updated) props and deleted props:
+
+```gdscript
+func _ready() -> void:
+ Talo.channels.channel_storage_props_updated.connect(_on_channel_props_updated)
+
+func _on_channel_props_updated(channel: TaloChannel, upserted_props: Array[TaloChannelStorageProp], deleted_props: Array[TaloChannelStorageProp]) -> void:
+ if channel.id != demo_channel.id:
+ return
+
+ for prop in upserted_props:
+ # e.g. "shared-gold: 80 upserted by jim"
+ print("%s:%s upserted by %s" % [prop.key, prop.value, prop.last_updated_by_alias.identifier])
+
+ for prop in deleted_props:
+ # e.g. "shared-gold deleted by jim, previous value was 80"
+ print("%s deleted by %s, previous value was %s" % [prop.key, prop.last_updated_by_alias.identifier, prop.value])
+
+```
diff --git a/content/docs/pre-1.0/godot/continuity.mdx b/content/docs/pre-1.0/godot/continuity.mdx
new file mode 100644
index 0000000..af3c369
--- /dev/null
+++ b/content/docs/pre-1.0/godot/continuity.mdx
@@ -0,0 +1,109 @@
+---
+description: Talo is designed to work online and offline. Talo will automatically keep data in sync for offline players.
+title: Continuity and offline mode
+---
+import { ScopeBadges } from '@/components/documentation/ScopeBadges'
+
+## What is Continuity?
+
+
+
+Talo Continuity is a resilience layer to keep your game in sync when Talo can't be reached.
+
+Continuity runs behind the scenes and catches server errors (like 502 Bad Gateway, 503 Service Unavailable or 504 Gateway Timeout) or [Godot network errors](https://docs.godotengine.org/en/stable/classes/class_httprequest.html#enum-httprequest-result).
+
+Failed requests are cached and replayed when network connectivity is re-established. Internally, a health check endpoint is polled to assess Talo's availability.
+
+
+
+ Continuity requires the `write:continuityRequests` API key scope. Without it, continuity won't work.
+
+
+
+### How does it work?
+
+When a network request to Talo fails, the request is cached to the player's disk. Continuity periodically checks if the network connection to Talo has been restored and if it has, the cached requests will be replayed in batches.
+
+Continuity also works between sessions: any pending continuity requests will get picked up when the player reopens your game.
+
+### Continuity timestamp
+
+Replayed requests automatically send a special header indicating the original time when the request was made. Talo uses this header to set the `created_at` of various entities like feedback, leaderboard entries and player stats.
+
+### Configuring Continuity
+
+As with anything on the client-side, Continuity could potentially be exploited by bad actors. Depending on the type of game you're building, this could have varying consequences. You can disable continuity by setting `enabled` to false under the `[continuity]` section of your `addons/talo/settings.cfg`.
+
+### Testing Continuity
+
+Continuity happens in the background and there are no direct APIs exposed for it. You can simulate being offline and toggle Continuity in the Talo Playground scene.
+
+
+
+ Toggling these options will not update your `settings.cfg`.
+
+
+
+If you have [request logging enabled](settings-reference#loggingrequests), the replayed requests will be highlighted for you in the console.
+
+## Network connection signals
+
+Talo emits signals when network connectivity changes, allowing you to respond to connection issues in your game.
+
+### `connection_lost`
+
+Emitted when Talo loses connection to the server. This happens when health checks fail or network errors occur.
+
+```gdscript
+func _ready():
+ Talo.connection_lost.connect(_on_connection_lost)
+
+func _on_connection_lost():
+ print("Connection to Talo lost")
+ # handle offline state (e.g., show an offline indicator)
+```
+
+### `connection_restored`
+
+Emitted when connection to Talo is re-established after being lost.
+
+```gdscript
+func _ready():
+ Talo.connection_restored.connect(_on_connection_restored)
+
+func _on_connection_restored():
+ print("Connection to Talo restored")
+ # handle online state (e.g., hide an offline indicator)
+```
+
+## Offline interactions
+
+### Checking if the player is offline
+
+`Talo.is_offline()` returns a boolean indicating if offline mode is enabled. Offline mode occurs when Talo can't be reached (server unavailable or no internet connection) or when the [`debug.offline_mode` setting](settings-reference#debugoffline_mode) is enabled.
+
+Use this function to handle custom offline interactions, such as building your own offline caches.
+
+### Player identification
+
+After a successful online identification, player data is cached locally for future offline use. You can disable this interaction with the [`cache_player_on_identify` setting](settings-reference#cache_player_on_identify).
+
+
+
+ Players cannot be created while offline.
+
+
+
+### Game saves
+
+All player saves are cached locally and automatically loaded when offline. Updates to save files are mirrored to the offline cache.
+
+Saves sync automatically when connectivity is restored. If the offline save is newer than the latest version of the online save, the offline save will become the new latest version.
+
+### Live config
+
+Live config data is cached locally after successful online queries. Updates are synced with the local cache, which is used when the player is offline.
+
+### Talo socket
+
+When connection is restored, the Talo socket automatically reconnects by repeating the socket token identification flow. This happens seamlessly in the background without requiring manual intervention.
\ No newline at end of file
diff --git a/content/docs/pre-1.0/godot/events.mdx b/content/docs/pre-1.0/godot/events.mdx
new file mode 100644
index 0000000..b6f5138
--- /dev/null
+++ b/content/docs/pre-1.0/godot/events.mdx
@@ -0,0 +1,49 @@
+---
+description: Event tracking allows you to understand what players are doing inside your game.
+title: Events
+---
+
+import { ScopeBadges } from '@/components/documentation/ScopeBadges'
+
+## Tracking
+
+
+
+To track an event, simply call `Talo.events.track()` with an event name like "Level up". You can optionally attach properties to events by passing a dictionary to the function call:
+
+```gdscript title="level_up_button.gd"
+extends Button
+
+var level := 1
+
+func _on_pressed() -> void:
+ level += 1
+
+ Talo.events.track("Level up", {
+ "New level": level
+ })
+```
+
+
+ `Talo.events.track()` does not currently support prop arrays.
+
+
+## Flushing
+
+Events are automatically sent to Talo in batches. You can manually call `Talo.events.flush()` to send the events currently waiting in the queue. Flushing also happens automatically when:
+
+1. The game loses focus
+2. The game is paused
+3. The game is closed
+
+## Meta props
+
+Talo sends some extra props with each event. These props are made up of the following metadata:
+
+- The player's operating system
+- The version of the game
+- The window mode (fullscreen, fullscreen windowed, maximized window or windowed)
+- The width of the game window
+- The height of the game window
+
+These properties are then attached as props to the player, providing you with extra tools for debugging possible bugs and configurations where they appear.
diff --git a/content/docs/pre-1.0/godot/exporting.mdx b/content/docs/pre-1.0/godot/exporting.mdx
new file mode 100644
index 0000000..713f0c6
--- /dev/null
+++ b/content/docs/pre-1.0/godot/exporting.mdx
@@ -0,0 +1,42 @@
+---
+description: Talo can separate development data from live data based on debug builds and feature tags.
+title: Exporting your project
+---
+
+## Android export permissions
+
+
+ When exporting to Android, you must enable the `INTERNET` permission in the export preset before
+ exporting the project.
+
+
+## Separating development data
+
+It's common to test your development builds against services you've already configured, like stats and leaderboards. To avoid mixing development data with your live data, Talo automates this for you.
+
+When the [`OS.has_feature("debug")`](https://docs.godotengine.org/en/stable/tutorials/export/feature_tags.html) flag is set to true, any data sent from the Godot plugin is marked as **development data**. This allows you to easily separate it in your Talo dashboard.
+
+Talo will only fetch development data for development builds. Live builds will only include live data.
+
+### "Dev" players
+
+Dev players are players created in development builds (see below). Their data will be permanently marked as development data - regardless if they create any new data in release builds.
+
+In the dashboard, this is denoted by orange backgrounds:
+
+
+
+### Export with debug
+
+To create a "release" build that uses live data, untick the "Export With Debug" option when exporting your game. This prevents the `OS.has_feature("debug")` feature tag from being attached, so Talo will treat this as a live build.
+
+
+
+### Feature tag overrides
+
+You can override this behavior above with the `talo_dev` and `talo_live` feature tags
+
+- `talo_dev`: Marks the build as development, regardless of the debug feature flag.
+- `talo_live`: Marks the build as live, regardless of the debug feature flag.
+
+
diff --git a/content/docs/pre-1.0/godot/feedback.mdx b/content/docs/pre-1.0/godot/feedback.mdx
new file mode 100644
index 0000000..e8835b9
--- /dev/null
+++ b/content/docs/pre-1.0/godot/feedback.mdx
@@ -0,0 +1,80 @@
+---
+description: Talo Feedback allows you to receive feedback directly from players. Feedback can be categorised, analysed and filtered to improve your game.
+title: Feedback
+---
+
+import { ScopeBadges } from '@/components/documentation/ScopeBadges'
+
+## Creating feedback categories
+
+To create a feedback category, visit [the feedback page](https://dashboard.trytalo.com/feedback) inside the Talo dashboard and click "Edit categories". Take note of the `Internal name` of the category you create as this is how you'll be referring to it inside your game.
+
+## Getting available categories
+
+
+
+To list all the feedback categories for your game, use the `get_categories()` function:
+
+```gdscript title="get_categories_button.gd"
+extends Button
+
+func _on_pressed() -> void:
+ var categories := await Talo.feedback.get_categories()
+
+ if categories.size() == 0:
+ print("No categories found. Create some in the Talo dashboard!")
+ else:
+ var mapped := categories.map(func (c): return c.internal_name)
+ print("Category internal names: " + ", ".join(mapped)) # prints: "bugs, gameplay-feedback, terrain-issues"
+```
+
+## Sending feedback
+
+
+
+To send feedback, call the `send()` function with the player's comment and your chosen category's internal name:
+
+```gdscript title="send_feedback_button.gd"
+extends Button
+
+@export var internal_name: String
+@export var feedback_comment: String
+
+func _on_pressed() -> void:
+ await Talo.feedback.send(internal_name, feedback_comment)
+ print("Feedback sent for %s: %s" % [internal_name, feedback_comment])
+```
+
+### Additional context with props
+
+You can also provide extra information with `props`. This is an optional `Dictionary[String, String]` that are displayed in the Talo dashboard and can be used as filters. For example, you could look at all feedback from a specific game version.
+
+```gdscript
+extends Button
+
+func _on_pressed() -> void:
+ var category_internal_name := "worldgen_feedback"
+ var comment := "Too many spikes!"
+ var props: Dictionary[String, String] = {
+ seed = "32523523"
+ }
+ await Talo.feedback.send(category_internal_name, comment, props)
+```
+
+### Handling rejected props
+
+Talo validates feedback props. If one or more props are invalid, the feedback will not be saved.
+
+You can listen for rejected props by connecting to the `Talo.feedback.props_rejected` signal:
+
+```gdscript
+func _ready() -> void:
+ Talo.feedback.props_rejected.connect(_on_props_rejected)
+
+func _on_props_rejected(rejected_props: Array[TaloRejectedProp]) -> void:
+ for prop in rejected_props:
+ # e.g. "Rejected prop 'seed': Prop value exceeds 512 characters (PROP_VALUE_TOO_LONG)"
+ print("Rejected prop '%s': %s (%s)" % [prop.key, prop.message, prop.error])
+```
+
+See [Players and props - Prop rejection reasons](/docs/pre-1.0/godot/player-props#prop-rejection-reasons) for the full list of rejection reason codes.
diff --git a/content/docs/pre-1.0/godot/groups.mdx b/content/docs/pre-1.0/godot/groups.mdx
new file mode 100644
index 0000000..8985565
--- /dev/null
+++ b/content/docs/pre-1.0/godot/groups.mdx
@@ -0,0 +1,52 @@
+---
+description: Talo groups allow you to apply filters on your player-base to create distinct segments between players. You can have groups for beta testers, players who completed your game or players with specific props.
+title: Player groups
+---
+
+import { ScopeBadges } from '@/components/documentation/ScopeBadges'
+
+## Creating groups
+
+To create a group, visit [the groups page](https://dashboard.trytalo.com) inside the Talo dashboard. Take note of the `ID` as this is how you'll be referring to your group.
+
+## Checking membership
+
+You can check if a player belongs to a group using `is_in_talo_group_id()`. Alternatively you can search by group name using `is_in_talo_group_name()` too.
+
+```gdscript
+func is_power_user():
+ return Talo.current_player.is_in_talo_group_id('9e56e835-eff6-4a6d-ac35-db8e7561af0e')
+
+func is_beta_tester():
+ return Talo.current_player.is_in_talo_group_name('beta-testers')
+```
+
+## Fetching individual groups
+
+
+
+Groups in the `TaloPlayer` class are stubs that only include an `id` and a `name`.
+
+To retrieve more data about a group, including its members, use the `Talo.player_groups.get_group()` function:
+
+```gdscript
+var group_page := await Talo.player_groups.get_group(group_id)
+if group_page != null:
+ print("%s has %s player(s)" % [group_page.group.name, group_page.count])
+else:
+ push_error("Group %s not found" % [group_id])
+
+if group_page.count == 0:
+ print("No players in group")
+ return
+
+var identifiers = []
+for player in group_page.group.members:
+ identifiers.append(player.get_alias().identifier)
+
+print("Found %s members: %s" % [group_page.count, ", ".join(identifiers)])
+```
+
+Group members will only be visible if you've enabled the setting on your group in the Talo dashboard. If `members_visible` is `false`, `members` will always be an empty array.
+
+Group members must be paginated. You can do this by providing a page number to `get_group()` which returns a `Talo.player_groups.GroupPage` with information such as `count`, `items_per_page` and `is_last_page`.
diff --git a/content/docs/pre-1.0/godot/identifying.mdx b/content/docs/pre-1.0/godot/identifying.mdx
new file mode 100644
index 0000000..4c91fe2
--- /dev/null
+++ b/content/docs/pre-1.0/godot/identifying.mdx
@@ -0,0 +1,313 @@
+---
+description: The Talo Godot plugin allows you to authenticate players, create player aliases and sync identities with external identity providers.
+title: Identifying a player
+---
+
+import { ScopeBadges } from '@/components/documentation/ScopeBadges'
+
+
+## Player aliases
+
+In Talo, players can have multiple aliases.
+For example, a player could have Steam and Google Play accounts. Both accounts can be linked to the same player, allowing them to use either service to log in to your game.
+
+## Identifying
+
+
+
+You should identify a player _after_ you know who they are (post-authentication) but _before_ you attempt to track any events, add leaderboard entries or do anything related directly to the player.
+
+You can identify a player using `Talo.players.identify()`. The code sample below shows you how you could identify a player using a UI element (this example is also available in the Playground scene):
+
+```gdscript title="identify_button.gd"
+extends Button
+
+@export var service: String
+@export var identifier: String
+
+func _on_pressed():
+ Talo.players.identify(service, identifier)
+```
+
+
+You cannot use "Talo" for the `service` parameter as this is reserved for **Talo Player Authentication**.
+
+If you are using Talo Player Authentication, `Talo.players.identify()` will be invoked automatically and the `Talo.players.identified` signal will also emit as normal.
+
+Visit the [Player authentication docs](/docs/pre-1.0/godot/player-authentication) to learn more about identifying players with authentication enabled.
+
+
+
+### Generating a mostly-unique identifier
+
+You can easily create an identifier with `Talo.players.generate_identifier()`. This is useful for temporarily identifying players before you know who they are, and then merging them with an identified player later on.
+
+### The "identified" signal
+
+After a successful identification, the `Talo.players.identified` signal will emit, returning the identified player. This allows you to, for example, put the player's identifier on a label:
+
+```gdscript title="player_name.gd"
+extends Label
+
+func _ready() -> void:
+ Talo.players.identified.connect(_on_identified)
+
+func _on_identified(_player: TaloPlayer) -> void:
+ text = Talo.current_alias.identifier
+```
+
+### The "identification_started" and "identification_failed" signals
+
+When `Talo.players.identify()` is called, the `Talo.players.identification_started` signal is emitted.
+
+If identification fails, the `Talo.players.identification_failed` signal is emitted.
+
+```gdscript
+func _ready() -> void:
+ Talo.players.identification_started.connect(func (): go_to_loading())
+ Talo.players.identification_failed.connect(func (): go_to_login())
+```
+
+## Checking identification
+
+You can use `Talo.identity_check()` to verify whether a player has been identified - it returns `ERR_UNAUTHORIZED` if a player hasn't been identified yet:
+
+```gdscript
+func do_stuff_if_authenticated() -> void:
+ if Talo.identity_check() != OK:
+ return
+
+ # do stuff
+```
+
+## Clearing the identified player
+
+You can clear the current player using `Talo.players.clear_identity()`.
+
+This will set `Talo.current_alias` and `Talo.current_player` to `null`. It will also clear any stored or pending data such as the [offline player cache](#offline-player-cache), pending events and continuity requests. For players using Talo authentication, it will also clear session data.
+
+Once all the relevant data has been cleared, the `Talo.players.identity_cleared` signal will be emitted.
+
+## Merging players
+
+
+
+Sometimes you might start tracking a player's actions before you know their true identity. For example, you could be tracking events with an "anonymous" identifier and then later on the same player chooses their username before submitting a leaderboard entry. Since both of these players need to be identified, two players will be created.
+
+You can merge players using `Talo.players.merge()` by providing the IDs of both players. The merge process takes all the props, aliases, and associated data (events, leaderboard entries, saves, etc.) from **Player 2** and merges them into **Player 1**. This means that duplicate props in **Player 1** will be replaced by the ones from **Player 2**.
+
+
+Player merging has some limitations:
+
+- **Player 2** cannot have a Talo Player Authentication, Steam, Google Play Games or Apple Game Center alias.
+- If **Player 1** has a Talo Player Authentication, Steam, Google Play Games or Apple Game Center alias, the merge must be initiated while identified as **Player 1** (i.e. `Talo.current_alias` must belong to Player 1). In this case, make sure your last `Talo.players.identify()` call before merging uses Player 1's alias.
+- Both players cannot have overlapping alias services. For example, if both players have an alias with the service "username", the merging process will fail.
+
+
+You can provide the `post_merge_identity_service` option to automatically re-identify the player once merging is complete:
+
+```gdscript
+await Talo.players.identify("anonymous", Talo.players.generate_identifier())
+var player1_id = Talo.current_player.id
+await Talo.players.identify("username", "guyman")
+var player2_id = Talo.current_player.id
+
+print(Talo.current_alias.service) # "username"
+
+var mergeOpts := Talo.players.MergeOptions.new()
+mergeOpts.post_merge_identity_service = "anonymous" # go back to the anonymous alias
+var merged_player := await Talo.players.merge(player1_id, player2_id, mergeOpts)
+
+print(Talo.current_alias.service) # "anonymous"
+```
+
+In the example above, the two players created with `Talo.players.identify()` are merged. Before merging, the current alias service was **"username"** (because that was the most recently identified player). Setting the `post_merge_identity_service` option will invoke `Talo.players.identify()` with the **"anonymous"** alias.
+
+## Steamworks integration
+
+
+ You can enable this integration on the [integrations
+ page](https://dashboard.trytalo.com/integrations).
+
+
+If you have the Steamworks integration enabled, Talo can sync a Steam player ([as described here](/docs/pre-1.0/integrations/steamworks#authentication)). You can do this via the `Talo.players.identify_steam` function. Here's an example using [GodotSteam](https://godotsteam.com):
+
+```gdscript
+extends Node
+
+var identity = "talo"
+
+func _ready() -> void:
+ Steam.steamInitEx()
+ Steam.get_ticket_for_web_api.connect(_on_get_ticket_for_web_api)
+ Steam.getAuthTicketForWebApi(identity)
+
+func _on_get_ticket_for_web_api(_auth_ticket: int, _result: int, _ticket_size: int, ticket_buffer: Array) -> void:
+ Talo.players.identify_steam(PackedByteArray(ticket_buffer).hex_encode(), identity)
+
+func _process(_delta: float) -> void:
+ Steam.run_callbacks()
+```
+
+The `identity` parameter is optional but strongly recommended as it ensures proper identification of the service verifying the ticket. It can be anything you like but must be the same as the `identity` passed to Steam when fetching the ticket.
+
+### Steamworks player props
+
+After successfully authenticating the player, several [props](/docs/pre-1.0/godot/player-props) will automatically be created for them:
+
+- `META_STEAMWORKS_VAC_BANNED` - "true" or "false"
+- `META_STEAMWORKS_PUBLISHER_BANNED` - "true" or "false"
+- `META_STEAMWORKS_OWNS_APP` - "true" or "false"
+- `META_STEAMWORKS_OWNS_APP_PERMANENTLY` - "true" or "false"
+- `META_STEAMWORKS_OWNS_APP_FROM_DATE` - ISO date e.g. "2025-11-08T17:30:00Z"
+- `META_STEAMWORKS_PERSONA_NAME` - the player's current display name
+- `META_STEAMWORKS_AVATAR_HASH` - e.g. "5bfdbd8cd4407d6cd37dfba06851b62a4856bb2d"
+
+You can build an avatar URL by replacing **[AVATAR_HASH]** with the `META_STEAMWORKS_AVATAR_HASH` prop value in the following URL: `https://avatars.steamstatic.com/[AVATAR_HASH]_full.jpg`.
+
+These props will be updated each time the player is identified using `Talo.players.identify_steam()`.
+
+## Google Play Games integration
+
+
+ You can enable this integration on the [integrations
+ page](https://dashboard.trytalo.com/integrations).
+
+
+If you have the Google Play Games integration enabled, Talo can identify a player using an OAuth client. Follow Google's official documentation for setting up [Game Server credentials](https://developer.android.com/games/pgs/console/setup#generate_an_oauth_20_client_id).
+
+All you need to do is pass a server auth code to the `Talo.players.identify_google_play_games` function. Here's an example using the [Godot Play Games Services plugin](https://github.com/godot-sdk-integrations/godot-play-game-services):
+
+```gdscript
+extends Node2D
+
+@onready var play_games_sign_in_client: PlayGamesSignInClient = %PlayGamesSignInClient
+
+func _enter_tree() -> void:
+ GodotPlayGameServices.initialize()
+
+func _ready() -> void:
+ play_games_sign_in_client.user_authenticated.connect(
+ func (is_authenticated):
+ if is_authenticated:
+ play_games_sign_in_client.request_server_side_access("yourid.apps.googleusercontent.com", false)
+ )
+
+ play_games_sign_in_client.server_side_access_requested.connect(
+ func (auth_code):
+ Talo.players.identify_google_play_games(auth_code)
+ )
+
+ play_games_sign_in_client.is_authenticated()
+```
+
+Replace "yourid.apps.googleusercontent.com" with the client ID you generated earlier.
+
+### Google Play Games player props
+
+After successfully authenticating the player, these [props](/docs/pre-1.0/godot/player-props) will automatically be created for them:
+
+- `META_GOOGLE_PLAY_GAMES_DISPLAY_NAME` - The player's chosen display name
+- `META_GOOGLE_PLAY_GAMES_AVATAR_URL` - A direct link to the player's avatar
+
+These props will be updated each time the player is identified using `Talo.players.identify_google_play_games()`.
+
+## Apple Game Center integration
+
+
+ You can enable this integration on the [integrations
+ page](https://dashboard.trytalo.com/integrations).
+
+
+If you have the Apple Game Center integration enabled, Talo can identify a player using Apple's identity verification signature. You can do this via the `Talo.players.identify_game_center` function.
+
+
+ The Game Center plugin singleton is only available on iOS and macOS. It will not exist when
+ running from the Godot editor. Make sure to check for its availability before calling any Game
+ Center methods.
+
+
+You'll need to fetch the local player's identity verification signature and pass the resulting values to Talo. Here's an example using the [GodotApplePlugins](https://github.com/migueldeicaza/GodotApplePlugins):
+
+```gdscript
+extends Node
+
+# replace with your own bundle identifier
+const bundle_id := "com.example.game"
+
+var game_center: GameCenterManager
+
+func _ready() -> void:
+ game_center = GameCenterManager.new()
+
+ game_center.authentication_error.connect(
+ func (err: String):
+ push_error("Game Center auth failed: %s" % err)
+ )
+
+ game_center.authentication_result.connect(
+ func (success: bool):
+ if success:
+ _handle_auth_success()
+ )
+
+ game_center.authenticate()
+
+func _handle_auth_success() -> void:
+ var local_player := game_center.local_player
+
+ local_player.fetch_items_for_identity_verification_signature(
+ func (res: Dictionary, err: Variant):
+ if err:
+ push_error(err)
+ else:
+ await Talo.players.identify_game_center(
+ res.url,
+ Marshalls.raw_to_base64(res.data),
+ Marshalls.raw_to_base64(res.salt),
+ res.timestamp,
+ local_player.team_player_id,
+ bundle_id
+ )
+ )
+```
+
+## Offline player cache
+
+If the `cache_player_on_identify` setting is enabled (default `true`), Talo will store player data locally. If a player tries to identify while offline, Talo will try to use local data if it exists.
+
+### Saving identifiers for future sessions
+
+The offline player cache enables you to store and retrieve player identifiers. You can leverage this in situations where players choose their own identifiers and you need them in subsequent sessions:
+
+```gdscript
+func retrieve_identifier() -> String:
+ var alias := TaloPlayerAlias.get_offline_alias()
+ if alias != null:
+ return alias.identifier
+ return ""
+```
+
+This function will return the identifier (if one exists) or an empty string. If the return value is not empty, you can safely call `Talo.players.identify()`.
+
+## Display names
+
+Every player alias has a `display_name` that provides a human-readable name for the player. You can read it from `Talo.current_alias.display_name` after a player has been identified. Display names are resolved from a [player's props](/docs/pre-1.0/godot/player-props) - arbitrary properties persisted across all of a player's aliases.
+
+```gdscript title="welcome_label.gd"
+extends Label
+
+func _ready() -> void:
+ Talo.players.identified.connect(_on_identified)
+
+func _on_identified(_player: TaloPlayer) -> void:
+ text = "Welcome, %s!" % Talo.current_alias.display_name
+```
+
+By default, a player's display name is their alias `identifier`. You can configure the **display name prop key** on the [game settings page](https://dashboard.trytalo.com/game-settings) in the Talo dashboard. When set, Talo resolves each alias's `display_name` to the value of the player's prop with that key.
+
+
+
+For example, if your game's display name prop key is `playerChosenName` and a player has a prop `playerChosenName = "TheGuy"`, their alias `display_name` will be "TheGuy".
+
+If a player doesn't have a prop matching the configured key, their display name falls back to their alias `identifier`. The display name is resolved by Talo on each identification, so it stays up to date as the player's props change.
diff --git a/content/docs/pre-1.0/godot/install.mdx b/content/docs/pre-1.0/godot/install.mdx
new file mode 100644
index 0000000..a3f44c6
--- /dev/null
+++ b/content/docs/pre-1.0/godot/install.mdx
@@ -0,0 +1,65 @@
+---
+description: Learn how to add Talo's Godot plugin to your game.
+title: Installation
+---
+
+## Import into Godot
+
+
+ Check out this blog post on [installing the Talo Godot
+ plugin](https://trytalo.com/blog/godot-plugin-announcement?utm_source=docs&utm_medium=tip) for a
+ detailed walkthrough
+
+
+### Using the Asset Library
+
+The Godot Asset Library is the recommended way to download Godot plugins. Open the `AssetLib` tab in Godot and search for "Talo". Click into the asset and click `Download`. Here's a [direct link](https://godotengine.org/asset-library/asset/2936) to the asset.
+
+
+
+### Downloading from itch.io
+
+You can download the latest version of the Godot plugin from our [itch.io page](https://sleepystudios.itch.io/talo-godot). Simply unzip the plugin and copy the `addons/talo` folder into your project.
+
+## Enable the plugin
+
+
+After installing the plugin, you must enable it in the `Project > Project Settings > Plugins` window.
+
+
+
+
+## Generate an API key
+
+Visit [the Talo dashboard](https://dashboard.trytalo.com), login or create an account (and confirm your email address), and visit the API Keys page.
+
+Choose the scopes available to your API key (you'll need the `read:players` and `write:players` scope to use the plugin) and create your API key.
+Save your API key somewhere securely.
+
+## Create and update settings
+
+The first time you run your game with the Talo plugin enabled, it'll create a `settings.cfg` inside the `addons/talo` folder. You can also create this file for yourself, the default file looks like this:
+
+```gdscript title="addons/talo/settings.cfg"
+access_key=""
+api_url="https://api.trytalo.com"
+socket_url="wss://api.trytalo.com"
+```
+
+You can also add the following section for logging:
+
+```gdscript title="addons/talo/settings.cfg"
+...
+
+[logging]
+requests=true
+responses=true
+```
+
+### Self-hosting
+
+If you're self-hosting Talo, you can configure your custom endpoint here in the `settings.cfg`.
+
+### Settings reference
+
+You can visit the [settings reference](/docs/pre-1.0/godot/settings-reference) for a detailed overview of Talo's configuration options.
diff --git a/content/docs/pre-1.0/godot/leaderboards.mdx b/content/docs/pre-1.0/godot/leaderboards.mdx
new file mode 100644
index 0000000..a28b641
--- /dev/null
+++ b/content/docs/pre-1.0/godot/leaderboards.mdx
@@ -0,0 +1,278 @@
+---
+description: Talo leaderboards are highly customisable - sort modes, entry uniqueness and refresh intervals can all be easily configured per-leaderboard.
+title: Leaderboards
+---
+
+import { ScopeBadges } from '@/components/documentation/ScopeBadges'
+
+## Creating leaderboards
+
+To create a leaderboard, visit [the leaderboards page](https://dashboard.trytalo.com/leaderboards) inside the Talo dashboard. Take note of the `Internal name` as this is how you'll be referring to your leaderboard.
+
+
+ Check out this blog post on [how to build quick & easy leaderboards in
+ Godot](https://trytalo.com/blog/leaderboards-godot?utm_source=docs&utm_medium=tip) for a detailed
+ walkthrough
+
+
+## Creating entries
+
+
+
+Use `Talo.leaderboards.add_entry()` to create an entry:
+
+```gdscript title="add_entry_button.gd"
+extends Button
+
+@export var leaderboard_name: String
+
+func _on_pressed() -> void:
+ var score := RandomNumberGenerator.new().randi_range(1, 50)
+ var res := await Talo.leaderboards.add_entry(leaderboard_name, score)
+
+ print("Added score: %s, at position: %s, new high score: %s" % [score, res.entry.position, "yes" if res.updated else "no"])
+```
+
+This function returns an `AddEntryResult` that includes the entry and whether the entry was updated.
+
+Updated entries are only relevant if the leaderboard is set to unique. Leaderboard entries won't be updated if they are not better (depending on the sort mode) than the player's previous entry.
+
+## Entry props
+
+Along with a score, you can also send a dictionary of `props` with an entry. The key/value pairs will be stringified and can be used to filter entries. For example, you could send a `team` prop:
+
+```gdscript
+func _on_submit_pressed() -> void:
+ await Talo.players.identify("username", username.text)
+ var score := RandomNumberGenerator.new().randi_range(0, 100)
+ var team := "Blue" if RandomNumberGenerator.new().randi_range(0, 1) == 0 else "Red"
+
+ var res := await Talo.leaderboards.add_entry(leaderboard_internal_name, score, { team = team })
+ info_label.text = "You scored %s points for the %s team!" % [score, team]
+
+ _build_entries()
+```
+
+You could then have a function that populates the leaderboard and checks if a team filter is active:
+
+```gdscript
+func _build_entries() -> void:
+ var entries := Talo.leaderboards.get_cached_entries(leaderboard_internal_name)
+
+ if _filter != "All": # e.g. "Blue" or "Red"
+ entries = entries.filter(
+ func (entry: TaloLeaderboardEntry):
+ return entry.get_prop("team", "None") == _filter
+ )
+
+ for entry in entries:
+ _create_entry(entry)
+```
+
+The code above is available in the leaderboards sample included with the Talo Godot plugin.
+
+### Array props
+
+To send a prop array with an entry, use a `[]`-suffixed key with an `Array` value:
+
+```gdscript
+var res := await Talo.leaderboards.add_entry(leaderboard_internal_name, score, {
+ "equipment[]": ["sword", "shield", "helmet"]
+})
+```
+
+### Mixed props
+
+Array props can be mixed with scalar props in the same call:
+
+```gdscript
+var res := await Talo.leaderboards.add_entry(leaderboard_internal_name, score, {
+ team = team,
+ "equipment[]": ["sword", "shield"]
+})
+```
+
+### Handling rejected entry props
+
+Talo validates entry props. If one or more props are invalid, the entry will not be saved.
+
+You can listen for rejected props by connecting to the `Talo.leaderboards.props_rejected` signal:
+
+```gdscript
+func _ready() -> void:
+ Talo.leaderboards.props_rejected.connect(_on_props_rejected)
+
+func _on_props_rejected(rejected_props: Array[TaloRejectedProp]) -> void:
+ for prop in rejected_props:
+ # e.g. "Rejected prop 'team': Prop key or value contains profanity (PROP_CONTAINS_PROFANITY)"
+ print("Rejected prop '%s': %s (%s)" % [prop.key, prop.message, prop.error])
+```
+
+See [Players and props - Prop rejection reasons](/docs/pre-1.0/godot/player-props#prop-rejection-reasons) for the full list of rejection reason codes.
+
+## Getting entries
+
+
+
+Leaderboard entries are paginated: a maximum of 50 entries come back with each request. Use `Talo.leaderboards.get_entries()` to retrieve an array of entries:
+
+```gdscript title="get_entries_button.gd"
+extends Button
+
+@export var leaderboard_name: String
+var current_page: int = 0
+
+func _on_pressed() -> void:
+ var options := Talo.leaderboards.GetEntriesOptions.new()
+ options.page = current_page
+
+ var res := await Talo.leaderboards.get_entries(leaderboard_name, options)
+ var entries: Array[TaloLeaderboardEntry] = res.entries
+ var count: int = res.count
+ var is_last_page: bool = res.is_last_page
+
+ print("%s entries, is last page: %s" % [count, is_last_page])
+```
+
+### Getting entries for the current player
+
+Since a player can have many aliases, you can use the `player_id` option to fetch entries across all of a player's aliases:
+
+```gdscript
+var options := Talo.leaderboards.GetEntriesOptions.new()
+options.player_id = Talo.current_player.id
+
+var res := await Talo.leaderboards.get_entries(internal_name, options)
+```
+
+If you want to scope entries to a specific alias instead, use the `alias_id` option:
+
+```gdscript
+var options := Talo.leaderboards.GetEntriesOptions.new()
+options.alias_id = Talo.current_alias.id
+
+var res := await Talo.leaderboards.get_entries(internal_name, options)
+```
+
+You can also filter entries by the service an alias belongs to using the `alias_service` option:
+
+```gdscript
+var options := Talo.leaderboards.GetEntriesOptions.new()
+options.alias_service = "steam"
+
+var res := await Talo.leaderboards.get_entries(internal_name, options)
+```
+
+### Getting archived entries
+
+If your leaderboard uses refresh intervals (i.e. daily, weekly, monthly, yearly), you can get archived entries using the `include_archived` option:
+
+```gdscript
+var options := Talo.leaderboards.GetEntriesOptions.new()
+options.include_archived = true
+
+var res := await Talo.leaderboards.get_entries(internal_name, options)
+```
+
+## Entry cache
+
+After fetching your leaderboard entries you can take advantage of the internal cache to construct your UI, removing the need for any subsequent network requests.
+
+You can use `Talo.leaderboards.get_cached_entries()` in the same way as `get_entries()` above. Every entry fetched previously using `get_entries()` will exist in the cache.
+
+`get_cached_entries()` also accepts an optional `GetCachedEntriesOptions` object to filter the cached results:
+
+```gdscript
+# Filter cached entries by player
+var options := Talo.leaderboards.GetCachedEntriesOptions.new()
+options.player_id = Talo.current_player.id
+
+var entries := Talo.leaderboards.get_cached_entries(internal_name, options)
+```
+
+```gdscript
+# Filter cached entries by alias
+var options := Talo.leaderboards.GetCachedEntriesOptions.new()
+options.alias_id = Talo.current_alias.id
+
+var entries := Talo.leaderboards.get_cached_entries(internal_name, options)
+```
+
+```gdscript
+# Filter cached entries by alias service
+var options := Talo.leaderboards.GetCachedEntriesOptions.new()
+options.alias_service = "steam"
+
+var entries := Talo.leaderboards.get_cached_entries(internal_name, options)
+```
+
+Similarly, updated results from `add_entry()` will also be reflected in the cache - the entry returned from the response will be upserted and the positions of the other entries in the cache will be updated.
+
+### Getting entries by their props
+
+The example above assumes we've fetched all of the leaderboard entries so we can filter on them. It's generally more efficient to filter by prop keys and values when fetching leaderboard entries.
+
+The following code will only fetch leaderboard entries that have the "team" key:
+
+```gdscript
+var options := Talo.leaderboards.GetEntriesOptions.new()
+options.prop_key = "team"
+
+var res := await Talo.leaderboards.get_entries(internal_name, options)
+```
+
+You can also filter by a prop value. This code will now make sure there is a "team" key and its value is "Blue":
+
+```gdscript
+var options := Talo.leaderboards.GetEntriesOptions.new()
+options.prop_key = "team"
+options.prop_value = "Blue"
+
+var res := await Talo.leaderboards.get_entries(internal_name, options)
+```
+
+### Filtering by dates
+
+You can provide a `start_date` and `end_date` to `GetEntriesOptions` to filter entries by their creation date. Dates can be provided in three formats: UTC Date (YYYY-MM-DD), DateTime (ISO 8601), or millisecond timestamp:
+
+```gdscript
+# Get entries created between specific dates using UTC Date format
+var options := Talo.leaderboards.GetEntriesOptions.new()
+options.start_date = "2025-09-01"
+options.end_date = "2025-09-30"
+
+var res := await Talo.leaderboards.get_entries(internal_name, options)
+# Returns entries created between September 1-30, 2025
+```
+
+Using DateTime (ISO 8601) format:
+
+```gdscript
+var options := Talo.leaderboards.GetEntriesOptions.new()
+options.start_date = "2025-09-01T00:00:00Z"
+options.end_date = "2025-09-30T23:59:59Z"
+```
+
+Using millisecond timestamps:
+
+```gdscript
+var options2 := Talo.leaderboards.GetEntriesOptions.new()
+options2.start_date = "1756684800000" # September 1, 2025 00:00:00 UTC
+options2.end_date = "1759276799000" # September 30, 2025 23:59:59 UTC
+```
+
+You can also use just one of the date filters:
+
+```gdscript
+# Get entries created on or after a specific date
+var options := Talo.leaderboards.GetEntriesOptions.new()
+options.start_date = "2025-09-15"
+# options.end_date is omitted - returns all entries created from September 15, 2025 00:00:00 UTC onwards
+```
+
+```gdscript
+# Get entries created on or before a specific date
+var options := Talo.leaderboards.GetEntriesOptions.new()
+options.end_date = "2025-09-30"
+# options.start_date is omitted - returns all entries created before October 1, 2025 00:00:00 UTC
+```
diff --git a/content/docs/pre-1.0/godot/live-config.mdx b/content/docs/pre-1.0/godot/live-config.mdx
new file mode 100644
index 0000000..e928012
--- /dev/null
+++ b/content/docs/pre-1.0/godot/live-config.mdx
@@ -0,0 +1,61 @@
+---
+description: Live config lets you push data to your game from the Talo dashboard without releasing a new update.
+title: Live config
+---
+
+import { ScopeBadges } from '@/components/documentation/ScopeBadges'
+
+## Getting the live config
+
+
+
+You must fetch the live config using `Talo.game_config.get_live_config()` before querying its current state. This emits the `Talo.game_config.live_config_loaded` signal, which returns the newly initialised config:
+
+```gdscript
+func _ready() -> void:
+ Talo.game_config.live_config_loaded.connect(_on_live_config_loaded)
+ # first fetch the config
+ Talo.game_config.get_live_config()
+
+func _on_live_config_loaded(config: TaloLiveConfig):
+ # now you can query the config
+ pass
+```
+
+You can fetch the game config any time to refresh the state.
+
+## Querying the live config
+
+You can query properties of the config using `Talo.live_config.get_prop()`, for example if you wanted to get a boolean value:
+
+```gdscript
+var halloween_event_enabled = bool(Talo.live_config.get_prop("halloween_event_enabled"));
+```
+
+`live_config.get_prop()` will return the value of the property as a string, so you will need to cast it to the correct type. This function also has a second argument which is the default value to return if the property is not found.
+
+### Offline cache
+
+If the player is offline (determined using `await Talo.is_offline()`), an offline copy of the live config will be returned instead. The cached version is updated after a successful online `get_live_config()` call.
+
+
+ The offline cache can be `null` if the live config hasn't been successfully queried before.
+
+
+You can check when the offline config was last updated using `Talo.live_config.get_offline_config_last_modified()` which returns a Unix timestamp.
+
+## Listening for live config updates
+
+
+
+You can listen for live config updates by connecting to the `Talo.game_config.live_config_updated` signal:
+
+```gdscript
+func _ready() -> void:
+ Talo.game_config.live_config_updated.connect(_on_live_config_updated)
+
+func _on_live_config_updated(live_config: TaloLiveConfig) -> void:
+ label.text = live_config.get_prop("live_string", "Not set!")
+```
+
+The `live_config_updated` signal is emitted (via the [Talo Socket](/docs/pre-1.0/socket)) whenever the live config is updated.
diff --git a/content/docs/pre-1.0/godot/meta.json b/content/docs/pre-1.0/godot/meta.json
new file mode 100644
index 0000000..8232a00
--- /dev/null
+++ b/content/docs/pre-1.0/godot/meta.json
@@ -0,0 +1,25 @@
+{
+ "pages": [
+ "install",
+ "settings-reference",
+ "exporting",
+ "request-verification",
+ "continuity",
+ "identifying",
+ "player-authentication",
+ "player-props",
+ "events",
+ "leaderboards",
+ "saves",
+ "stats",
+ "groups",
+ "live-config",
+ "feedback",
+ "socket",
+ "channels",
+ "player-presence",
+ "player-relationships"
+ ],
+ "title": "Godot plugin",
+ "icon": "godot"
+}
diff --git a/content/docs/pre-1.0/godot/player-authentication.mdx b/content/docs/pre-1.0/godot/player-authentication.mdx
new file mode 100644
index 0000000..000db62
--- /dev/null
+++ b/content/docs/pre-1.0/godot/player-authentication.mdx
@@ -0,0 +1,287 @@
+---
+description: Talo player authentication fully manages player login credentials, registrations and email verifications.
+title: Player authentication
+---
+
+import { ScopeBadges } from '@/components/documentation/ScopeBadges'
+
+## Included samples
+
+The "authentication" sample included with the Godot plugin shows how to:
+* Register players with a username, password and optional email.
+* Log players in with their chosen credentials.
+* Verify login requests.
+* Request a forgotten password email and reset passwords.
+* Update player details.
+
+## Registering
+
+
+
+To register a player, you can use `Talo.player_auth.register()`. At a minimum, you need to provide a username and a password for the player.
+
+If you want the player to be able to verify their logins via email (two factor authentication), you will also need to provide an email and set the `verification_enabled` flag to `true`.
+
+```gdscript
+@onready var username: TextEdit = %Username
+@onready var password: TextEdit = %Password
+@onready var enable_verification: CheckBox = %EnableVerification
+@onready var email: TextEdit = %Email
+@onready var validation_label: Label = %ValidationLabel
+
+...
+
+var res = await Talo.player_auth.register(username.text, password.text, email.text, enable_verification.button_pressed)
+if res != OK:
+ match Talo.player_auth.last_error.get_code():
+ TaloAuthError.ErrorCode.IDENTIFIER_TAKEN:
+ validation_label.text = "Username is already taken"
+ TaloAuthError.ErrorCode.IDENTIFIER_PROFANITY:
+ validation_label.text = "Username contains profanity"
+ _:
+ validation_label.text = Talo.player_auth.last_error.get_string()
+```
+
+Once a player is registered, a session will automatically be created for them and they will be logged in.
+
+## Logging in
+
+
+
+To start a session, call the `Talo.player_auth.login()` function with the player's username and password.
+
+The `login()` function returns a `Talo.player_auth.LoginResult` which maps to:
+
+1. A successful login
+2. A successful login but the player must verify their details before creating the session
+3. An unsuccessful login attempt
+
+```gdscript
+signal verification_required
+
+@onready var username: TextEdit = %Username
+@onready var password: TextEdit = %Password
+@onready var validation_label: Label = %ValidationLabel
+
+...
+
+var res := await Talo.player_auth.login(username.text, password.text)
+match res:
+ Talo.player_auth.LoginResult.FAILED:
+ match Talo.player_auth.last_error.get_code():
+ TaloAuthError.ErrorCode.INVALID_CREDENTIALS:
+ validation_label.text = "Username or password is incorrect"
+ _:
+ validation_label.text = Talo.player_auth.last_error.get_string()
+ Talo.player_auth.LoginResult.VERIFICATION_REQUIRED:
+ verification_required.emit()
+ Talo.player_auth.LoginResult.OK:
+ pass
+```
+
+### Automatic logins
+
+After a successful login, the player's session token is stored locally. If a token is found when the game is opened, Talo will automatically identify the player.
+
+You can disable this by adding this to your `settings.cfg`:
+
+```
+[player_auth]
+auto_start_session=false
+```
+
+You can manually trigger this behaviour using `Talo.player_auth.start_session()`.
+
+### The "session_found" and "session_not_found" signals
+
+When `Talo.player_auth.start_session()` is called, either the `Talo.player_auth.session_found` or the `Talo.player_auth.session_not_found` signal will be emitted if a token is found.
+
+You can use these signals when deciding whether or not to show a splash screen or login screen:
+
+```gdscript
+func _ready() -> void:
+ # auto_start_session should be disabled
+ Talo.player_auth.session_found.connect(func (): go_to_loading())
+ Talo.player_auth.session_not_found.connect(func (): go_to_login())
+ Talo.player_auth.start_session()
+```
+
+## Verifying logins
+
+
+
+To verify a player's login, you need to call `Talo.player_auth.verify()` with the `code` sent to the player's email:
+
+```gdscript
+@onready var code: TextEdit = %Code
+@onready var validation_label: Label = %ValidationLabel
+
+...
+
+var res := await Talo.player_auth.verify(code.text)
+if res != OK:
+ match Talo.player_auth.last_error.get_code():
+ TaloAuthError.ErrorCode.INVALID_CREDENTIALS:
+ validation_label.text = "Verification code is incorrect"
+ _:
+ validation_label.text = Talo.player_auth.last_error.get_string()
+```
+
+## Changing a player's password
+
+
+
+To change the logged in player's password, you need to provide their current password and a new password to the `Talo.player_auth.change_password()` function:
+
+```gdscript
+signal password_change_success
+
+@onready var current_password: TextEdit = %CurrentPassword
+@onready var new_password: TextEdit = %NewPassword
+@onready var validation_label: Label = %ValidationLabel
+
+...
+
+var res = await Talo.player_auth.change_password(current_password.text, new_password.text)
+if res != OK:
+ match Talo.player_auth.last_error.get_code():
+ TaloAuthError.ErrorCode.INVALID_CREDENTIALS:
+ validation_label.text = "Current password is incorrect"
+ TaloAuthError.ErrorCode.NEW_PASSWORD_MATCHES_CURRENT_PASSWORD:
+ validation_label.text = "New password must be different from the current password"
+ _:
+ validation_label.text = Talo.player_auth.last_error.get_string()
+else:
+ password_change_success.emit()
+```
+
+## Changing a player's email
+
+
+
+You can change a player's email with the `Talo.player_auth.change_email()` function - just pass in their current password and a new email.
+
+If the email is already in use, the error code will be `TaloAuthError.ErrorCode.EMAIL_TAKEN`.
+
+## Changing a player's identifier
+
+
+
+You can change a player's identifier with the `Talo.player_auth.change_identifier()` function - just pass in their current password and a new identifier.
+
+## Forgotten password flow
+
+
+
+If a player forgets their password, you can begin the recovery flow by providing their email to the `Talo.player_auth.forgot_password()` function.
+
+Players who have not provided an email will not be able to initiate this flow.
+
+```gdscript title="forgot_password.gd"
+signal forgot_password_success
+
+@onready var email: TextEdit = %Email
+
+...
+
+if await Talo.player_auth.forgot_password(email.text) == OK:
+ forgot_password_success.emit()
+```
+
+If a player with the provided email exists, they'll receive a code that they'll use to set a new password.
+
+To reset a password, pass the code from the email along with a new password to the `Talo.player_auth.reset_password()` function:
+
+```gdscript title="reset_password.gd"
+signal password_reset_success
+
+@onready var code: TextEdit = %Code
+@onready var new_password: TextEdit = %NewPassword
+@onready var validation_label: Label = %ValidationLabel
+
+...
+
+var res = await Talo.player_auth.reset_password(code.text, new_password.text)
+if res != OK:
+ match Talo.player_auth.last_error.get_code():
+ TaloAuthError.ErrorCode.PASSWORD_RESET_CODE_INVALID:
+ validation_label.text = "Reset code is invalid"
+ _:
+ validation_label.text = Talo.player_auth.last_error.get_string()
+else:
+ password_reset_success.emit()
+```
+
+## Toggling verification
+
+
+
+To toggle verification on and off, you can call the `Talo.player_auth.toggle_verification`. You'll need to provide the player's current password.
+
+If you're enabling verification and the player doesn't already have an email address set, you will need to provide one as the third parameter of the function call. By providing an email address for a player that already has one set, you will overwrite the existing email address with the newly provided email address.
+
+## Deleting accounts
+
+
+
+Player accounts can be deleted using `Talo.player_auth.delete_account`. You'll need to provide the player's current password. When an account is successfully deleted several things will happen internally:
+
+1. The alias will be deleted. We'll go into more detail about this below.
+2. The current alias will be set to `null`.
+3. The auth session will be cleared.
+
+You'll need to handle these things by, for example, navigating back to a "Login" screen.
+
+
+ You can provide your players with a self-service deletion link. This can be found on your game's
+ [settings page](https://dashboard.trytalo.com/game-settings).
+
+
+### Deleted aliases
+
+When an alias is deleted, all leaderboard entries and game feedback associated with that alias will be deleted. Any channels owned by the alias will now have a `null` owner.
+
+## Migrating accounts
+
+
+
+You can migrate a player's account to a different service and identifier using `Talo.player_auth.migrate_account()`. You'll need to provide the player's current password, the new service (e.g. `username`, `steam`, `google_play_games`) and the new identifier.
+
+The original player auth account will be deleted and they will only be able to use their new identifier.
+
+Here is an example of migrating to a [Steam alias](/docs/pre-1.0/godot/identifying#steamworks-integration):
+
+```gdscript
+@onready var current_password: TextEdit = %CurrentPassword
+@onready var new_service: OptionButton = %NewService
+@onready var new_identifier: TextEdit = %NewIdentifier
+@onready var validation_label: Label = %ValidationLabel
+
+var ticket_identity := "talo"
+
+func _ready() -> void:
+ Steam.steamInitEx()
+ Steam.get_ticket_for_web_api.connect(_on_get_ticket_for_web_api)
+ Steam.getAuthTicketForWebApi(ticket_identity)
+
+func _on_get_ticket_for_web_api(_auth_ticket: int, _result: int, _ticket_size: int, ticket_buffer: Array) -> void:
+ _migrate_account(PackedByteArray(ticket_buffer).hex_encode())
+
+func _migrate_account(ticket: String) -> void:
+ var res = await Talo.player_auth.migrate_account(current_password.text, "steam", "%s:%s" % [ticket_identity, ticket])
+
+ if res != OK:
+ validation_label.text = Talo.player_auth.last_error.get_string()
+```
+
+You can also migrate to the `google_play_games` service by providing an [auth code](/docs/pre-1.0/godot/identifying#google-play-games-integration).
+
+When a migration is successful, the current session is cleared, the player's alias is updated to the new service and identifier, and the `Talo.players.identified` signal is emitted.
+
+## Getting the last error
+
+As shown in the examples above, whenever a request fails you can query the last error returned using the `Talo.player_auth.last_error` variable.
+
+You can get the `TaloAuthError.ErrorCode` enum value using the `get_code()` function or the raw string using the `get_string()` function.
+
+You can view all the authentication errors and their descriptions [here](/docs/pre-1.0/http/player-auth-api#error-codes).
diff --git a/content/docs/pre-1.0/godot/player-presence.mdx b/content/docs/pre-1.0/godot/player-presence.mdx
new file mode 100644
index 0000000..558f584
--- /dev/null
+++ b/content/docs/pre-1.0/godot/player-presence.mdx
@@ -0,0 +1,65 @@
+---
+description: With Talo's presence API, you can track whether a player is online, allowing you to easily build social features like friends lists.
+title: Player presence
+---
+
+import { ScopeBadges } from '@/components/documentation/ScopeBadges'
+
+## Getting a player's presence
+
+
+
+To get a player's presence, use the `Talo.player_presence.get_presence()` function. This function has a single argument which is the ID of the player you want to get the presence of.
+
+```gdscript
+var presence := await Talo.player_presence.get_presence(player_id)
+```
+
+This function returns a `TaloPlayerPresence` object:
+
+```gdscript
+class_name TaloPlayerPresence extends Node
+
+var online: bool
+var custom_status: String
+var player_alias: TaloPlayerAlias
+var updated_at: String
+```
+
+The `player_alias` represents the last alias used to update the player's presence. The `updated_at` timestamp represents the last time the player's presence was updated. Custom statuses can be used to show a player's current activity or to represent other states beyond simply online or offline.
+
+## Updating presence
+
+
+
+To update the current player's presence, use the `Talo.player_presence.update_presence()` function. You can provide the new `online` status and an optional `custom_status`.
+
+```gdscript
+var res := await Talo.player_presence.update_presence(online, custom_status)
+```
+
+This function returns a `TaloPlayerPresence` object.
+
+## Subscribing to presence
+
+
+
+You can use the `Talo.player_presence.presence_changed` signal to subscribe to player presence updates. This signal will return a `TaloPlayerPresence` object and information about what changed.
+
+Here's an example of how to use this signal to update a friends list:
+
+```gdscript
+func _ready() -> void:
+ Talo.player_presence.presence_changed.connect(_on_presence_changed)
+
+func _on_presence_changed(presence: TaloPlayerPresence, online_changed: bool, custom_status_changed: bool) -> void:
+ # check if this player is in our friends list
+ if _friends_list.has(presence.player_alias.id):
+ if online_changed:
+ if presence.online:
+ print("%s is now online" % presence.player_alias.identifier)
+ else:
+ print("%s is now offline" % presence.player_alias.identifier)
+ if custom_status_changed:
+ print("They are currently: %s" % presence.custom_status)
+```
diff --git a/content/docs/pre-1.0/godot/player-props.mdx b/content/docs/pre-1.0/godot/player-props.mdx
new file mode 100644
index 0000000..1e85160
--- /dev/null
+++ b/content/docs/pre-1.0/godot/player-props.mdx
@@ -0,0 +1,184 @@
+---
+description: Players can have properties that are persisted across all of their aliases and across sessions. Props can be viewed and modified from the dashboard.
+title: Players and props
+---
+
+import { ScopeBadges } from '@/components/documentation/ScopeBadges'
+
+
+## Accessing players
+
+
+
+After identifying a player, you can access the underlying player object using `Talo.current_player`. For the current player alias, you can use `Talo.current_alias`.
+
+You can also find a player by their ID using `Talo.players.find()`. Players retrieved this way are read-only: you can only modify or take actions on behalf of a player that was previously identified.
+
+## Searching for players
+
+
+
+You can use `Talo.players.search()` to query players. This function accepts a single `query` parameter that will search your playerbase for matching player IDs, alias identifiers or prop values. You can use this function to find players by their username, their ID or if they have a prop with a specific value.
+
+```gdscript
+var search_page := await Talo.players.search("bob")
+if search_page.count == 0:
+ print("No players found")
+ return
+
+var identifiers = []
+for player in search_page.players:
+ identifiers.append(player.get_alias().identifier)
+
+print("Found %s results: %s" % [search_page.count, ", ".join(identifiers)])
+```
+
+## Getting and setting props
+
+Players can have a list of arbitrary properties that are persisted across all of their aliases. These props are identified by their unique key and can have any string value. Keys can be up to 128 characters long and values can be up to 512 characters long.
+
+All functions that modify props accept an optional `update` parameter (default `true`) that controls whether the player is synced with Talo after the change. Set it to `false` to batch multiple changes and avoid redundant [debounces](/docs/pre-1.0/godot/settings-reference#debounce_timer_seconds).
+
+### Getting props
+
+You can retrieve the current player's props using `Talo.current_player.props`. To retrieve a single prop use `Talo.current_player.get_prop()` (where you can also specify a fallback).
+
+```gdscript
+var level := Talo.current_player.get_prop("level", "1")
+print("Level: ", level)
+```
+
+### Setting props
+
+
+
+You can set props using `Talo.current_player.set_prop()`. If a prop with specified key doesn't exist it'll be created, otherwise the existing prop with the same key will be updated.
+
+```gdscript
+Talo.current_player.set_prop("level", "5")
+
+# Set multiple props without triggering a sync each time
+Talo.current_player.set_prop("level", "5", false)
+Talo.current_player.set_prop("class", "warrior", false)
+Talo.current_player.set_prop("xp", "1200") # syncs here
+```
+
+
+ Player props are not linearisable - simultaneous requests may be applied out of order. You should
+ avoid setting or deleting props in `_process()` functions.
+
+
+### Deleting props
+
+
+
+Props can be deleted with `Talo.current_player.delete_prop()` or by using `Talo.current_player.set_prop()` and setting the value to `null`.
+
+```gdscript
+Talo.current_player.delete_prop("level")
+```
+
+## Prop arrays
+
+Prop arrays allow you to store multiple values under a single key. Each item is stored as its own value (up to 512 characters each), with a maximum of 1000 items per array.
+
+
+Array keys are internally suffixed with `[]` (e.g. a key of `"inventory_items"` is stored as `"inventory_items[]"`).
+
+When using the functions below, you should reference the key without the `[]` suffix.
+
+
+
+### Getting prop arrays
+
+You can retrieve all values for a prop array using `Talo.current_player.get_prop_array()`. It returns an `Array[String]` of the current values.
+
+```gdscript
+var items := Talo.current_player.get_prop_array("inventory_items")
+print("Inventory: ", ", ".join(items))
+```
+
+### Setting prop arrays
+
+
+
+Use `Talo.current_player.set_prop_array()` to replace all values for a prop array key. Duplicate and empty values are ignored. The array must not be empty.
+
+```gdscript
+Talo.current_player.set_prop_array("inventory_items", ["sword", "shield", "helmet"])
+```
+
+### Inserting into prop arrays
+
+
+
+Use `Talo.current_player.insert_into_prop_array()` to add a single value to an existing prop array. If the value already exists it will not be added again.
+
+```gdscript
+Talo.current_player.insert_into_prop_array("inventory_items", "helmet")
+```
+
+### Removing from prop arrays
+
+
+
+Use `Talo.current_player.remove_from_prop_array()` to remove a single value from a prop array.
+
+```gdscript
+Talo.current_player.remove_from_prop_array("inventory_items", "helmet")
+```
+
+### Deleting prop arrays
+
+
+
+Use `Talo.current_player.delete_prop_array()` to delete an entire prop array.
+
+```gdscript
+Talo.current_player.delete_prop_array("inventory_items")
+```
+
+## Handling rejected props
+
+Player prop updates support partial success. If an update contains a mix of valid and invalid props, the valid props will be set and only the invalid ones will fail ([see rejection reasons](#prop-rejection-reasons)).
+
+You can listen for rejected props by connecting to the `Talo.players.props_rejected` signal:
+
+```gdscript
+func _ready() -> void:
+ Talo.players.props_rejected.connect(_on_props_rejected)
+
+func _on_props_rejected(rejected_props: Array[TaloRejectedProp]) -> void:
+ for prop in rejected_props:
+ # prop.key: the key of the rejected prop
+ # prop.error: the rejection reason code (e.g. TaloRejectedProp.RejectionReason.PROP_VALUE_TOO_LONG)
+ # prop.message: a human-readable description of the rejection
+ # e.g. "Rejected prop 'username': Prop value exceeds 512 characters (PROP_VALUE_TOO_LONG)"
+ print("Rejected prop '%s': %s (%s)" % [prop.key, prop.message, prop.error])
+```
+
+### Prop rejection reasons
+
+The `TaloRejectedProp` class exposes an `error` property of type `TaloRejectedProp.RejectionReason`:
+
+| Reason | Description |
+| ------------------------- | ---------------------------------------- |
+| `PROP_KEY_TOO_LONG` | The prop key exceeds 128 characters |
+| `PROP_VALUE_TOO_LONG` | The prop value exceeds 512 characters |
+| `PROP_ARRAY_TOO_LONG` | The prop array exceeds 1000 items |
+| `PROP_CONTAINS_PROFANITY` | The prop key or value contains profanity |
+| `PROP_KEY_RESERVED` | The prop key is reserved by Talo |
+
+```gdscript
+func _on_props_rejected(rejected_props: Array[TaloRejectedProp]) -> void:
+ for rejected_prop in rejected_props:
+ match rejected_prop.error:
+ TaloRejectedProp.RejectionReason.PROP_KEY_TOO_LONG:
+ push_warning("Key too long: %s" % rejected_prop.key)
+ TaloRejectedProp.RejectionReason.PROP_VALUE_TOO_LONG:
+ push_warning("Value too long for key: %s" % rejected_prop.key)
+ TaloRejectedProp.RejectionReason.PROP_CONTAINS_PROFANITY:
+ push_warning("Profanity detected in key: %s" % rejected_prop.key)
+ _:
+ push_warning("Prop '%s' was rejected: %s" % [rejected_prop.key, rejected_prop.message])
+```
diff --git a/content/docs/pre-1.0/godot/player-relationships.mdx b/content/docs/pre-1.0/godot/player-relationships.mdx
new file mode 100644
index 0000000..13f1e0a
--- /dev/null
+++ b/content/docs/pre-1.0/godot/player-relationships.mdx
@@ -0,0 +1,280 @@
+---
+description: Player relationships link players together for social features like friends lists, parties and guilds.
+title: Player relationships
+---
+
+import { ScopeBadges } from '@/components/documentation/ScopeBadges'
+
+
+## How relationships work
+
+The Player Relationships API creates subscriptions between players. When a player broadcasts a message, all their subscribers receive it instantly.
+
+This flexible system supports both social features (friends lists with bidirectional relationships, follower systems with unidirectional relationships) and event-driven mechanics.
+
+Since broadcasts accept any data that can be converted to a string (text, JSON data, etc.), you can build pub/sub systems where players react to each other's actions - like levelling up, unlocking achievements, or completing challenges.
+
+
+ Check out the [player relationships feature page](https://trytalo.com/player-relationships) for
+ more details and frequently asked questions.
+
+
+### Friends list sample
+
+A complete sample is available in the Godot plugin at `addons/talo/samples/friends_list`. This demo showcases:
+
+- Sending and accepting friend requests
+- Real-time presence updates showing online friends
+- Broadcasting messages between friends
+
+## Subscribing to players
+
+
+
+To subscribe to a player, you need to first decide what type of relationship you want to create: unidirectional or bidirectional. Unidirectional relationships create a subscription to the target player and bidirectional relationships will create a reciprocal subscription between both players.
+
+You also need to know the target player alias' ID. You can get this through [player presence updates](/docs/pre-1.0/godot/player-presence), or by searching for players using [player search](/docs/pre-1.0/godot/player-props#searching-for-players).
+
+Once you have both of these pieces of information, you can create the relationship:
+
+```gdscript
+var search_page := await Talo.players.search("target_player_identifier")
+var target_alias_id := search_page.players[0].get_alias().id # this assumes we found at least one player
+
+# unidirectional relationship
+await Talo.player_relationships.subscribe_to(target_alias_id, TaloPlayerAliasSubscription.RelationshipType.UNIDIRECTIONAL)
+
+# or, bidirectional relationship
+await Talo.player_relationships.subscribe_to(target_alias_id, TaloPlayerAliasSubscription.RelationshipType.BIDIRECTIONAL)
+```
+
+This creates an **unconfirmed** subscription (or two for bidirectional relationships). Players will need to confirm subscription requests before broadcasts can be received by the subscribers.
+
+## Confirming subscriptions
+
+
+
+If you know the player alias ID of the player that sent you a subscription request, you can confirm it using `Talo.player_relationships.confirm_subscription_from()`:
+
+```gdscript
+func _ready():
+ # players receive this signal when they get a subscription request
+ Talo.player_relationships.relationship_request_received.connect(_on_relationship_request_received)
+
+func _on_relationship_request_received(player_alias: TaloPlayerAlias):
+ await Talo.player_relationships.confirm_subscription_from(player_alias.id)
+```
+
+Alternatively, you can list all pending subscription requests using `Talo.player_relationships.get_subscribers()`.
+
+This function allows you to filter by unconfirmed subscription requests. You can iterate over the results and confirm each subscription individually using their ID:
+
+```gdscript
+var options := Talo.player_relationships.GetSubscribersOptions.new()
+options.confirmed = Talo.player_relationships.ConfirmedFilter.UNCONFIRMED
+var subscribers_page := await Talo.player_relationships.get_subscribers(options)
+
+# loop through all unconfirmed subscriptions and confirm them
+for subscriber in subscribers_page.subscriptions:
+ await Talo.player_relationships.confirm_subscription_by_id(subscriber.id)
+```
+
+## Broadcasting messages
+
+
+
+Once relationships have been established, players can communicate with each other using broadcasts. When a player broadcasts a message, it is delivered to all of their confirmed subscribers (in a future release, Talo will support private/targeted broadcasts).
+
+To send a broadcast, use `Talo.player_relationships.broadcast()`:
+
+```gdscript
+# send a simple text message
+Talo.player_relationships.broadcast("Hello everyone!")
+
+# send structured data as JSON
+var event_data = {
+ "event": "level_up",
+ "level": 15,
+ "timestamp": Time.get_unix_time_from_system()
+}
+Talo.player_relationships.broadcast(JSON.stringify(event_data))
+```
+
+Subscribers will receive these broadcasts via the `message_received` signal (see [Signals](#signals) below).
+
+## Unsubscribing
+
+
+
+Only subscribers can revoke subscriptions - the player being subscribed to cannot remove their subscribers. When a bidirectional subscription is revoked, the reciprocal relationship is automatically deleted.
+
+You can unsubscribe in two ways:
+
+**By player alias ID** (recommended for most cases):
+
+```gdscript
+# unsubscribe from a player
+await Talo.player_relationships.unsubscribe_from(target_alias_id)
+```
+
+**By subscription ID** (if you already have the subscription object):
+
+```gdscript
+# revoke a specific subscription
+await Talo.player_relationships.revoke_subscription(subscription.id)
+```
+
+## Querying relationships
+
+
+
+### Checking subscription status
+
+Use `is_subscribed_to()` to check if the current player has a subscription to another player:
+
+```gdscript
+# check if subscribed (any status)
+var is_subscribed := await Talo.player_relationships.is_subscribed_to(target_alias_id, false)
+
+# check if subscribed AND confirmed
+var is_confirmed := await Talo.player_relationships.is_subscribed_to(target_alias_id, true)
+```
+
+### Listing subscriptions
+
+Get all players that the current player is subscribed to using `get_subscriptions()`:
+
+```gdscript
+# get all subscriptions
+var page := await Talo.player_relationships.get_subscriptions()
+for subscription in page.subscriptions:
+ print("Subscribed to: %s" % subscription.subscribed_to.identifier)
+```
+
+You can filter the results using options:
+
+```gdscript
+var options := Talo.player_relationships.GetSubscriptionsOptions.new()
+
+# filter by confirmation status
+options.confirmed = Talo.player_relationships.ConfirmedFilter.CONFIRMED # only confirmed
+options.confirmed = Talo.player_relationships.ConfirmedFilter.UNCONFIRMED # only unconfirmed
+options.confirmed = Talo.player_relationships.ConfirmedFilter.ANY # all (default)
+
+# filter by specific player alias ID
+options.alias_id = target_alias_id
+
+# filter by relationship type
+options.relationship_type = Talo.player_relationships.RelationshipTypeFilter.UNIDIRECTIONAL
+options.relationship_type = Talo.player_relationships.RelationshipTypeFilter.BIDIRECTIONAL
+options.relationship_type = Talo.player_relationships.RelationshipTypeFilter.ANY # all (default)
+
+# pagination
+options.page = 0 # page number (default: 0)
+
+var page := await Talo.player_relationships.get_subscriptions(options)
+```
+
+### Listing subscribers
+
+Get all players subscribed to the current player using `get_subscribers()`:
+
+```gdscript
+# get all subscribers
+var page := await Talo.player_relationships.get_subscribers()
+for subscription in page.subscriptions:
+ print("Subscriber: %s" % subscription.subscriber.identifier)
+```
+
+This function accepts the same filter options as `get_subscriptions()`:
+
+```gdscript
+var options := Talo.player_relationships.GetSubscribersOptions.new()
+
+# filter by confirmation status
+options.confirmed = Talo.player_relationships.ConfirmedFilter.UNCONFIRMED # pending requests
+
+# filter by specific player alias ID
+options.alias_id = subscriber_alias_id
+
+# filter by relationship type
+options.relationship_type = Talo.player_relationships.RelationshipTypeFilter.BIDIRECTIONAL
+
+# pagination
+options.page = 1
+
+var page := await Talo.player_relationships.get_subscribers(options)
+```
+
+## Signals
+
+
+
+The Player Relationships API emits signals for real-time relationship events. Connect to these signals to respond to relationship changes and incoming messages:
+
+### relationship_request_received
+
+Emitted when another player sends the current player a relationship request.
+
+```gdscript
+func _ready():
+ Talo.player_relationships.relationship_request_received.connect(_on_relationship_request_received)
+
+func _on_relationship_request_received(player_alias: TaloPlayerAlias):
+ print("%s wants to connect with you" % player_alias.identifier)
+ # optionally auto-confirm
+ await Talo.player_relationships.confirm_subscription_from(player_alias.id)
+```
+
+### relationship_request_cancelled
+
+Emitted when an unconfirmed relationship request is deleted by either the requester or recipient.
+
+```gdscript
+func _ready():
+ Talo.player_relationships.relationship_request_cancelled.connect(_on_request_cancelled)
+
+func _on_request_cancelled(player_alias: TaloPlayerAlias):
+ print("Request with %s was cancelled" % player_alias.identifier)
+```
+
+### relationship_confirmed
+
+Emitted when another player confirms your relationship request to them.
+
+```gdscript
+func _ready():
+ Talo.player_relationships.relationship_confirmed.connect(_on_relationship_confirmed)
+
+func _on_relationship_confirmed(player_alias: TaloPlayerAlias):
+ print("Now connected with %s" % player_alias.identifier)
+```
+
+### relationship_ended
+
+Emitted when a confirmed relationship is deleted by either player.
+
+```gdscript
+func _ready():
+ Talo.player_relationships.relationship_ended.connect(_on_relationship_ended)
+
+func _on_relationship_ended(player_alias: TaloPlayerAlias):
+ print("No longer connected with %s" % player_alias.identifier)
+```
+
+### message_received
+
+Emitted when a broadcast message is received from a connected player.
+
+```gdscript
+func _ready():
+ Talo.player_relationships.message_received.connect(_on_message_received)
+
+func _on_message_received(player_alias: TaloPlayerAlias, message: String):
+ print("%s sent: %s" % [player_alias.identifier, message])
+
+ # if the message is JSON, you can parse it
+ var json := JSON.new()
+ if json.parse(message) == OK:
+ print("Parsed data: %s" % json.data)
+```
diff --git a/content/docs/pre-1.0/godot/request-verification.mdx b/content/docs/pre-1.0/godot/request-verification.mdx
new file mode 100644
index 0000000..3ab567b
--- /dev/null
+++ b/content/docs/pre-1.0/godot/request-verification.mdx
@@ -0,0 +1,49 @@
+---
+description: Enable request verification in the Godot plugin to cryptographically sign network requests and socket messages, preventing replay attacks and tampering.
+title: Request verification
+---
+
+## How it works
+
+Request verification adds an extra layer of security by cryptographically signing every request and socket message sent by identified players. This prevents replay attacks and ensures requests haven't been tampered with in transit.
+
+When enabled, the Godot plugin automatically generates a signature for each request using your verification key. Talo validates the signature before processing the request.
+
+## Configuration
+
+Before enabling verification in your game, you must:
+
+1. Enable **Verify requests** on the [Game Settings page](https://dashboard.trytalo.com/game-settings).
+2. Create a verification key in the dashboard.
+3. Copy the key's **version** and **value** into your `addons/talo/settings.cfg`.
+
+
+
+Once configured, requests and socket messages are automatically signed for identified players.
+
+### Settings
+
+Add the following under the `[verification]` section of your `addons/talo/settings.cfg`:
+
+```gdscript title="addons/talo/settings.cfg"
+[verification]
+enabled=true
+key_version="1"
+key_value="your-secret-key"
+```
+
+#### `enabled`
+
+Enable this to start signing requests. You must also enable verification and add a verification key in the dashboard.
+
+#### `key_version`
+
+The version string of the verification key you created (e.g. `"1"`).
+
+#### `key_value`
+
+The secret value of the verification key.
+
+## Error handling
+
+If a signature is invalid or missing, the server responds with `401 Unauthorized` for HTTP requests or an `INVALID_SIGNATURE` socket error for WebSocket messages.
diff --git a/content/docs/pre-1.0/godot/saves.mdx b/content/docs/pre-1.0/godot/saves.mdx
new file mode 100644
index 0000000..e0b515e
--- /dev/null
+++ b/content/docs/pre-1.0/godot/saves.mdx
@@ -0,0 +1,187 @@
+---
+description: Talo Saves allow you to persist your game's state across multiple sessions. Each object in your scene can be saved and restored from one of your player's saves.
+title: Saves
+---
+
+import { ScopeBadges } from '@/components/documentation/ScopeBadges'
+
+## Saves demos
+
+As part of the Godot plugin, we've included a few demos to help you get started:
+
+1. [Playground saves](https://github.com/TaloDev/godot/tree/develop/addons/talo/samples/playground) - allows you to create/load/update/delete a very basic save
+2. [Persistent buttons](https://github.com/TaloDev/godot/tree/develop/addons/talo/samples/persistent_buttons) - a simple demo focused on saving/loading functionality
+3. [Scene-based saves](https://github.com/TaloDev/godot/tree/develop/addons/talo/samples/multiscene_saves) - a full demo showing how data gets persisted between multiple scenes and entities
+
+
+ Check out this blog post on [how to save and load data in
+ Godot](https://trytalo.com/blog/game-saves-godot?utm_source=docs&utm_medium=tip) for a detailed
+ walkthrough
+
+
+## Loadables
+
+Talo's game saves let you easily save and load specific nodes in your game. To do this, your nodes need to extend the `TaloLoadable` node. Generally this is the parent node for scenes but you can also have it as a child node (e.g. you may want your Loadable to be a child of a parent `CharacterBody2D`).
+
+### Registering loadables
+
+Once a Loadable has entered the tree, its `_ready()` function registers it with the saves manager. We need to register all the Loadables in the scene so that when we load our save, we can match the content in the save file with the structure of the scene.
+
+
+ If your Loadable overrides the `_ready()` function, ensure that it contains `super()` so that Talo
+ can still register the Loadable.
+
+
+Importantly, each Loadable _must_ have a unique `id` so that Talo knows which node to load with which data. This can be set in the inspector or in the code.
+
+```gdscript
+func _ready():
+ id = 'very-unique-collectible-name' # id must be unique
+ super() # ensure Talo registers the Loadable
+```
+
+### Saved objects
+
+Save files are collections of "saved objects". Here's what a typical saved object looks like:
+
+```typescript
+{
+ id: "uuid-uuid-uuid-uuid",
+ name: "/root/MyScene/Player",
+ data: [
+ {
+ key: "stars",
+ type: "2",
+ value: "5"
+ },
+ {
+ key: "spawn_point",
+ type: "5",
+ value: "Vector2(225, 166)"
+ },
+ {
+ key: "spawn_level",
+ type: "4",
+ value: "\"green_zone\""
+ }
+ ]
+}
+```
+
+
+ You can visualise players' save files as node graphs in the Talo dashboard. Just go to the
+ player's profile, click `Saves` and choose the save you want to view.
+
+
+Saved objects must have a unique `id`: this comes from the Loadable and is used to match the saved object with the correct Loadable. The `name` in a saved object refers to the `NodePath` of the Loadable in the scene.
+
+The most interesting part is the `data` which contains the fields we register in the Loadable (explained below) as well as the original `type` of the data. Notice that all values are serialised into strings. This is so that when the data is loaded, it can be easily converted back into its original type.
+
+### Data hydration
+
+When saves are created, all the Loadable nodes in your scene will be serialised into saved objects. Similarly, when a save is loaded, each saved object is paired up with a matching Loadable.
+
+Once paired up, the Loadable is hydrated with the latest data from the saved object. Hydrating the Loadable calls the Loadable's `on_loaded()` function with the data containing each value converted back to its original type.
+
+## Registering fields
+
+In order to save and load data, we need to tell Talo what we want to save. Here's an example of what that would look like for the player saved object above:
+
+```gdscript
+var stars := 0
+var spawn_level := "starting_zone"
+var spawn_point := Vector2.ZERO
+
+...
+
+func register_fields():
+ register_field("stars", stars)
+ register_field("spawn_point", spawn_point)
+ register_field("spawn_level", spawn_level)
+```
+
+Each field needs to be registered with the `register_field()` function. The `register_fields()` function is called just before data is serialised to ensure we have the latest data.
+
+## Loading data
+
+When a saved object is paired up with a Loadable, the Loadable is hydrated with the data from the saved object. This calls the `on_loaded()` function of the Loadable, where you can set the properties of the node:
+
+```gdscript
+func on_loaded(data: Dictionary):
+ stars = data.get("stars", 0)
+
+ spawn_point = data.get("spawn_point", Vector2.ZERO)
+ character_body.position = spawn_point
+
+ spawn_level = data.get("spawn_level", "starting_zone")
+ # check if we need to change the scene
+```
+
+### Destroyed loadables
+
+If a node is registered but is no longer valid when updating/creating a save (i.e. if `queue_free()` was used), a `meta.destroyed` key is saved as the only field for that object.
+
+You can handle destroyed objects using the `HandleDestroyed` function which will automatically `queue_free()` an object if it has the `meta.destroyed` key:
+
+```gdscript
+func on_loaded(data: Dictionary) -> void:
+ if handle_destroyed(data):
+ return
+
+ # load data as normal
+```
+
+## Loading saves
+
+
+
+You can load saves using `Talo.saves.get_saves()`. Once your saves have been fetched, the `Talo.saves.saves_loaded` signal is emitted.
+
+Saves can be accessed using `Talo.saves.all` or `Talo.saves.latest`. To load a save, use `Talo.saves.choose_save()` and pass in the save you want to load.
+
+Once your save has been chosen, the `Talo.saves.save_chosen` signal will emit.
+
+Finally, when all your registered Loadables have called their `on_loaded()`, an `Talo.saves.save_loading_completed` signal is emitted which can be used hide your loading screen.
+
+
+
+
+ You can find more details including code samples in our [Godot saves deep
+ dive](https://trytalo.com/blog/saves-deep-dive-godot?utm_source=docs&utm_medium=tip) post
+
+
+## Creating saves
+
+
+
+To create a save, use `Talo.saves.create_save()`. Your new save will automatically become your chosen save (although the `save_chosen` signal will not emit).
+
+Offline copies of saves are created in the [user data](https://docs.godotengine.org/en/stable/tutorials/io/data_paths.html#accessing-persistent-user-data-user) within an encrypted file (to prevent tampering).
+
+This will register all the fields for loadables in the scene to create the save file.
+
+## Updating saves
+
+
+
+To update a save, use `Talo.saves.update_save()`. You can optionally pass in a new name for your save. You can also update the current save using `Talo.saves.update_current_save()`.
+
+This will get the latest data for all loadables in the scene and check if any loadables should be marked as destroyed.
+
+## Deleting saves
+
+
+
+You can delete a save by passing in the save's ID to `Talo.saves.delete_save()`. Optionally you can pass in the `unload_if_current_save` flag which will [unload](#unloading-saves) the current save if it is the one being deleted.
+
+## Offline saves & syncing
+
+Talo automatically creates offline versions of saves. When network access is restored, Talo will attempt to resync the saves, preferring the most recently updated save if an online AND offline save exists.
+
+Additionally, if a save is only available offline then it will be synced as soon as a network connection is available.
+
+## Unloading saves
+
+You can unload a save using `Talo.saves.unload_current_save()` - this is useful when you want to completely reset the game.
+
+This emits the `Talo.saves.save_unloaded` signal and clears any saved object data, essentially reverting every scene back to its original state. The previously unloaded save's data will be unaffected.
diff --git a/content/docs/pre-1.0/godot/settings-reference.mdx b/content/docs/pre-1.0/godot/settings-reference.mdx
new file mode 100644
index 0000000..ef3474e
--- /dev/null
+++ b/content/docs/pre-1.0/godot/settings-reference.mdx
@@ -0,0 +1,114 @@
+---
+description: Customise Talo using the settings.cfg file.
+title: Settings reference
+---
+
+## Default settings
+
+When Talo first loads, a default `settings.cfg` is generated that looks like this:
+
+```gdscript title="addons/talo/settings.cfg"
+access_key=""
+api_url="https://api.trytalo.com"
+socket_url="wss://api.trytalo.com"
+auto_connect_socket=true
+handle_tree_quit=true
+cache_player_on_identify=true
+
+[continuity]
+enabled=true
+
+[player_auth]
+auto_start_session=false
+```
+
+## Accessing settings
+
+You can use the `Talo.settings` object to get or set individual options. You will need to call `Talo.settings.save_config()` after modifying a setting to persist the change.
+
+## Settings
+
+### access_key
+
+This is generated inside [the Talo dashboard](https://dashboard.trytalo.com). You'll need to fill out your `access_key` before making requests to the Talo API.
+
+The scopes on your API key determine whether certain actions can be performed. For example, if you want to fetch leaderboard entries, you'll need the `read:leaderboards` scope. If you want to create leaderboard entries, you'll need the `write:leaderboards` scope.
+
+At a minimum, the Talo Godot plugin requires the `read:players` and `write:players` scopes.
+
+### api_url
+
+This is the location of the Talo API. The default cloud version is `https://api.trytalo.com`. If you're [self-hosting Talo](/docs/pre-1.0/selfhosting/overview), this should be the address of your `backend` container.
+
+### socket_url
+
+This is usually the same as your `api_url` but with the `ws` or `wss` protocol.
+
+### auto_connect_socket
+
+If enabled, the plugin will automatically connect to the Talo Socket when the game starts.
+
+### handle_tree_quit
+
+When enabled, Talo will run the following code:
+
+```gdscript
+get_tree().set_auto_accept_quit(false)
+```
+
+This allows Talo to listen for the `NOTIFICATION_WM_CLOSE_REQUEST` notification, so that events can be flushed before the game exits. Learn more about [Godot notifications here](https://docs.godotengine.org/en/stable/tutorials/best_practices/godot_notifications.html).
+
+```gdscript
+func _notification(what: int):
+ match what:
+ NOTIFICATION_WM_CLOSE_REQUEST:
+ _do_flush()
+ if Talo.settings.handle_tree_quit:
+ get_tree().quit()
+ NOTIFICATION_APPLICATION_FOCUS_OUT, NOTIFICATION_APPLICATION_PAUSED:
+ _do_flush()
+```
+
+If this setting is disabled, `get_tree().quit()` will not be called after Talo flushes events. You will need to handle this notification and quitting the game yourself.
+
+### cache_player_on_identify
+
+If enabled, Talo will automatically cache the player after a successful online identification. If the player is offline and tries to identify in later sessions, Talo will attempt to use the cached the player data.
+
+### continuity.enabled
+
+If enabled, Talo will try to automatically replay failed network requests - [learn more about Continuity here](/docs/pre-1.0/godot/continuity).
+
+### player_auth.auto_start_session
+
+If enabled and a valid session token is found, the player will be automatically authenticated. This is documented on the [Player Authentication page](/docs/pre-1.0/godot/player-authentication#automatic-logins).
+
+### logging.requests
+
+If enabled, requests to the Talo API will be logged to the console.
+
+### logging.responses
+
+If enabled, responses from the Talo API will be logged to the console.
+
+### debug.offline_mode
+
+If enabled, Talo will simulate the player not having an internet connection. This is primarily useful for testing.
+
+### debounce_timer_seconds
+
+Controls the time (in seconds) between frequent requests such as updating player props, updating saves and health checks. The default value is 1 second.
+
+It is strongly recommended that you do not set this value below the default. A lower value can lead to slow responses or data being overwritten due to request conflicts. Increasing this value will reduce the frequency of requests and may help prevent data conflicts.
+
+### verification.enabled
+
+Enable request verification to cryptographically sign requests and socket messages. This must also be enabled in the Talo dashboard. Learn more on the [Request verification page](/docs/pre-1.0/godot/request-verification).
+
+### verification.key_version
+
+The version of the verification key being used (e.g. `"1"`).
+
+### verification.key_value
+
+The value for the verification key.
diff --git a/content/docs/pre-1.0/godot/socket.mdx b/content/docs/pre-1.0/godot/socket.mdx
new file mode 100644
index 0000000..f5d4e8e
--- /dev/null
+++ b/content/docs/pre-1.0/godot/socket.mdx
@@ -0,0 +1,95 @@
+---
+description: The Talo Socket automatically handles connections, messages and events.
+title: Talo Socket
+---
+
+import { ScopeBadges } from '@/components/documentation/ScopeBadges'
+
+## Usage
+
+Ideally you should never need to use the socket directly because individual services like players and channels should send the correct data on your behalf. However, the Talo Socket exposes a few public functions and signals for custom logic like handling errors and re-connecting to the server if the connection is closed.
+
+The socket connection is automatically established (this can disabled by setting `auto_connect_socket` to `false` in your config). When a player gets identified, they also get automatically identified with the socket server.
+
+You can learn more about how the socket works [here](/docs/pre-1.0/sockets/intro).
+
+## Obtaining a socket ticket
+
+
+
+To connect to the socket server, you need to obtain a socket ticket. This is done by calling `Talo.socket_tickets.create_ticket()`.
+
+Opening a socket connection using `Talo.socket.open_connection()` will automatically obtain a ticket and pass it as a query parameter to the socket URL.
+
+## Receiving messages
+
+The `message_received` signal provides you with a [response](/docs/pre-1.0/sockets/responses) and payload. For example, if you were building a chat system, you would connect a function similar to the one below to listen for new chat messages:
+
+```gdscript
+func _ready() -> void:
+ Talo.socket.message_received.connect(_on_message_received)
+
+func _on_message_received(res: String, data: Dictionary) -> void:
+ if res == "v1.channels.message":
+ if data.channel.id == _active_channel_id:
+ var alias := TaloPlayerAlias.new(data.playerAlias)
+ _add_chat_message("[%s] %s: %s" % [data.channel.name, alias.identifier, data.message])
+```
+
+## Sending messages
+
+Sending messages is handled by functions in services like `Talo.channels.send_message()` where the correct data is prepared and sent for you. You can also use the `Talo.socket.send(req, data)` function to send your own [requests](/docs/pre-1.0/sockets/requests):
+
+```gdscript
+func send_message(channel_id: int, message: String) -> void:
+ if Talo.identity_check() != OK:
+ return
+
+ Talo.socket.send("v1.channels.message", {
+ channel = {
+ id = channel_id
+ },
+ message = message
+ })
+```
+
+## Handling connection closures
+
+
+
+The socket server can disconnect for a number of reasons such as the player going offline or being [rate limited](/docs/pre-1.0/sockets/common-errors#rate-limit-exceeded). The socket will emit a `connection_closed` signal with a status code and reason.
+
+The Talo socket automatically reconnects when the [connection is restored](continuity#connection_restored). If you need to manually re-open the socket, you can establish a connection and re-identify the player like this:
+
+```gdscript
+func reconnect_socket():
+ await Talo.socket.reset_connection()
+ if Talo.identity_check() == OK:
+ var socket_token := await Talo.players.create_socket_token()
+ Talo.socket.set_socket_token(socket_token)
+```
+
+## Closing the connection
+
+You can choose to manually end the socket connection using `Talo.socket.close_connection()`. You can provide a custom status code (the default is 1000) and close reason.
+
+## Request verification
+
+If your game has [request verification](/docs/pre-1.0/godot/request-verification) enabled, socket messages are automatically signed for players. The server validates these signatures to prevent replay attacks and tampering.
+
+## Error handling
+
+The `error_received` signal is emitted when a `v1.error` response is received. You can check the error code (using the `TaloSocketError.ErrorCode` enum), message and original request through the `TaloSocketError` object that is sent with the signal:
+
+```gdscript
+Talo.socket.error_received.connect(
+ func (err: TaloSocketError):
+ print("%s %s %s %s" % [err.req, err.code, err.message, err.cause])
+
+ match err.code:
+ TaloSocketError.ErrorCode.NO_PLAYER_FOUND:
+ print("Player not identified yet!")
+ TaloSocketError.ErrorCode.RATE_LIMIT_EXCEEDED:
+ print("Rate limited!")
+)
+```
diff --git a/content/docs/pre-1.0/godot/stats.mdx b/content/docs/pre-1.0/godot/stats.mdx
new file mode 100644
index 0000000..51856a3
--- /dev/null
+++ b/content/docs/pre-1.0/godot/stats.mdx
@@ -0,0 +1,177 @@
+---
+description: Talo stats let you track individual player data as well as aggregated global data - like how many items have been crafted or how many deaths a player has.
+title: Stats
+---
+
+import { ScopeBadges } from '@/components/documentation/ScopeBadges'
+
+## Creating stats
+
+To create a stat, visit [the stats page](https://dashboard.trytalo.com/stats) inside the Talo dashboard. Take note of the `Internal name` as this is how you'll be referring to your stat.
+
+
+ Check out this blog post on [how to track player stats in
+ Godot](https://trytalo.com/blog/stat-tracking-godot?utm_source=docs&utm_medium=tip) for a detailed
+ walkthrough
+
+
+## Getting stats
+
+
+
+You can list stats using `Talo.stats.get_stats()`.
+
+This will return all the constraint data defined in the dashboard like the `default_value`, `max_change` and `max_value`. This will also return the global values for global stats:
+
+```gdscript
+var res := await Talo.stats.get_stats()
+var stat_names := res.map(
+ func(item: TaloStat):
+ return item.internal_name
+)
+
+var internal_names := ", ".join(PackedStringArray(stat_names))
+
+## e.g. Stats: gold-collected, health-healed, deaths
+print("Stats: %s" % [internal_names])
+```
+
+### Fetching individual stats
+
+If you already have the internal name of a stat, you can use `Talo.stats.find()` and pass in the internal name to return data for a single stat.
+
+## Tracking stats
+
+
+
+You can track a stat using the Stats API `track()` function which takes your stat's `internal_name` and the amount to change the stat by (default 1.0):
+
+```gdscript title="player.gd"
+func on_death() -> void:
+ Talo.stats.track('deaths')
+```
+
+```gdscript title="player_potion.gd"
+var health: float
+
+func on_heal(potion: Potion) -> void:
+ health += potion.amount
+ Talo.stats.track('health-healed', potion.amount)
+```
+
+### Stat values
+
+After updating a stat using `track()`, you can check the updated value for the player stat and global stat:
+
+```gdscript
+var res := await Talo.stats.track(stat_name)
+print("%s, %s" % [res.value, res.stat.global_value])
+```
+
+#### Find a stat value
+
+
+
+You can also get the current value of a stat for a player using `Talo.stats.find_player_stat()`.
+
+## Listing player stat values
+
+
+
+If you need to find all player stat values for the current player, you can use `Talo.stats.list_player_stats()`. This function returns a list of player stats which include the value and related stat:
+
+```gdscript
+var res := await Talo.stats.list_player_stats()
+var values := PackedStringArray(
+ res.map(
+ func (item: TaloPlayerStat): return "%s = %s" % [item.stat.internal_name, item.value]
+ )
+)
+var player_stat_values := ", ".join(values) if values.size() > 0 else "none"
+print("Player stats: %s" % [player_stat_values]) # Player stats: deaths = 5, health-healed = 200
+```
+
+The function above iterates through the player stats and picks out the `stat.internal_name` and `value`.
+
+## Stat history
+
+
+
+You can fetch a history of updates to a stat for the current player using `Talo.stats.get_history()`. These results are paginated and can be filtered by specific start and end dates:
+
+```gdscript
+func fetch_history() -> void:
+ var res := await Talo.stats.get_history(stat_name)
+ var changes := PackedStringArray(res.history.map(func(item): return str(item.change)))
+
+ # e.g. "gold-collected changed by 100, 46, 82, 19, 104"
+ print("%s changed by: %s" % [stat_name, ", ".join(changes)])
+```
+
+### Filtering stat history
+
+The `get_history()` function accepts the following optional parameters:
+
+- `page`: Page number for pagination (default: 0)
+- `start_date`: Filter to show only changes tracked on or after this date. This can be a UTC Date (YYYY-MM-DD), DateTime (ISO 8601), or millisecond timestamp
+- `end_date`: Filter to show only changes tracked on or before this date. This can be a UTC Date (YYYY-MM-DD), DateTime (ISO 8601), or millisecond timestamp
+
+```gdscript
+# Get the second page of history
+var res := await Talo.stats.get_history(stat_name, 1)
+
+# Get history for September 2025
+var res := await Talo.stats.get_history(stat_name, 0, "2025-09-01", "2025-09-30")
+
+# Get all history since a specific date
+var res := await Talo.stats.get_history(stat_name, 0, "2025-09-15")
+```
+
+## Global stat history
+
+
+
+For global stats, you can fetch a history of updates from all players using `Talo.stats.get_global_history()`.
+
+This returns the following metrics about the stat:
+
+```gdscript
+func fetch_global_history() -> void:
+ var res := await Talo.stats.get_global_history(stat_name)
+ var total_updates := res.count
+ var global_metrics := res.global_value
+ var player_metrics := res.player_value
+
+ print("Total: %s, Min: %s, max: %s, median: %s, average: %s, average change: %s, average player value: %s" % [
+ total_updates,
+ global_metrics.min_value,
+ global_metrics.max_value,
+ global_metrics.median_value,
+ global_metrics.average_value, # average global value
+ global_metrics.average_change,
+ player_metrics.average_value # average player value
+ ])
+```
+
+### Filtering global stat history
+
+The `get_global_history()` function accepts the following optional parameters:
+
+- `page`: Page number for pagination (default: 0)
+- `player_id`: Filter to show only changes from a specific player ID
+- `start_date`: Filter to show only changes tracked on or after this date. This can be a UTC Date (YYYY-MM-DD), DateTime (ISO 8601), or millisecond timestamp
+- `end_date`: Filter to show only changes tracked on or before this date. This can be a UTC Date (YYYY-MM-DD), DateTime (ISO 8601), or millisecond timestamp
+
+```gdscript
+# Get global history for a specific player
+var res := await Talo.stats.get_global_history(stat_name, 0, "player-123")
+
+# Get global history for September 2025
+var res := await Talo.stats.get_global_history(stat_name, 0, "", "2025-09-01", "2025-09-30")
+
+# Get global history for a specific player in a date range
+var res := await Talo.stats.get_global_history(stat_name, 0, "player-123", "2025-09-01", "2025-09-30")
+
+# Get the second page of global history
+var res := await Talo.stats.get_global_history(stat_name, 1)
+```
diff --git a/content/docs/pre-1.0/http/authentication.mdx b/content/docs/pre-1.0/http/authentication.mdx
new file mode 100644
index 0000000..40730ae
--- /dev/null
+++ b/content/docs/pre-1.0/http/authentication.mdx
@@ -0,0 +1,42 @@
+---
+description: Learn how to authenticate your API requests.
+title: Authentication
+---
+
+## Creating and using an API key
+
+To create an API key, visit the [API keys page](https://dashboard.trytalo.com/api-keys) inside the Talo dashboard.
+
+After creating the key, you should use it in the `Authorization` header:
+
+```
+curl \
+-X POST \
+-H 'Authorization: Bearer eyJhb..........w5c' \
+-H 'Content-type: application/json' \
+'https://api.trytalo.com/v1/events'
+```
+
+## Player authentication
+
+Most endpoints perform actions on behalf of players.If you are using [player authentication](https://trytalo.com/players#authentication) for these endpoints, you must send the following headers:
+
+1. `x-talo-player`: the ID of the player
+2. `x-talo-alias`: the ID of the player's identified alias
+3. `x-talo-session`: the session token received after registering or logging-in a player
+
+
+ The Godot plugin and Unity package will automatically populate these headers for you. If you're
+ receiving session-related errors, visit the [Common
+ errors](/docs/pre-1.0/http/common-errors#missing-or-invalid-session) page for more information.
+
+
+### Session token lifetimes
+
+By default, session tokens returned by [register, login, verify, and refresh](/docs/pre-1.0/http/player-auth-api) are long-lived and do not need to be renewed. Your game can store the token on the device and reuse it for as long as the player keeps playing.
+
+If you prefer shorter-lived sessions, you can opt in to the refresh token flow using the `withRefresh` parameter. When enabled:
+
+- The session token expires after **15 minutes**.
+- A **30-day refresh token** is returned alongside it.
+- Call the [refresh session](/docs/pre-1.0/http/player-auth-api) endpoint with the refresh token to receive a new session and refresh token.
diff --git a/content/docs/pre-1.0/http/bruno-collection.mdx b/content/docs/pre-1.0/http/bruno-collection.mdx
new file mode 100644
index 0000000..f9ca2a5
--- /dev/null
+++ b/content/docs/pre-1.0/http/bruno-collection.mdx
@@ -0,0 +1,55 @@
+---
+description: Interactive API explorer for the Talo API
+title: Bruno request collection
+---
+
+[Bruno](https://www.usebruno.com) is an open-source API client (a Postman alternative) for exploring and testing REST APIs. A pre-built Talo collection is available so you can try every endpoint.
+
+## Interactive explorer
+
+
+ Open the API explorer in a new tab
+
+
+## Install Bruno
+
+Bruno is a desktop app available for macOS, Windows and Linux. You can download it from [usebruno.com](https://www.usebruno.com).
+
+## Import the Talo collection
+
+1. Open Bruno and click **Import Collection**.
+2. Paste the repository URL: `https://github.com/TaloDev/bruno-collection.git`.
+3. Choose a location to save the collection locally.
+4. Click **Clone**.
+
+The full collection is available in [this repository](https://github.com/TaloDev/bruno-collection).
+
+## Configure the environment
+
+The collection ships with a `Prod` environment that has two variables:
+
+| Variable | Description |
+| --------- | ----------------------------------------------------------------------------------------------------- |
+| `baseUrl` | The API root. Defaults to `https://api.trytalo.com` - change this to point at a self-hosted instance. |
+| `apiKey` | An API key from the [Talo dashboard](https://dashboard.trytalo.com/api-keys). |
+
+Requests will return `401 Unauthorized` until you set `apiKey`.
+
+To edit the environment in Bruno: click the environment name (`Prod`) in the top-right, then click **Configure**.
+
+## Acting on behalf of a player
+
+Most player-scoped endpoints expect two headers in addition to the API key:
+
+1. `x-talo-player` - the player's ID
+2. `x-talo-alias` - the ID of one of the player's identified alias
+
+To get these, run the [Identify](/docs/pre-1.0/http/player-api) endpoint first (found in the **Players** folder of the collection). This will automatically set the variables for both of those headers.
+
+### Talo player authentication
+
+If your game uses [Talo's player authentication](/docs/pre-1.0/http/player-auth-api), you'll also need the `x-talo-session` header.
+
+Run the **register** and **login** requests (from the **Player auth** folder). This will set the `x-talo-session` variable and refresh token variable (for refreshing the short-lived session token).
+
+See [Player authentication](/docs/pre-1.0/http/authentication#player-authentication) for the full flow.
diff --git a/content/docs/pre-1.0/http/common-errors.mdx b/content/docs/pre-1.0/http/common-errors.mdx
new file mode 100644
index 0000000..aa2a9b2
--- /dev/null
+++ b/content/docs/pre-1.0/http/common-errors.mdx
@@ -0,0 +1,58 @@
+---
+description: Debug common issues with API requests.
+title: Common errors
+---
+
+## Missing authentication
+
+`401 Unauthorized`
+
+The `Authorization` header is required, with its value set to `Bearer your-access-key-token`.
+
+## Missing scopes
+
+`403 Forbidden - { message: 'Missing API key scope(s): [scopes]' }`
+
+This means your API key is missing scopes for the specified resource. You can update your API key inside the dashboard.
+
+Generally, `GET` requests require `read` access, `POST/PUT/PATCH/DELETE` requests require `write` access.
+
+## Missing parameters
+
+`400 Bad Request`
+
+When omitting a required parameter (e.g. a query param, body param or header), you'll usually receive this error. Refer to the documentation for the API to make sure your request is correct.
+
+The error object returned may be a single message, for example `{ message: Something went wrong }`, or an error object, which includes a key for every invalid/missing key:
+
+```
+errors: {
+ events: ['events is missing from the request body']
+}
+```
+
+## Rate-limiting
+
+`429 Too Many Requests`
+
+Receiving this status means you've hit Talo's rate limit. Rate limits use a fixed window (resets every minute), so you should aim to make less than 100 requests per minute, per player. You can achieve this by, for example, batching event and stat updates.
+
+Authentication endpoints (e.g. `/v1/players/auth/*`, `/v1/players/identify` and `/v1/socket-tickets`) are limited to 20 requests per minute, per player.
+
+The `Retry-After` header will return how many seconds to wait before retrying - typically this will be set to '60' (the remaining seconds until the window resets).
+
+## Missing or invalid session
+
+```
+401 Authorized
+{ message: 'The x-talo-session header is required for this player', errorCode: 'MISSING_SESSION' }
+```
+
+```
+401 Authorized
+{ message: 'The x-talo-session header is invalid', errorCode: 'INVALID_SESSION' }
+```
+
+When calling API endpoints on behalf of a player (i.e. using the `x-talo-player` or `x-talo-alias` header) that is using [player authentication](https://trytalo.com/players#authentication) you must also send a valid `x-talo-session` header.
+
+You'll need to include the `x-talo-player`, `x-talo-alias` and `x-talo-session` header which should be set to the token you receive from registering or logging-in a player.
diff --git a/content/docs/pre-1.0/http/dev-data.mdx b/content/docs/pre-1.0/http/dev-data.mdx
new file mode 100644
index 0000000..04adcd2
--- /dev/null
+++ b/content/docs/pre-1.0/http/dev-data.mdx
@@ -0,0 +1,23 @@
+---
+description: Separate your development data from live data inside the Talo dashboard.
+title: Separating development data
+---
+
+## Dev build headers
+
+It's very common to test your development builds against Talo services you've already configured like stats and leaderboards.
+Usually, you'll want to separate out development data from live data and Talo automates this for you.
+
+If the `X-Talo-Dev-Build` header is set to `1`, any data being sent will be marked as development data and can be separated out in the dashboard.
+
+To also include development data when fetching entities (like leaderboard entries), you should set the `X-Talo-Include-Dev-Data` header to `1`.
+
+## "Dev" players
+
+Dev players are players created when you use the headers described above. Their data will be permanently marked as development data - regardless if they create any new data without the headers.
+
+In the dashboard, this is denoted by orange backgrounds:
+
+
+
+You can toggle a player's dev status from their profile.
diff --git a/content/docs/pre-1.0/http/event-api.mdx b/content/docs/pre-1.0/http/event-api.mdx
new file mode 100644
index 0000000..ec42636
--- /dev/null
+++ b/content/docs/pre-1.0/http/event-api.mdx
@@ -0,0 +1,49 @@
+---
+description: Event tracking allows you to understand what players are doing inside your game.
+title: Event API
+---
+
+import { ServiceDocumentation } from '@/components/documentation/ServiceDocumentation'
+import { generateServiceTOC } from '@/lib/api-docs'
+
+export const service = 'EventAPI'
+
+export const pageToc = [
+ { title: 'Overview', url: '#overview', depth: 2 },
+ { title: 'Endpoints', url: '#endpoints', depth: 2 },
+ ...generateServiceTOC(service),
+ { title: 'Types', url: '#types', depth: 2 },
+ { title: 'EventData', url: '#eventdata', depth: 3 },
+]
+
+## Overview
+
+Event tracking lets you get rid of the guess-work. By tracking important actions when they happen, you're able to gauge how well a mechanic is being used and even track the overall progress of your players
+
+See how often potions get used, items get bought and how many level-ups there are per day, month or year.
+
+Learn more about events here.
+
+## Endpoints
+
+
+
+## Types
+
+### EventData
+
+```ts
+type EventData = {
+ name: string // name of the event
+ timestamp: number // UNIX milisecond timestamp, e.g. 1656971058297
+ props?: Prop[]
+}
+
+type Prop = {
+ key: string
+ value: string
+}
+```
diff --git a/content/docs/pre-1.0/http/game-channel-api.mdx b/content/docs/pre-1.0/http/game-channel-api.mdx
new file mode 100644
index 0000000..f2ae56c
--- /dev/null
+++ b/content/docs/pre-1.0/http/game-channel-api.mdx
@@ -0,0 +1,41 @@
+---
+description: Talo Channels can be used for player chats, sending data to specific groups, pushing game updates and storing shared game state.
+title: Game channel API
+---
+
+import { ServiceDocumentation } from '@/components/documentation/ServiceDocumentation'
+import { generateServiceTOC } from '@/lib/api-docs'
+
+export const service = 'GameChannelAPI'
+
+export const pageToc = [
+ { title: 'Overview', url: '#overview', depth: 2 },
+ { title: 'Endpoints', url: '#endpoints', depth: 2 },
+ ...generateServiceTOC(service),
+ { title: 'Types', url: '#types', depth: 2 },
+ { title: 'Prop', url: '#prop', depth: 3 },
+]
+
+## Overview
+
+Channels are a way to send messages between players in your game. You can use channels to create chat rooms, send notifications or create any other kind of messaging system you can think of.
+
+Learn more about channels [here](https://trytalo.com/channels?utm_source=docs&utm_medium=api-docs).
+
+## Endpoints
+
+
+
+## Types
+
+### Prop
+
+```ts
+type Prop = {
+ key: string
+ value: string | null
+}
+```
diff --git a/content/docs/pre-1.0/http/game-config-api.mdx b/content/docs/pre-1.0/http/game-config-api.mdx
new file mode 100644
index 0000000..0cfcb37
--- /dev/null
+++ b/content/docs/pre-1.0/http/game-config-api.mdx
@@ -0,0 +1,30 @@
+---
+description: Live config lets you push data to your game from the Talo dashboard without releasing a new update.
+title: Game config API
+---
+
+import { ServiceDocumentation } from '@/components/documentation/ServiceDocumentation'
+import { generateServiceTOC } from '@/lib/api-docs'
+
+export const service = 'GameConfigAPI'
+
+export const pageToc = [
+ { title: 'Overview', url: '#overview', depth: 2 },
+ { title: 'Endpoints', url: '#endpoints', depth: 2 },
+ ...generateServiceTOC(service),
+]
+
+## Overview
+
+Talo's live config allows you to configure your game directly from the dashboard. Your game can then fetch this config on-demand, allowing your game and players to have the latest data without downloading a new update.
+
+Live config is simply a collection of key-value pairs. For example, you could have a key of "maxLevel" which has value of "10". While running a beta test you can incrementally increase this maximum level to allow access to more content at your own pace.
+
+Learn more about live config [here](https://trytalo.com/live-config?utm_source=docs&utm_medium=api-docs).
+
+## Endpoints
+
+
diff --git a/content/docs/pre-1.0/http/game-feedback-api.mdx b/content/docs/pre-1.0/http/game-feedback-api.mdx
new file mode 100644
index 0000000..07760ac
--- /dev/null
+++ b/content/docs/pre-1.0/http/game-feedback-api.mdx
@@ -0,0 +1,28 @@
+---
+description: Talo Feedback allows you to receive feedback directly from players. Feedback can be categorised, analysed and filtered to improve your game.
+title: Game feedback API
+---
+
+import { ServiceDocumentation } from '@/components/documentation/ServiceDocumentation'
+import { generateServiceTOC } from '@/lib/api-docs'
+
+export const service = 'GameFeedbackAPI'
+
+export const pageToc = [
+ { title: 'Overview', url: '#overview', depth: 2 },
+ { title: 'Endpoints', url: '#endpoints', depth: 2 },
+ ...generateServiceTOC(service),
+]
+
+## Overview
+
+Using Talo's Feedback API, you can effortlessly collect valuable player feedback to improve your game.
+
+Learn more about feedback [here](https://trytalo.com/feedback?utm_source=docs&utm_medium=api-docs).
+
+## Endpoints
+
+
diff --git a/content/docs/pre-1.0/http/game-save-api.mdx b/content/docs/pre-1.0/http/game-save-api.mdx
new file mode 100644
index 0000000..c7ac8aa
--- /dev/null
+++ b/content/docs/pre-1.0/http/game-save-api.mdx
@@ -0,0 +1,52 @@
+---
+description: Talo Saves allow you to persist your game's state across multiple sessions. Each object in your scene can be saved and restored from one of your player's saves.
+title: Game save API
+---
+
+import { ServiceDocumentation } from '@/components/documentation/ServiceDocumentation'
+import { generateServiceTOC } from '@/lib/api-docs'
+
+export const service = 'GameSaveAPI'
+
+export const pageToc = [
+ { title: 'Overview', url: '#overview', depth: 2 },
+ { title: 'Endpoints', url: '#endpoints', depth: 2 },
+ ...generateServiceTOC(service),
+ { title: 'Types', url: '#types', depth: 2 },
+ { title: 'SaveContent', url: '#savecontent', depth: 3 },
+]
+
+## Overview
+
+Talo stores saves for players using JSON. Save data structures are completely flexible and dictated by your game.
+
+Learn more about game saves [here](https://trytalo.com/saves?utm_source=docs&utm_medium=api-docs).
+
+## Endpoints
+
+
+
+## Types
+
+### SaveContent
+
+```ts
+type SaveContent = {
+ objects: SavedObject[]
+}
+
+type SavedObject = {
+ id: UUID
+ name: string // e.g. name of an object in your scene hierarchy
+ data: SavedObjectData[]
+}
+
+type SavedObjectData = {
+ key: string
+ value: string
+ type: string // class to cast value to on-load, e.g. System.Boolean in C#
+}
+```
diff --git a/content/docs/pre-1.0/http/game-stat-api.mdx b/content/docs/pre-1.0/http/game-stat-api.mdx
new file mode 100644
index 0000000..63a153b
--- /dev/null
+++ b/content/docs/pre-1.0/http/game-stat-api.mdx
@@ -0,0 +1,28 @@
+---
+description: Talo stats let you track individual player data as well as aggregated global data - like how many items have been crafted or how many deaths a player has.
+title: Game stat API
+---
+
+import { ServiceDocumentation } from '@/components/documentation/ServiceDocumentation'
+import { generateServiceTOC } from '@/lib/api-docs'
+
+export const service = 'GameStatAPI'
+
+export const pageToc = [
+ { title: 'Overview', url: '#overview', depth: 2 },
+ { title: 'Endpoints', url: '#endpoints', depth: 2 },
+ ...generateServiceTOC(service),
+]
+
+## Overview
+
+With Talo, you can track game stats on a per-player basis. For example, you could track the number of secrets discovered by a player or the amount of puzzles they've solved.
+
+Learn more about game stats [here](https://trytalo.com/stats?utm_source=docs&utm_medium=api-docs).
+
+## Endpoints
+
+
diff --git a/content/docs/pre-1.0/http/leaderboard-api.mdx b/content/docs/pre-1.0/http/leaderboard-api.mdx
new file mode 100644
index 0000000..7770971
--- /dev/null
+++ b/content/docs/pre-1.0/http/leaderboard-api.mdx
@@ -0,0 +1,29 @@
+---
+description: Talo leaderboards are highly customisable - sort modes, entry uniqueness and refresh intervals can all be easily configured per-leaderboard.
+title: Leaderboard API
+---
+
+import { ServiceDocumentation } from '@/components/documentation/ServiceDocumentation'
+import { generateServiceTOC } from '@/lib/api-docs'
+
+
+export const service = 'LeaderboardAPI'
+
+export const pageToc = [
+ { title: 'Overview', url: '#overview', depth: 2 },
+ { title: 'Endpoints', url: '#endpoints', depth: 2 },
+ ...generateServiceTOC(service),
+]
+
+## Overview
+
+Talo works across all game genres and therefore leaderboards are highly-customisable. Leaderboards can be sorted in descending or ascending order. You can also customise whether players can only have one or multiple entries in a single leaderboard.
+
+Learn more about leaderboards [here](https://trytalo.com/leaderboards?utm_source=docs&utm_medium=api-docs).
+
+## Endpoints
+
+
diff --git a/content/docs/pre-1.0/http/meta.json b/content/docs/pre-1.0/http/meta.json
new file mode 100644
index 0000000..da6877d
--- /dev/null
+++ b/content/docs/pre-1.0/http/meta.json
@@ -0,0 +1,24 @@
+{
+ "pages": [
+ "authentication",
+ "common-errors",
+ "dev-data",
+ "request-verification",
+ "bruno-collection",
+ "event-api",
+ "game-channel-api",
+ "game-config-api",
+ "game-feedback-api",
+ "game-save-api",
+ "game-stat-api",
+ "leaderboard-api",
+ "player-api",
+ "player-auth-api",
+ "player-group-api",
+ "player-presence-api",
+ "player-relationships-api",
+ "socket-ticket-api"
+ ],
+ "title": "API reference",
+ "icon": "http"
+}
diff --git a/content/docs/pre-1.0/http/player-api.mdx b/content/docs/pre-1.0/http/player-api.mdx
new file mode 100644
index 0000000..b91a3f9
--- /dev/null
+++ b/content/docs/pre-1.0/http/player-api.mdx
@@ -0,0 +1,43 @@
+---
+description: Talo allows you to easily authenticate players, create player aliases and sync identities with external identity providers.
+title: Player API
+---
+
+import { ServiceDocumentation } from '@/components/documentation/ServiceDocumentation'
+import { generateServiceTOC } from '@/lib/api-docs'
+
+export const service = 'PlayerAPI'
+
+export const pageToc = [
+ { title: 'Overview', url: '#overview', depth: 2 },
+ { title: 'Endpoints', url: '#endpoints', depth: 2 },
+ ...generateServiceTOC(service),
+ { title: 'Types', url: '#types', depth: 2 },
+ { title: 'Prop', url: '#prop', depth: 3 },
+]
+
+## Overview
+
+Talo provides a flexible and secure player authentication system. You can use Talo to authenticate players using their email, username, or any other identifier you choose.
+
+Players can have any number of properties that will persist between game sessions and all of their identities. You can use these, for example, to store a player's current health or level.
+
+Learn more about player management [here](https://trytalo.com/players?utm_source=docs&utm_medium=api-docs).
+
+## Endpoints
+
+
+
+## Types
+
+### Prop
+
+```ts
+type Prop = {
+ key: string
+ value: string | null
+}
+```
diff --git a/content/docs/pre-1.0/http/player-auth-api.mdx b/content/docs/pre-1.0/http/player-auth-api.mdx
new file mode 100644
index 0000000..421e1f6
--- /dev/null
+++ b/content/docs/pre-1.0/http/player-auth-api.mdx
@@ -0,0 +1,213 @@
+---
+description: Talo player authentication fully manages player login credentials, registrations and email verifications.
+title: Player auth API
+---
+
+import { ServiceDocumentation } from '@/components/documentation/ServiceDocumentation'
+import { generateServiceTOC } from '@/lib/api-docs'
+
+export const service = 'PlayerAuthAPI'
+
+export const pageToc = [
+ { title: 'Overview', url: '#overview', depth: 2 },
+ { title: 'Endpoints', url: '#endpoints', depth: 2 },
+ ...generateServiceTOC(service),
+ {
+ title: 'Identifying players using Talo Authentication',
+ url: '#identifying-players-using-talo-authentication',
+ depth: 2,
+ },
+ { title: 'Error codes', url: '#error-codes', depth: 2 },
+]
+
+## Overview
+
+Talo provides a flexible and secure authentication system out of the box. You can use Talo to authenticate players using their email, username, or any other identifier you choose.
+
+Learn more about player authentication [here](https://trytalo.com/players#authentication?utm_source=docs&utm_medium=api-docs).
+
+## Identifying players using Talo Authentication
+
+Once you have registered a player, you can identify them using the [identify](/docs/pre-1.0/http/player-api) endpoint. You will need to ensure that:
+
+1. The `identifier` is the one you used to register the player (email, username, etc.)
+2. The `service` is set to `talo`
+3. The `x-talo-session` header contains the session token you received from the login/register endpoint
+
+To create a new session token, you will need to go through the login flow again for the player.
+
+
+ By default session tokens are long-lived. You can opt in to short-lived (15-minute) session tokens
+ paired with a 30-day refresh token using the `withRefresh` parameter. See [Session token
+ lifetimes](/docs/pre-1.0/http/authentication#session-token-lifetimes) for the full details.
+
+
+## Endpoints
+
+
+
+## Error codes
+
+### `INVALID_CREDENTIALS`
+
+When authentication fails (i.e. wrong identifier or password), this error is returned. This error also occurs when changing a player password, email address, or identifier, or when toggling verification and the current password entered is incorrect.
+
+```javascript
+{
+ message: 'Incorrect identifier or password',
+ errorCode: 'INVALID_CREDENTIALS'
+}
+```
+
+```javascript
+{
+ message: 'Current password is incorrect',
+ errorCode: 'INVALID_CREDENTIALS'
+}
+```
+
+### `VERIFICATION_ALIAS_NOT_FOUND`
+
+When trying to verify a player login, this error is thrown if the `aliasId` is not valid.
+
+```javascript
+{
+ message: 'Player alias not found',
+ errorCode: 'VERIFICATION_ALIAS_NOT_FOUND'
+}
+```
+
+### `VERIFICATION_CODE_INVALID`
+
+When trying to verify a player login, this error is thrown if the `code` is not valid.
+
+```javascript
+{
+ message: 'Invalid code',
+ errorCode: 'VERIFICATION_CODE_INVALID'
+}
+```
+
+### `IDENTIFIER_TAKEN`
+
+When trying to create a new player or update an existing player's identifier, this error is thrown if the `identifier` is already taken.
+
+```javascript
+{
+ message: 'Player with identifier boz already exists',
+ errorCode: 'IDENTIFIER_TAKEN'
+}
+```
+
+### `MISSING_SESSION`
+
+Players that use Talo Authentication must provide a session token in the request headers. This error is thrown when the session token is missing.
+
+```javascript
+{
+ message: 'The x-talo-session header is required for this player',
+ errorCode: 'MISSING_SESSION'
+}
+```
+
+### `INVALID_SESSION`
+
+Players that use Talo Authentication must provide a session token in the request headers. This error is thrown when the session token is invalid.
+
+```javascript
+{
+ message: 'The x-talo-session header is invalid',
+ errorCode: 'INVALID_SESSION'
+}
+```
+
+### `NEW_PASSWORD_MATCHES_CURRENT_PASSWORD`
+
+When trying to change a player's password, this error is thrown if the new password matches the current password.
+
+```javascript
+{
+ message: 'Please choose a different password',
+ errorCode: 'NEW_PASSWORD_MATCHES_CURRENT_PASSWORD'
+}
+```
+
+### `NEW_EMAIL_MATCHES_CURRENT_EMAIL`
+
+When trying to change a player's email address, this error is thrown if the new email matches the current email.
+
+```javascript
+{
+ message: 'Please choose a different email address',
+ errorCode: 'NEW_EMAIL_MATCHES_CURRENT_EMAIL'
+}
+```
+
+### `NEW_IDENTIFIER_MATCHES_CURRENT_IDENTIFIER`
+
+When trying to change a player's identifier, this error is thrown if the new identifier matches the current identifier.
+
+```javascript
+{
+ message: 'Please choose a different identifier',
+ errorCode: 'NEW_IDENTIFIER_MATCHES_CURRENT_IDENTIFIER'
+}
+```
+
+### `PASSWORD_RESET_CODE_INVALID`
+
+When trying to reset a player password, this error is thrown if the reset code is incorrect or has expired.
+
+```javascript
+{
+ message: 'This code is either invalid or has expired',
+ errorCode: 'PASSWORD_RESET_CODE_INVALID'
+}
+```
+
+### `VERIFICATION_EMAIL_REQUIRED`
+
+When attempting to toggle verification on, this error is thrown if the player does not have an email address set and one is not provided in the request.
+
+```javascript
+{
+ message: 'An email address is required to enable verification',
+ errorCode: 'VERIFICATION_EMAIL_REQUIRED'
+}
+```
+
+### `EMAIL_TAKEN`
+
+When registering an auth account with email verification, enabling email verification or changing an account's email, this error will be thrown if another player is already using the chosen email address.
+
+```javascript
+{
+ message: 'This email address is already in use',
+ errorCode: 'EMAIL_TAKEN'
+}
+```
+
+### `INVALID_MIGRATION_TARGET`
+
+When starting a player auth account migration, this error is thrown if an invalid service is provided. Currently the only invalid service is "talo".
+
+```javascript
+{
+ message: 'Cannot migrate to the Talo service',
+ errorCode: 'INVALID_MIGRATION_TARGET'
+}
+```
+
+### `IDENTIFIER_PROFANITY`
+
+When registering an auth account, this error is thrown if the identifier contains profanity and the block alias identifier profanity setting is enabled.
+
+```javascript
+{
+ message: 'Alias identifier contains inappropriate language',
+ errorCode: 'IDENTIFIER_PROFANITY'
+}
+```
diff --git a/content/docs/pre-1.0/http/player-group-api.mdx b/content/docs/pre-1.0/http/player-group-api.mdx
new file mode 100644
index 0000000..316e1f3
--- /dev/null
+++ b/content/docs/pre-1.0/http/player-group-api.mdx
@@ -0,0 +1,28 @@
+---
+description: Talo groups allow you to apply filters on your player-base to create distinct segments between players. You can have groups for beta testers, players who completed your game or players with specific props.
+title: Player group API
+---
+
+import { ServiceDocumentation } from '@/components/documentation/ServiceDocumentation'
+import { generateServiceTOC } from '@/lib/api-docs'
+
+export const service = 'PlayerGroupAPI'
+
+export const pageToc = [
+ { title: 'Overview', url: '#overview', depth: 2 },
+ { title: 'Endpoints', url: '#endpoints', depth: 2 },
+ ...generateServiceTOC(service),
+]
+
+## Overview
+
+Using groups, you can segment your player-base using criteria like login times, props, stats and leaderboard entries. For example, you could have groups for beta testers, for all the players who have completed the game, or for players with the best highscores.
+
+Learn more about player groups [here](https://trytalo.com/players#groups?utm_source=docs&utm_medium=api-docs).
+
+## Endpoints
+
+
diff --git a/content/docs/pre-1.0/http/player-presence-api.mdx b/content/docs/pre-1.0/http/player-presence-api.mdx
new file mode 100644
index 0000000..93276c0
--- /dev/null
+++ b/content/docs/pre-1.0/http/player-presence-api.mdx
@@ -0,0 +1,28 @@
+---
+description: With Talo's presence API, you can track whether a player is online, allowing you to easily build social features like friends lists.
+title: Player presence API
+---
+
+import { ServiceDocumentation } from '@/components/documentation/ServiceDocumentation'
+import { generateServiceTOC } from '@/lib/api-docs'
+
+export const service = 'PlayerPresenceAPI'
+
+export const pageToc = [
+ { title: 'Overview', url: '#overview', depth: 2 },
+ { title: 'Endpoints', url: '#endpoints', depth: 2 },
+ ...generateServiceTOC(service),
+]
+
+## Overview
+
+The Player Presence API allows you to automatically track the presence of players in your game.
+
+This can be useful for a variety of multiplayer features, such as showing a player as online or offline, setting custom statuses and friends lists.
+
+## Endpoints
+
+
diff --git a/content/docs/pre-1.0/http/player-relationships-api.mdx b/content/docs/pre-1.0/http/player-relationships-api.mdx
new file mode 100644
index 0000000..f01bdda
--- /dev/null
+++ b/content/docs/pre-1.0/http/player-relationships-api.mdx
@@ -0,0 +1,26 @@
+---
+description: Player relationships link players together for social features like friends lists, parties and guilds.
+title: Player relationships API
+---
+
+import { ServiceDocumentation } from '@/components/documentation/ServiceDocumentation'
+import { generateServiceTOC } from '@/lib/api-docs'
+
+export const service = 'PlayerRelationshipsAPI'
+
+export const pageToc = [
+ { title: 'Overview', url: '#overview', depth: 2 },
+ { title: 'Endpoints', url: '#endpoints', depth: 2 },
+ ...generateServiceTOC(service),
+]
+
+## Overview
+
+Talo's Player Relationships API is the easiest way to build friends lists, follower systems, party invites and other social features that need player-to-player connections.
+
+## Endpoints
+
+
diff --git a/content/docs/pre-1.0/http/request-verification.mdx b/content/docs/pre-1.0/http/request-verification.mdx
new file mode 100644
index 0000000..36615ac
--- /dev/null
+++ b/content/docs/pre-1.0/http/request-verification.mdx
@@ -0,0 +1,106 @@
+---
+description: Talo's request verification system prevents replay attacks and tampering by validating signatures on HTTP API requests and socket messages.
+title: Request verification
+---
+
+## How it works
+
+Request verification adds an extra layer of security to your game by cryptographically validating that requests come from a legitimate client and haven't been tampered with or replayed.
+
+When enabled, Talo requires every HTTP request and socket message (sent by identified players) to include a signature. Talo validates this signature against a verification key that you manage in the dashboard.
+
+## Enabling request verification
+
+Request verification is controlled by the "verify requests" setting. This can be toggled on the [Game Settings page](https://dashboard.trytalo.com/game-settings).
+
+
+ Enabling `verifyRequests` without updating your game client will cause requests to fail with a
+ `401 Unauthorized` response.
+
+
+
+
+## Verification keys
+
+Verification keys are the secrets used to sign requests. Each key has a `version` (a string identifier like `"1"`) and a `value` (the secret used for the HMAC).
+
+You can manage verification keys in the Talo dashboard under your game's settings.
+
+## Signature format
+
+Signatures are sent in the `x-talo-signature` header for HTTP requests.
+
+The full signature is made of three parts separated by `|` and `.`:
+
+```
+{version}|{base64Header}.{base64Signature}
+```
+
+### Key version
+
+The `version` identifies which verification key was used to create the signature. It must match an active key created in the dashboard for your game.
+
+### Base64 header
+
+The `base64Header` is a Base64-encoded JSON object containing three fields:
+
+- `rid`: A unique identifier for the request. This must be different for every request you send.
+
+- `payload`: The SHA-256 hex digest of the request's JSON body. For requests with no body, hash an empty string.
+
+- `timestamp`: The current Unix timestamp in milliseconds when the request is created. Stale timestamps will be rejected.
+
+### Base64 signature
+
+The `base64Signature` can be computed like this:
+
+1. Take the JSON header object (`{ rid, payload, timestamp }`) and encode it as a Base64 string.
+2. Compute an HMAC-SHA256 of that Base64 string using your verification key's `value` as the secret.
+3. Base64-encode the HMAC output.
+
+This final Base64-encoded HMAC is the signature portion of the full value.
+
+## Generating a signature
+
+This TypeScript utility function shows how to generate a valid signature:
+
+```typescript
+import crypto from 'node:crypto'
+
+function buildSignature(body: string, key: string, version: string) {
+ // Unique request identifier
+ const rid = crypto.randomUUID()
+
+ // Current timestamp in milliseconds
+ const timestamp = Date.now()
+
+ // SHA-256 hex digest of the raw request body
+ const payload = crypto.createHash('sha256').update(body).digest('hex')
+
+ const header = { rid, payload, timestamp }
+ const headerB64 = Buffer.from(JSON.stringify(header)).toString('base64')
+
+ // HMAC-SHA256 of the base64 header using the verification key value
+ const hmac = crypto.createHmac('sha256', key).update(headerB64).digest()
+ const signatureB64 = Buffer.from(hmac).toString('base64')
+
+ return `${version}|${headerB64}.${signatureB64}`
+}
+```
+
+You can then send the signature in the `x-talo-signature` header alongside your request.
+
+## Socket messages
+
+Enabling request verification also enables verification for socket messages.
+
+See the [socket request verification docs](/docs/pre-1.0/sockets/request-verification) for details on how to prepend the signature to the message payload.
+
+## When verification applies
+
+Request verification is only enforced when:
+
+- `verifyRequests` is enabled for the game.
+- The request is sent by an identified player (i.e. when the `x-talo-alias` header is also present).
+
+Requests without a player alias do not require a signature, even when `verifyRequests` is enabled.
diff --git a/content/docs/pre-1.0/http/socket-ticket-api.mdx b/content/docs/pre-1.0/http/socket-ticket-api.mdx
new file mode 100644
index 0000000..5922a96
--- /dev/null
+++ b/content/docs/pre-1.0/http/socket-ticket-api.mdx
@@ -0,0 +1,28 @@
+---
+description: Connect to Talo's socket to effortlessly send messages between players.
+title: Socket ticket API
+---
+
+import { ServiceDocumentation } from '@/components/documentation/ServiceDocumentation'
+import { generateServiceTOC } from '@/lib/api-docs'
+
+export const service = 'SocketTicketAPI'
+
+export const pageToc = [
+ { title: 'Overview', url: '#overview', depth: 2 },
+ { title: 'Endpoints', url: '#endpoints', depth: 2 },
+ ...generateServiceTOC(service),
+]
+
+## Overview
+
+Talo provides a secure socket system for real-time communication between your game and players. The Talo Socket powers services like [channels](https://trytalo.com/channels?utm_source=socket-docs&utm_medium=api-docs) and [player relationships](https://trytalo.com/player-relationships?utm_source=socket-docs&utm_medium=api-docs).
+
+To connect to the socket server, you need to obtain a socket ticket. Learn more about socket authentication [here](/docs/pre-1.0/sockets/intro#authentication).
+
+## Endpoints
+
+
diff --git a/content/docs/pre-1.0/index.mdx b/content/docs/pre-1.0/index.mdx
new file mode 100644
index 0000000..cc11ceb
--- /dev/null
+++ b/content/docs/pre-1.0/index.mdx
@@ -0,0 +1,33 @@
+---
+description: Talo is an open source game backend that makes it easy to add leaderboards, saves, analytics and more to your Godot or Unity game.
+title: Talo overview
+---
+## Pre-1.0
+
+This is the archived documentation for Talo releases before the 1.0 update (Godot plugin 0.49.0 and Unity package 0.60.0). If you're migrating to the latest version, see the [Godot upgrade guide](/docs/1.x/godot/upgrading-to-1.0) or [Unity upgrade guide](/docs/1.x/unity/upgrading-to-1.0).
+
+## Choose your adventure
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+## Everything you need in one place
+
+- **Gameplay** - Talo can manage leaderboards, save files, live config, player feedback (and more) for you.
+- **Game analytics** - track and analyse player behaviour to see what players are engaging with.
+- **Multiplayer** - create multiplayer interactions and subscribe to real-time updates using websockets.
+- **Players and data** - easily create, authenticate and manage player data in your game.
+
diff --git a/content/docs/pre-1.0/integrations/apple-game-center.mdx b/content/docs/pre-1.0/integrations/apple-game-center.mdx
new file mode 100644
index 0000000..abd4911
--- /dev/null
+++ b/content/docs/pre-1.0/integrations/apple-game-center.mdx
@@ -0,0 +1,20 @@
+---
+description: Use Talo's Apple Game Center integration to automatically identify players signed in to Game Center.
+title: Apple Game Center
+---
+
+## How it works
+
+Using your app's Bundle ID, you can automatically identify players signed in to Apple Game Center.
+
+You can enable this integration on the [integrations page](https://dashboard.trytalo.com/integrations).
+
+
+
+## Authentication
+
+To get started, enter your app's Bundle ID into the dashboard. Talo will use this to verify the identity verification signature provided by Game Center.
+
+To identify a player, fetch the local player's identity verification signature from Game Center and pass the values to Talo. Talo will cryptographically verify the signature against Apple's public key and automatically identify the player without them needing to create an account.
+
+Check out the [Godot plugin docs](/docs/pre-1.0/godot/identifying#apple-game-center-integration) or [Unity package docs](/docs/pre-1.0/unity/identifying#apple-game-center-integration) for more details.
diff --git a/content/docs/pre-1.0/integrations/google-play-games.mdx b/content/docs/pre-1.0/integrations/google-play-games.mdx
new file mode 100644
index 0000000..4a43ba8
--- /dev/null
+++ b/content/docs/pre-1.0/integrations/google-play-games.mdx
@@ -0,0 +1,22 @@
+---
+description: Use Talo's Google Play Games integration to automatically identify players signed in to Google.
+title: Google Play Games
+---
+
+## How it works
+
+Using [Game Server credentials](https://developer.android.com/games/pgs/console/setup#generate_an_oauth_20_client_id), you can automatically identify players signed in to Google Play Games.
+
+You can enable this integration on the [integrations page](https://dashboard.trytalo.com/integrations).
+
+
+
+## Authentication
+
+To get started, [create an OAuth client](https://developer.android.com/games/pgs/console/setup#generate_an_oauth_20_client_id) (ensure you create credentials for a **Game server**) and copy the details into the dashboard.
+
+To identify a player, [request a server auth code](https://developer.android.com/games/pgs/android/server-access) and pass the code to Talo. Talo will automatically sync the player without them needing to create an account.
+
+Talo will also add helpful `props` to your player such as their display name and avatar URL.
+
+Check out the [Godot plugin docs](/docs/pre-1.0/godot/identifying#google-play-games-integration) or [Unity package docs](/docs/pre-1.0/unity/identifying#google-play-games-integration) for more details.
diff --git a/content/docs/pre-1.0/integrations/meta.json b/content/docs/pre-1.0/integrations/meta.json
new file mode 100644
index 0000000..6efce30
--- /dev/null
+++ b/content/docs/pre-1.0/integrations/meta.json
@@ -0,0 +1,9 @@
+{
+ "pages": [
+ "steamworks",
+ "google-play-games",
+ "apple-game-center"
+ ],
+ "title": "Integrations",
+ "icon": "integrations"
+}
diff --git a/content/docs/pre-1.0/integrations/steamworks.mdx b/content/docs/pre-1.0/integrations/steamworks.mdx
new file mode 100644
index 0000000..7f691dc
--- /dev/null
+++ b/content/docs/pre-1.0/integrations/steamworks.mdx
@@ -0,0 +1,94 @@
+---
+description: Talo's Steamworks integration can sync your players, stats and leaderboards. You can authenticate players and verify their ownership of your game.
+title: Steamworks
+---
+
+## How it works
+
+By using your [Web API Publisher key](https://partner.steamgames.com/doc/webapi_overview/auth), you can sync data between Talo and Steamworks.
+
+You can enable this integration on the [integrations page](https://dashboard.trytalo.com/integrations).
+
+
+
+## Authentication
+
+By setting up the Talo Steamworks integration, you are able to authenticate and identify players using the Steam User Auth API.
+
+You'll need to pass the ticket generated by the [GetAuthTicketForWebApi](https://partner.steamgames.com/doc/api/ISteamUser#GetAuthTicketForWebApi) API when identifying players. Using this, Talo will automatically verify the session and sync the player.
+
+Talo will also add helpful `props` to your player such as app ownership details, profile names and avatars.
+
+Check out the [Godot plugin docs](/docs/pre-1.0/godot/identifying#steamworks-integration) or [Unity package docs](/docs/pre-1.0/unity/identifying#steamworks-integration) for more details.
+
+## Leaderboards
+
+Talo syncs leaderboards by mapping between the ID of the leaderboard in Talo and in Steamworks. If a mapping can't be found, we will fall back to mapping between the Talo `Internal name` and the Steamworks `Name` (not the `Community name`).
+
+To ensure syncing works correctly, avoid manually updating the `Name` inside Steamworks.
+
+### Creating leaderboards
+
+When you create a leaderboard in Talo, we'll automatically create one in Steamworks too.
+
+There are some limitations:
+
+- The Steamworks API doesn't allow us to push through display names, you will need to set a `Community Name` inside Steamworks to make the leaderboard "public"
+- The `Unique` property will be ignored since Steamworks leaderboards only allow unique entries
+- The Steamworks leaderboard will only allow trusted writes (i.e. from a server, not a client)
+
+### Updating leaderboards
+
+The Steamworks API does not have a way to update leaderboards. However, if the leaderboard does not exist in Steamworks, we will create it.
+
+### Deleting leaderboards
+
+When you delete a leaderboard in Talo, we'll also delete it in Steamworks.
+
+### Setting scores
+
+When a player submits a score, we'll also push that score through to your Steamworks leaderboard. It will use the `KeepBest` method since as mentioned above, Steamworks leaderboards are always in unique mode.
+
+### Toggling score visibility
+
+If you hide an entry, we will delete it from the Steamworks leaderboard. Unhidden entries will be re-created.
+
+### Resetting leaderboards
+
+If you reset a leaderboard, Talo will delete all entries in Steamworks. This is an asychronous background process which can may up to an hour to complete.
+
+### Manually syncing leaderboards
+
+Here's how it works:
+
+1. We'll pull in your leaderboards from Steamworks
+2. If a leaderboard exists in Talo and Steamworks ([by checking the mapping](#leaderboards)), we will update the `Sort mode`, `Name` and `Internal name` in Talo as well as set `Unique` to `true` (as all Steamworks leaderboards only allow unique entries)
+3. If the leaderboard does not exist in Steamworks, we will [create it](#creating-leaderboards)
+4. If the leaderboard does not exist in Talo, we will create a unique-mode leaderboard
+5. For each of your players with a Steam alias, we will create or update entries from each leaderboard
+6. If a leaderboard entry only exists in Talo, we will create it for your Steamworks leaderboard
+
+## Stats
+
+Talo syncs stats by mapping between the `Internal name` in Talo and the `API name` in Steamworks. To ensure syncing works correctly, you may want to avoid manually updating the `API name` inside Steamworks.
+
+### Setting stats
+
+When a stat changes for a player, we'll update it in Steamworks.
+
+### Resetting stats
+
+Steamworks does not currently have a public API to delete user stats. If you reset a stat, Talo will update all the Steamworks user stats to be the default stat value.
+
+This is an asychronous background process which can may up to an hour to complete.
+
+### Manually syncing stats
+
+Here's how it works:
+
+1. We'll pull in your stats from Steamworks - each `Set by` needs to be `GS` in order for us to find the stat
+2. If a stat exists in Talo and Steamworks ([by checking the mapping](#stats)), we will update the `Default value` and `Name` in Talo
+3. If the stat does not exist in Talo, we will create a non-global stat with a matching `Internal name` and `Name` to the Steamworks one
+4. If the stat does not exist in Steamworks, we cannot create one, you will need to do this manually
+5. For each of your players with a Steam alias, we will update their stats with the ones from Steamworks
+6. If a player stat only exists in Talo, we will push it through to Steamworks
diff --git a/content/docs/pre-1.0/meta.json b/content/docs/pre-1.0/meta.json
new file mode 100644
index 0000000..730473e
--- /dev/null
+++ b/content/docs/pre-1.0/meta.json
@@ -0,0 +1,5 @@
+{
+ "pages": ["index", "godot", "unity", "http", "sockets", "selfhosting", "integrations"],
+ "title": "Pre-1.0",
+ "root": true
+}
\ No newline at end of file
diff --git a/content/docs/pre-1.0/selfhosting/configuration.mdx b/content/docs/pre-1.0/selfhosting/configuration.mdx
new file mode 100644
index 0000000..d8aee85
--- /dev/null
+++ b/content/docs/pre-1.0/selfhosting/configuration.mdx
@@ -0,0 +1,109 @@
+---
+description: Learn how to configure your self-hosted Talo.
+title: Configuration
+---
+
+## Backend environment variables
+
+Below is a list of configuration options that can be found in your .env file:
+
+
+ You'll need to restart the backend container after modifying your .env file
+
+
+| Environment variable | Required | Description |
+| :----------------------------------- | :------- | :---------------------------------------------------------------------------------------------------------------------------- |
+| JWT_SECRET | ✅ Yes | Your unique secret that signs all authenticated requests. Treat this as securely as you would a database password |
+| API_SECRET | ✅ Yes | 32-character secret for encrypting API keys |
+| DASHBOARD_URL | ✅ Yes | The URL of your frontend. Used for setting CORS headers and in emails |
+| DB_HOST | ✅ Yes | The host name for your database. If using containers, this is typically the name of your database service |
+| DB_PORT | ✅ Yes | The port your database runs on, usually 3306 |
+| DB_NAME | ✅ Yes | The name of your database |
+| DB_USER | ✅ Yes | The user which has access to your database |
+| DB_PASS | ✅ Yes | The password for the database user |
+| STORAGE_DRIVER | No | The driver used for file storage. Set to `s3` for S3-compatible storage (AWS, Cloudflare R2, DigitalOcean Spaces) |
+| S3_REGION | No | The region for your S3 bucket (e.g., `us-east-1` or `auto`) |
+| S3_ENDPOINT | No | The full endpoint URL for your storage provider (e.g., `https://s3.amazonaws.com`) |
+| S3_ACCESS_KEY_ID | No | The access key ID for your S3 provider |
+| S3_SECRET_ACCESS_KEY | No | The secret access key for your S3 provider |
+| S3_BUCKET | No | The name of the bucket where exports and files will be stored |
+| EMAIL_DRIVER | No | This can be set to 'log' (default, prints to the console) or 'relay' (send via an external SMTP server) |
+| EMAIL_HOST | No | The host for the 'relay' server, e.g. smtp.mailserver.com |
+| EMAIL_PORT | No | The port for the 'relay' server, usually 587 or 465 |
+| EMAIL_USERNAME | No | The username for the 'relay' server, e.g. example@mailserver.com |
+| EMAIL_PASSWORD | No | The password for the 'relay' server |
+| EMAIL_DEBUG | No | Set this environment variable to print debug info about emails to the console |
+| FROM_EMAIL | No | The email address used to send emails from the backend |
+| AUTO_CONFIRM_EMAIL | No | A boolean that defines whether users have their emails automatically confirmed after signing up |
+| CLICKHOUSE_HOST | ✅ Yes | The host name for your ClickHouse instance. If using containers, this is typically the name of your ClickHouse service |
+| CLICKHOUSE_PORT | No | The port your ClickHouse instance runs on, usually 8123 |
+| CLICKHOUSE_DB | ✅ Yes | The name of your ClickHouse database |
+| CLICKHOUSE_USER | ✅ Yes | The user which has access to your ClickHouse database |
+| CLICKHOUSE_PASSWORD | ✅ Yes | The password for the ClickHouse database user |
+| REDIS_PASSWORD | ✅ Yes | The password for your Redis instance |
+| RECOVERY_CODES_SECRET | No | 32-character secret for encrypting your 2FA recovery codes, required to use 2FA |
+| STEAM_INTEGRATION_SECRET | No | 32-character secret for encrypting your Steam API key, required to use the Steamworks integration |
+| GOOGLE_PLAY_GAMES_INTEGRATION_SECRET | No | 32-character secret for encrypting your Google OAuth client secret, required to use the Google Play Games integration |
+| REGISTRATION_MODE | No | Controls how registrations work: 'open' (default, open to anyone), 'exclusive' (invite-only) or 'disabled' |
+| HYPERDX_API_KEY | No | Your HyperDX API key - used to enable the integration |
+| HDX_NODE_BETA_MODE | No | Set to '1' to enable HyperDX trace attributes |
+| OTEL_EXPORTER_OTLP_ENDPOINT | No | The open telemetry collector endpoint where traces will be sent e.g. [https://in-otel.hyperdx.io](https://in-otel.hyperdx.io) |
+| NO_PROXY | No | Set to '1' to _not_ use the `X-Forwarded-For` header for player auth activity IP addresses |
+| API_RATE_LIMIT | No | Set the default requests per second (default 100) |
+| API_RATE_LIMIT_AUTH | No | Set the requests per second for player auth, identification and socket auth endpoints (default 20) |
+
+## Third party configurations
+
+Talo uses a handful of third party services to handle common tasks.
+
+### Emails
+
+Talo can relay emails via an external SMTP server (like Gmail or Zoho) using the "relay" `EMAIL_DRIVER`. Under the hood, Talo uses [Nodemailer](https://nodemailer.com) to handle the relay and you can refer to their docs for example configurations.
+
+When the `EMAIL_DRIVER` is set to `log`, the email file and attachments are saved to the `storage/mail` directory.
+
+### Sentry
+
+[Sentry](https://sentry.io) is an error monitoring tool with a generous free tier. You can enable Sentry by setting the `SENTRY_DSN` environment variable to your unique DSN.
+
+### HyperDX
+
+[HyperDX](https://hyperdx.io) is an observability platform with cloud-hosted and self-hosted options. It uses opentelemetry to provide oversight over traces and logs.
+
+All you have to do is provide Talo with the `HYPERDX_API_KEY` and `OTEL_EXPORTER_OTLP_ENDPOINT` environment variables. We also recommend a few other environment variables:
+
+```
+HYPERDX_API_KEY=[your key]
+OTEL_EXPORTER_OTLP_ENDPOINT=[your opentelemetry collector endpoint]
+HDX_NODE_BETA_MODE=1 # enables trace attributes
+OTEL_NODE_DISABLED_INSTRUMENTATIONS=koa # this instrumentation isn't very helpful
+```
+
+### Object storage
+
+Talo supports object storage for data exports. For AWS S3, your environment variables would look like this:
+
+```
+STORAGE_DRIVER=s3
+S3_REGION=us-east-1
+S3_ENDPOINT=https://s3.amazonaws.com
+S3_ACCESS_KEY_ID=AK...
+S3_SECRET_ACCESS_KEY=ABC...
+S3_BUCKET=talo-exports
+```
+
+Talo uses [`s3mini`](https://github.com/good-lly/s3mini) which also works with other object storage providers like Cloudflare R2 and Hetzner Object Storage. This is how Talo maps environment variables:
+
+```typescript
+// if S3_ENDPOINT isn't set, use the default S3 URL
+const endpoint = process.env.S3_ENDPOINT ?? 'https://s3.amazonaws.com'
+const bucket = process.env.S3_BUCKET
+const endpointWithBucket = endpoint + bucket
+
+return new S3mini({
+ accessKeyId: process.env.S3_ACCESS_KEY_ID!,
+ secretAccessKey: process.env.S3_SECRET_ACCESS_KEY!,
+ endpoint: endpointWithBucket,
+ region: process.env.S3_REGION,
+})
+```
diff --git a/content/docs/pre-1.0/selfhosting/meta.json b/content/docs/pre-1.0/selfhosting/meta.json
new file mode 100644
index 0000000..8c3dbdc
--- /dev/null
+++ b/content/docs/pre-1.0/selfhosting/meta.json
@@ -0,0 +1,8 @@
+{
+ "pages": [
+ "overview",
+ "configuration"
+ ],
+ "title": "Self-hosting",
+ "icon": "selfhosting"
+}
diff --git a/content/docs/pre-1.0/selfhosting/overview.mdx b/content/docs/pre-1.0/selfhosting/overview.mdx
new file mode 100644
index 0000000..afdf546
--- /dev/null
+++ b/content/docs/pre-1.0/selfhosting/overview.mdx
@@ -0,0 +1,28 @@
+---
+description: Talo is open-source and we publish our backend and frontend Docker images publicly so you can host Talo on your own servers and make any of the modifications you want to.
+title: Self-hosting quickstart
+---
+
+## Clone the repo
+
+The easiest way to get started is using Docker and our [self-hosting repository](https://github.com/TaloDev/hosting).
+
+### Examples
+
+The repository comes with two Docker Compose examples:
+
+1. `basic` - this example exposes the backend and frontend as simple HTTP servers for quickly getting up and running (accessible via http://localhost or http://[your server's IP address]).
+
+2. `nginx` - this example uses nginx as a web server, enables HTTPS and automatically configures domain names for your backend and frontend (e.g. https://dashboard.example.com and https://api.example.com) using reverse proxies. We've used [docker-nginx-certbot](https://github.com/JonasAlfredsson/docker-nginx-certbot) for automating and enabling SSL so make sure to have a look at the image's documentation. You should add an A record pointing to your server for each domain configured before starting the containers.
+
+3. `caddy` (recommended) - this example uses [caddy](https://caddyserver.com) as a web server, enables HTTPS and automatically configures domain names for your backend and frontend (e.g. https://dashboard.example.com and https://api.example.com) using reverse proxies. You should add an A record pointing to your server for each domain configured before starting the containers.
+
+
+ Check out this blog post on [the caddy
+ template](https://trytalo.com/blog/caddy-selfhosting?utm_source=docs&utm_medium=tip) for more
+ details
+
+
+## Contributing
+
+You can submit a pull request to our [backend repository](https://github.com/TaloDev/backend) or [frontend repository](https://github.com/TaloDev/frontend) if you feel like sharing any of your improvements!
diff --git a/content/docs/pre-1.0/sockets/common-errors.mdx b/content/docs/pre-1.0/sockets/common-errors.mdx
new file mode 100644
index 0000000..7a7af06
--- /dev/null
+++ b/content/docs/pre-1.0/sockets/common-errors.mdx
@@ -0,0 +1,147 @@
+---
+title: Common errors
+---
+
+## Missing authentication
+
+A valid socket ticket is required when opening a socket connection. Learn more about how to obtain a socket ticket and connect to the socket server [here](/docs/pre-1.0/intro#connecting-to-the-socket-server).
+
+Connections without a valid ticket will be closed immediately.
+
+## Invalid message
+
+```javascript
+{
+ "res": "v1.error",
+ "data": {
+ "req": "unknown",
+ "message": "Invalid message request",
+ "errorCode": "INVALID_MESSAGE",
+ "cause": "{\"req\":\"v1.does.not.exist\",\"data\":{}}"
+ }
+}
+```
+
+If you send a `req` that does not exist, you will receive this error. The `req` in the error object is "unknown" because the schema does not match. Check the "cause" for what the failing request looked like.
+
+## Invalid message data
+
+```javascript
+{
+ "res": "v1.error",
+ "data": {
+ "req": "v1.channels.message",
+ "message": "Invalid message data",
+ "errorCode": "INVALID_MESSAGE_DATA",
+ "cause": "{\"myMessageToTheChannelIsGoingToBeThis\":\"general\"}"
+ }
+}
+```
+
+If the request is valid but the payload data is not, you will receive this message. Check the "cause" to see what you sent and match it up against the schema in the docs.
+
+## No player found
+
+```javascript
+{
+ "res": "v1.error",
+ "data": {
+ "req": "v1.channels.message",
+ "message": "You must identify a player before sending this request",
+ "errorCode": "NO_PLAYER_FOUND"
+ }
+}
+```
+
+Most (but not all) requests require the player to be identified before being processed.
+
+## Invalid socket token
+
+```javascript
+{
+ "res": "v1.error",
+ "data": {
+ "req": "v1.players.identify",
+ "message": "Invalid socket token",
+ "errorCode": "INVALID_SOCKET_TOKEN"
+ }
+}
+```
+
+When a player is identified using the HTTP API, they receive a `socketToken` to securely connect to the Talo Socket. Socket tokens expire after an hour and are replaced every time the player is identified by the HTTP API.
+
+## Invalid session token
+
+```javascript
+{
+ "res": "v1.error",
+ "data": {
+ "req": "v1.players.identify",
+ "message": "Invalid session token",
+ "errorCode": "INVALID_SESSION_TOKEN"
+ }
+}
+```
+
+If you are using [Talo Player Authentication](https://trytalo.com/players#authentication) for the alias being identified, you will also need to send the `sessionToken` that you receive from the HTTP API after identifying the player. If this is missing, invalid or expired, you will not be able to identify the player.
+
+## Missing API key scopes
+
+```javascript
+{
+ "res": "v1.error",
+ "data": {
+ "req": "v1.channels.message",
+ "message": "Missing API key scope(s): write:gameChannels",
+ "errorCode": "MISSING_API_KEY_SCOPES"
+ }
+}
+```
+
+Most requests will require your API key to have specific scopes. You will receive this error if they are missing.
+
+## Listener error
+
+```javascript
+{
+ "res": "v1.error",
+ "data": {
+ "req": "v1.channels.message",
+ "message": "An error occurred while processing the message",
+ "errorCode": "LISTENER_ERROR",
+ "cause": "Player not in channel"
+ }
+}
+```
+
+A request listener can throw an error specific to its own internal logic. These are captured under the generic `LISTENER_ERROR` and the actual error will be highlighted in the "cause".
+
+## Invalid signature
+
+```javascript
+{
+ "res": "v1.error",
+ "data": {
+ "req": "v1.channels.message",
+ "message": "Invalid signature",
+ "errorCode": "INVALID_SIGNATURE"
+ }
+}
+```
+
+If your game has [request verification](/docs/pre-1.0/sockets/request-verification) enabled, all socket messages sent after identifying a player must include a valid signature. If the signature is missing, malformed or fails Talo's validation, you will receive this error.
+
+## Rate limit exceeded
+
+```javascript
+{
+ "res": "v1.error",
+ "data": {
+ "req": "unknown",
+ "message": "Rate limit exceeded",
+ "errorCode": "RATE_LIMIT_EXCEEDED"
+ }
+}
+```
+
+Unidentified players are limited to 1 request per second and identified players are limited to 60 requests per second. If this is exceeded, you will receive a rate limit error. After 3 warnings, the connection will be closed and you will need to re-establish a connection to the socket server.
diff --git a/content/docs/pre-1.0/sockets/custom-ping-pongs.mdx b/content/docs/pre-1.0/sockets/custom-ping-pongs.mdx
new file mode 100644
index 0000000..cd599d8
--- /dev/null
+++ b/content/docs/pre-1.0/sockets/custom-ping-pongs.mdx
@@ -0,0 +1,64 @@
+---
+description: If your clientside websocket framework doesn't support ping-pong control frames, you can use Talo's custom heartbeat message to keep connections alive.
+title: Custom ping-pongs
+---
+
+## Browser support
+
+
+ The browser's native WebSocket API doesn't expose access to ping/pong control frames - browsers
+ handle them automatically under the hood. If you're using the browser WebSocket API directly,
+ you'll need to use this custom heartbeat mechanism.
+
+
+## Heartbeat message
+
+To ensure your socket connections don't fail their heartbeat checks, send the `v1.heartbeat` string every 30 seconds.
+
+Unlike other Talo socket messages, this should be a plain string and **not** JSON.
+
+### How do Talo heartbeats work?
+
+Talo will ping socket connections every 30 seconds. After sending a ping, Talo expects the connection to respond with a pong. If a pong isn't received after 30 seconds, the connection will be terminated.
+
+The process above uses control frames. If you send the `v1.heartbeat` message, Talo will instantly respond with the same message and mark the connection as "still alive".
+
+The client doesn't need to do anything with the message - this process is simply designed to keep the server informed of healthy open connections.
+
+## Example usage
+
+Here's a complete example using the browser's WebSocket API:
+
+```typescript
+const socket = new WebSocket('wss://api.trytalo.com')
+
+// send heartbeat every 25 seconds (before the 30-second timeout)
+const HEARTBEAT_INTERVAL = 25_000
+let heartbeatTimer: number
+
+socket.addEventListener('open', () => {
+ console.log('Connected to Talo')
+
+ // start sending heartbeats
+ heartbeatTimer = setInterval(() => {
+ if (socket.readyState === WebSocket.OPEN) {
+ socket.send('v1.heartbeat')
+ }
+ }, HEARTBEAT_INTERVAL)
+})
+
+socket.addEventListener('close', () => {
+ clearInterval(heartbeatTimer)
+})
+
+socket.addEventListener('message', (event) => {
+ // ignore heartbeat responses
+ if (event.data === 'v1.heartbeat') {
+ return
+ }
+
+ // handle your normal Talo messages here
+ const message = JSON.parse(event.data)
+ console.log(message.res, message.data)
+})
+```
diff --git a/content/docs/pre-1.0/sockets/intro.mdx b/content/docs/pre-1.0/sockets/intro.mdx
new file mode 100644
index 0000000..cea05a0
--- /dev/null
+++ b/content/docs/pre-1.0/sockets/intro.mdx
@@ -0,0 +1,106 @@
+---
+description: The Talo Socket makes it easy to add interactivity to your game. You can send messages between players and make your game client to be reactive to changes.
+title: Socket basics
+---
+
+## Message structure
+
+The socket used by Talo follows the web socket standard and is compatible with libraries that also do the same.
+
+One major difference is that the Talo Socket will validate messages against a request type and payload structure. For example, if you send a message with the `v1.players.identified` request, your payload's data must also match the expected schema. The list of available [requests](/docs/pre-1.0/requests) and [responses](/docs/pre-1.0/responses) are available on their respective pages.
+
+Using the identification example, here is the JSON expected by the Talo Socket when identifying a player:
+
+```javascript
+{
+ "req": "v1.players.identified",
+ "data": {
+ "playerAliasId": 105,
+ "socketToken": "9aae3c26-98d0-4a13-9dd7-b187aa36f6cd"
+ }
+}
+```
+
+The request type is defined by the `req` key and the payload of the request is defined using the `data` key.
+
+Responses use a similar message structure, except that `req` is replaced by `res` to denote a response:
+
+```javascript
+{
+ "res": "v1.players.identified.success",
+ "data": {
+ "id": 105,
+ "service": "username",
+ "identifier": "billy",
+ "player": {
+ "id": "85d67584-1346-4fad-a17f-fd7bd6c85364",
+ "props": [],
+ "devBuild": false,
+ "createdAt": "2024-10-25T18:18:28.000Z",
+ "lastSeenAt": "2024-12-04T07:15:13.000Z",
+ "groups": []
+ },
+ "lastSeenAt": "2024-12-04T07:15:13.000Z",
+ "createdAt": "2024-10-25T18:18:28.000Z",
+ "updatedAt": "2024-12-04T07:15:13.000Z"
+ }
+}
+```
+
+## Authentication
+
+The Talo Socket provides two layers of authentication:
+
+1. You must provide a [socket ticket](/docs/pre-1.0/http/socket-ticket-api) when opening a socket connection. This identifies your game and the key's scopes are used to determine whether a client is eligible to receive specific responses.
+2. When players are identified, a `socketToken` is stored against the player alias for 1 hour. While the socket token is valid, you can use it to authenticate your player with the socket.
+
+
+ Not all requests and responses will need a player to be identified. However, your API key will
+ need the `read:players` scope to identify players.
+
+
+
+ Unlike the HTTP API, players cannot be created during identification. To successfully identify a
+ player, their alias must already exist in Talo.
+
+
+## Connecting to the socket server
+
+The default socket URL is `wss://api.trytalo.com`. When opening a socket connection, you must provide a socket ticket as a query parameter. The full URL should look like this:
+
+```
+wss://api.trytalo.com/socket?ticket=your-ticket-here
+```
+
+Your ticket should be a valid socket ticket, i.e. it was created within the last 5 minutes. Visit the [API docs](/docs/pre-1.0/http/socket-ticket-api) for more information on how to obtain a socket ticket.
+
+## Error responses
+
+Due to the nature of sockets, error responses are completely asynchronous. Error responses follow a standard format so you can match them up against your requests. For example:
+
+```javascript
+{
+ "res": "v1.error",
+ "data": {
+ "req": "v1.players.identify",
+ "message": "Invalid socket token",
+ "errorCode": "INVALID_SOCKET_TOKEN"
+ }
+}
+```
+
+Some errors may also return a "cause" key which drills down into why a request failed:
+
+```javascript
+{
+ "res": "v1.error",
+ "data": {
+ "req": "v1.channels.message",
+ "message": "An error occurred while processing the message",
+ "errorCode": "LISTENER_ERROR",
+ "cause": "Player not in channel"
+ }
+}
+```
+
+For a more detailed look into the available error codes, visit the [common errors](/docs/pre-1.0/common-errors) page.
diff --git a/content/docs/pre-1.0/sockets/meta.json b/content/docs/pre-1.0/sockets/meta.json
new file mode 100644
index 0000000..fc7b36c
--- /dev/null
+++ b/content/docs/pre-1.0/sockets/meta.json
@@ -0,0 +1,12 @@
+{
+ "pages": [
+ "intro",
+ "common-errors",
+ "requests",
+ "responses",
+ "custom-ping-pongs",
+ "request-verification"
+ ],
+ "title": "Socket reference",
+ "icon": "sockets"
+}
diff --git a/content/docs/pre-1.0/sockets/request-verification.mdx b/content/docs/pre-1.0/sockets/request-verification.mdx
new file mode 100644
index 0000000..1d5a1c0
--- /dev/null
+++ b/content/docs/pre-1.0/sockets/request-verification.mdx
@@ -0,0 +1,111 @@
+---
+description: Socket message verification uses cryptographic signatures to prevent replay attacks and tampering on WebSocket messages.
+title: Request verification
+---
+
+## How it works
+
+Request verification adds an extra layer of security to your game by cryptographically validating that socket messages come from a legitimate player and haven't been tampered with or replayed.
+
+When enabled, Talo requires every socket message sent by identified players to include a signature. The server validates this signature against a verification key that you manage in the dashboard.
+
+The signature is prepended to the JSON message payload, separated by a newline character (`\n`):
+
+```
+{version}|{base64Header}.{base64Signature}
+{"req":"v1.channels.message","data":{"channel":{"id":1},"message":"Hello"}}
+```
+
+Talo extracts the signature from the first line and validates the JSON payload on the second line.
+
+## Signature format
+
+The signature format is identical to the `x-talo-signature` header:
+
+```
+{version}|{base64Header}.{base64Signature}
+```
+
+### Key version
+
+The `version` identifies which verification key was used to create the signature. It must match an active key created in the dashboard for your game.
+
+### Base64 header
+
+The `base64Header` is a Base64-encoded JSON object containing three fields:
+
+- `rid`: A unique identifier for the message. This must be different for every message you send.
+- `payload`: The SHA-256 hex digest of the raw JSON message string (the second line of the payload).
+- `timestamp`: The current Unix timestamp in milliseconds when the message is created. Stale timestamps will be rejected.
+
+### Base64 signature
+
+The `base64Signature` is computed in three steps:
+
+1. Take the JSON header object (`{ rid, payload, timestamp }`) and encode it as a Base64 string.
+2. Compute an HMAC-SHA256 of that Base64 string using your verification key's `value` as the secret.
+3. Base64-encode the HMAC output.
+
+This final Base64-encoded HMAC is the signature portion of the full value.
+
+## Generating a signature
+
+This TypeScript utility function shows how to generate a valid signature:
+
+```typescript
+import crypto from 'node:crypto'
+
+function buildSignature(body: string, key: string, version: string) {
+ // Unique request identifier
+ const rid = crypto.randomUUID()
+
+ // Current timestamp in milliseconds
+ const timestamp = Date.now()
+
+ // SHA-256 hex digest of the raw JSON message
+ const payload = crypto.createHash('sha256').update(body).digest('hex')
+
+ const header = { rid, payload, timestamp }
+ const headerB64 = Buffer.from(JSON.stringify(header)).toString('base64')
+
+ // HMAC-SHA256 of the base64 header using the verification key value
+ const hmac = crypto.createHmac('sha256', key).update(headerB64).digest()
+ const signatureB64 = Buffer.from(hmac).toString('base64')
+
+ return `${version}|${headerB64}.${signatureB64}`
+}
+```
+
+You can then prepend the signature to the JSON message separated by a newline character (`\n`):
+
+```typescript
+const message = JSON.stringify({
+ req: 'v1.channels.message',
+ data: { channel: { id: 1 }, message: 'Hello' },
+})
+const signature = buildSignature(message, key, version)
+
+socket.send(`${signature}\n${message}`)
+```
+
+## When verification applies
+
+Socket message verification is only enforced when:
+
+- `verifyRequests` is enabled for the game.
+- The player has been identified, i.e. the `v1.players.identified.success` message has been received.
+
+## Invalid signature error
+
+If a signature is missing or invalid, the server responds with the following error:
+
+```javascript
+{
+ "res": "v1.error",
+ "data": {
+ "req": "v1.channels.message",
+ "message": "Invalid signature",
+ "errorCode": "INVALID_SIGNATURE"
+ }
+}
+```
diff --git a/content/docs/pre-1.0/sockets/requests.mdx b/content/docs/pre-1.0/sockets/requests.mdx
new file mode 100644
index 0000000..8ef7dcb
--- /dev/null
+++ b/content/docs/pre-1.0/sockets/requests.mdx
@@ -0,0 +1,47 @@
+---
+description: Requests are socket messages you send to Talo and other players
+title: Requests
+---
+
+## Players
+
+### Identifying a player
+
+Most (but not all) requests require the player to be identified before being processed.
+
+```typescript
+{
+ "req": "v1.players.identify",
+ "data": {
+ "playerAliasId": number
+ "socketToken": string
+ "sessionToken": string | undefined
+ }
+}
+```
+
+| Authentication required | Scopes |
+| ----------------------- | -------------- |
+| No | `read:players` |
+
+## Channels
+
+### Sending a message
+
+Players must be subscribed to the channel they are trying to send a message to and the channel must exist, otherwise a `LISTENER_ERROR` will be thrown.
+
+```typescript
+{
+ "req": "v1.channels.message",
+ "data": {
+ "channel": {
+ "id": number
+ }
+ "message": string
+ }
+}
+```
+
+| Authentication required | Scopes |
+| ----------------------- | -------------------- |
+| Yes | `write:gameChannels` |
diff --git a/content/docs/pre-1.0/sockets/responses.mdx b/content/docs/pre-1.0/sockets/responses.mdx
new file mode 100644
index 0000000..14c9531
--- /dev/null
+++ b/content/docs/pre-1.0/sockets/responses.mdx
@@ -0,0 +1,279 @@
+---
+description: Responses are socket messages sent from Talo and other players
+title: Responses
+---
+
+## Players
+
+### Identification successful
+
+If the player is successfully identified, this response will be sent.
+
+```typescript
+{
+ "res": "v1.players.identify.success",
+ "data": PlayerAlias
+}
+```
+
+| Authentication required | Scopes |
+| ----------------------- | ------ |
+| Yes | None |
+
+### Presence updated
+
+This response is sent when a player's presence is updated.
+
+```typescript
+{
+ "res": "v1.players.presence.updated",
+ "data": {
+ "presence": PlayerPresence
+ "onlineChanged": boolean
+ "customStatusChanged": boolean
+ }
+}
+```
+
+| Authentication required | Scopes |
+| ----------------------- | -------------- |
+| Yes | `read:players` |
+
+## Channels
+
+### Receiving a message
+
+If a player is subscribed to a channel where a message has been sent, they will receive this response.
+
+```typescript
+{
+ "res": "v1.channels.message",
+ "data": {
+ "channel": GameChannel
+ "message": string
+ "playerAlias": PlayerAlias
+ }
+}
+```
+
+| Authentication required | Scopes |
+| ----------------------- | ------------------- |
+| Yes | `read:gameChannels` |
+
+### Joining a channel
+
+When a player joins a channel, all channel members will be notified.
+
+```typescript
+{
+ "res": "v1.channels.player-joined",
+ "data": {
+ "channel": GameChannel
+ "playerAlias": PlayerAlias
+ }
+}
+```
+
+| Authentication required | Scopes |
+| ----------------------- | ------------------- |
+| Yes | `read:gameChannels` |
+
+### Leaving a channel
+
+When a player leaves a channel, all channel members will be notified.
+
+```typescript
+enum GameChannelLeavingReason {
+ DEFAULT,
+ TEMPORARY_MEMBERSHIP
+}
+
+{
+ "res": "v1.channels.player-left",
+ "data": {
+ "channel": GameChannel
+ "playerAlias": PlayerAlias,
+ "meta": {
+ "reason": GameChannelLeavingReason
+ }
+ }
+}
+```
+
+| Authentication required | Scopes |
+| ----------------------- | ------------------- |
+| Yes | `read:gameChannels` |
+
+### Ownership transferred
+
+This response is sent to channel members when the owner is updated.
+
+```typescript
+{
+ "res": "v1.channels.ownership-transferred",
+ "data": {
+ "channel": GameChannel
+ "newOwner": PlayerAlias
+ }
+}
+```
+
+| Authentication required | Scopes |
+| ----------------------- | ------------------- |
+| Yes | `read:gameChannels` |
+
+### Channel deleted
+
+When channels are deleted, all members will be notified.
+
+```typescript
+{
+ "res": "v1.channels.deleted",
+ "data": {
+ "channel": GameChannel
+ }
+}
+```
+
+| Authentication required | Scopes |
+| ----------------------- | ------------------- |
+| Yes | `read:gameChannels` |
+
+### Channel updated
+
+This response is sent to channel members when any of the channel's properties (including its name and props) are updated.
+
+```typescript
+{
+ "res": "v1.channels.updated",
+ "data": {
+ "channel": GameChannel
+ "changedProperties": string[]
+ }
+}
+```
+
+| Authentication required | Scopes |
+| ----------------------- | ------------------- |
+| Yes | `read:gameChannels` |
+
+### Channel storage updated
+
+Channel members will receive this when storage props are created, updated or deleted.
+
+```typescript
+{
+ "res": "v1.channels.storage.updated",
+ "data": {
+ "channel": GameChannel
+ "upsertedProps": GameChannelStorageProp[]
+ "deletedProps": GameChannelStorageProp[]
+ }
+}
+```
+
+| Authentication required | Scopes |
+| ----------------------- | ------------------- |
+| Yes | `read:gameChannels` |
+
+## Live config
+
+### Live config updated
+
+This response is sent to all players when the live config is updated from the Talo dashboard.
+
+```typescript
+{
+ "res": "v1.live-config.updated",
+ "data": {
+ "config": Prop[]
+ }
+}
+```
+
+| Authentication required | Scopes |
+| ----------------------- | ----------------- |
+| Yes | `read:gameConfig` |
+
+## Types
+
+### Prop
+
+```ts
+type Prop = {
+ key: string
+ value: string
+}
+```
+
+### Player
+
+```ts
+type Player = {
+ id: number
+ props: Prop[]
+ devBuild: boolean
+ lastSeenAt: Date
+ createdAt: Date
+ groups: {
+ id: number
+ name: string
+ }[]
+ auth?: {
+ email: string
+ verificationEnabled: boolean
+ sessionCreatedAt: Date
+ }
+}
+```
+
+### PlayerAlias
+
+```ts
+type PlayerAlias = {
+ id: number
+ service: string
+ identifier: string
+ player: Player
+ lastSeenAt: Date
+ createdAt: Date
+ updatedAt: Date
+}
+```
+
+### PlayerPresence
+
+```ts
+type PlayerPresence = {
+ online: boolean
+ customStatus: string
+ playerAlias: PlayerAlias
+ updatedAt: Date
+}
+```
+
+### GameChannel
+
+```ts
+type GameChannel = {
+ id: number
+ name: string
+ owner: PlayerAlias
+ totalMessages: number
+ memberCount: number
+ props: Prop[]
+ createdAt: Date
+ updatedAt: Date
+}
+```
+
+```ts
+type GameChannelStorageProp = {
+ key: string
+ value: string
+ createdBy: PlayerAlias
+ lastUpdatedBy: PlayerAlias
+ createdAt: Date
+ updatedAt: Date
+}
+```
diff --git a/content/docs/pre-1.0/unity/channels.mdx b/content/docs/pre-1.0/unity/channels.mdx
new file mode 100644
index 0000000..fd9e75b
--- /dev/null
+++ b/content/docs/pre-1.0/unity/channels.mdx
@@ -0,0 +1,475 @@
+---
+description: Talo Channels can be used for player chats, sending data to specific groups, pushing game updates and storing shared game state.
+title: Channels and storage
+---
+
+import { ScopeBadges } from '@/components/documentation/ScopeBadges'
+
+## How channels work
+
+Channels allow you to send socket messages between players, making it extremely easy to add peer-to-peer (P2P) multiplayer mechanics.
+
+When players join a channel, they get subscribed to that channel's messages. Messages can be anything: plain strings for chat, enums for instructions/events or even JSON data.
+
+Channels can also store data inside special props that all subscribed players can modify.
+
+
+ Visit the [Channels feature page](https://trytalo.com/channels) for more details.
+
+
+## Listing channels
+
+
+
+To get all available channels for your game, use the `Talo.Channels.GetChannels()` function. This is a paginated function which takes a single `page` parameter.
+
+This function returns an array with 3 items: the channels, the total number of channels (if they weren't paginated) and if this is the last page.
+
+```csharp
+var page = 0
+var res = await Talo.Channels.GetChannels(new GetChannelsOptions { page = page })
+
+var channels: Channel[] = res.channels
+var count: int = res.count
+var isLastPage: bool = res.isLastPage
+```
+
+## Finding a channel
+
+
+
+You can find a channel by its ID using `Talo.Channels.Find()`. This function takes a channel ID integer and returns a `Channel` object.
+
+### Filtering by prop keys and values
+
+The following code will only fetch channels that have the "guildId" key:
+
+```csharp
+var options = new GetChannelsOptions() { page = page, propKey = "guildId" }
+var res = await Talo.Channels.GetChannels(options)
+```
+
+You can also filter by a prop value. This code will now make sure there is a "guildId" key and its value is "157":
+
+```csharp
+var options = new GetChannelsOptions() { page = page, propKey = "guildId", propValue = "157" }
+var res = await Talo.Channels.GetChannels(options)
+```
+
+### Searching for channels
+
+You can search for channels by name or owner identifier using the `search` option:
+
+```csharp
+var options = new GetChannelsOptions() { page = page, search = "search term" }
+var res = await Talo.Channels.GetChannels(options)
+```
+
+## Listing subscribed channels
+
+
+
+You can use `Talo.Channels.GetSubscribedChannels()` which returns a `Channel[]` to find out which channels the current player is subscribed to.
+
+### Filtering by prop keys and values
+
+Similar to `GetChannels()`, you can filter by prop keys and values by providing options:
+
+```csharp
+var options = new GetSubscribedChannelsOptions() { propKey = "guildId", propValue = "157" }
+var res = await Talo.Channels.GetSubscribedChannels(options)
+```
+
+## Creating a channel
+
+
+
+To create a channel, call `Talo.Channels.Create()` with a channel name and (optionally) the auto cleanup value and/or props.
+
+When auto cleanup is enabled, the channel will be deleted when the owner or the last subscribed member leaves.
+
+Props (an array of `(string, string)` tuples) are a way of adding arbitrary data to your channels in the same way as you would for events, players and leaderboards.
+
+```csharp
+var props = new (string, string)[]
+{
+ ("propKey", "propValue")
+};
+
+var channel = await Talo.Channels.Create(new CreateChannelOptions() { name = channelName, autoCleanup = true, props = props });
+```
+
+## Joining and leaving channels
+
+
+
+To join or leave a channel, use `Talo.Channels.Join()` and `Talo.Channels.Leave()` respectively. Both functions take the ID of the channel as the only parameter.
+
+If you attempt to join a channel and the current player is already in that channel, nothing will happen. Leaving a channel also follows the same pattern.
+
+## Updating channels
+
+
+
+The owner of a channel can update the channel using `Talo.Channels.Update()`. The name and owner of the channel can be updated using this function.
+
+To transfer ownership of the channel, you should specify the ID of the player alias that will own the channel. The new owner of the channel must be a member first before ownership can be transferred.
+
+You can also update the props of the channel: keys will be overrided with new values and keys with a value of `null` will be deleted.
+
+## Deleting channels
+
+
+
+The owner of a channel can delete the channel using `Talo.Channels.Delete()`. All other members of the channel will be unsubscribed automatically.
+
+## Private channels
+
+You can also create invite-only private channels using the `isPrivate` option.
+
+Private channels will not be listed when using `Talo.Channels.GetChannels()`. They also cannot be joined in the same way: the channel owner must invite players to a private channel.
+
+### Channel invites
+
+
+
+To create a channel invite, use `Talo.Channels.Invite()` with a channel ID and player alias ID.
+
+Invited players will automatically join the channel.
+
+```csharp
+var channel = await Talo.Channels.Create(new CreateChannelOptions() { name = "channel name", isPrivate = true }
+await Talo.Channels.Invite(channel.id, inviteePlayerAlias.id);
+```
+
+
+ you can use invites for public channels too.
+
+
+## Temporary membership channels
+
+If players should only be members of a channel while they're online, you can choose to enable the `temporary_membership` option when creating your channel:
+
+```csharp
+var channel = await Talo.Channels.Create(new CreateChannelOptions() { name = "channel name", temporaryMembership = true }
+```
+
+Any player that joins the channel and then goes offline will automatically be removed from the channel. The `Talo.Channels.OnChannelLeft` event will invoke with the reason `ChannelLeavingReason.TemporaryMembership`.
+
+## Getting channel members
+
+
+
+You can fetch a list of channel members using `Talo.Channels.GetMembers()`. This will return a `ChannelMembersResponse`:
+
+```csharp
+var membersPage = await Talo.Channels.GetMembers(channel.id);
+Debug.Log(string.Join(", ", membersPage.members.Select((m) => m.identifier)));
+```
+
+
+ the current player can only fetch channel members for channels they are part of.
+
+
+### Filtering channel members
+
+You can provide a `GetMembersOptions` as the second parameter of `GetMembers()` to provide extra filters:
+
+```csharp
+var options = new GetMembersOptions()
+{
+ page = 2,
+ propKey = "guildId",
+ propValue = "5"
+};
+var membersPage = await Talo.Channels.GetMembers(channel.id, options);
+
+var filteredMembers = membersPage.members;
+var isLastPage = membersPage.isLastPage;
+```
+
+You can provide the following filtering options:
+
+- `page`: the current pagination index
+- `playerId`: find channel members with this player ID
+- `aliasId`: find a channel member with this ID
+- `identifier`: find a channel member with this identifier
+- `propKey`: find channel members with this prop key
+- `propValue`: find channel members with a matching `propKey` and `propValue`
+- `playerGroupId`: find channel members that are part of this group
+
+## Listening for messages
+
+
+
+To listen for messages, you can subscribe to the `Talo.Channels.OnMessageReceived` event. This event provides the `Channel`, the sender's `PlayerAlias` and the message.
+
+Here's an example of how to use this event to update a chat UI:
+
+```csharp
+private void Start()
+{
+ Talo.Channels.OnMessageReceived += OnMessageReceived;
+}
+
+private void OnMessageReceived(Channel channel, PlayerAlias sender, string message)
+{
+ if (channel.id == _activeChannelId)
+ {
+ AddChatMessage($"[{channel.name}] {sender.identifier}: {message}");
+ }
+}
+```
+
+### Listening for other events
+
+You can also listen for the following events:
+
+- `Talo.Channels.OnChannelJoined`: Invoked when a player joins a channel. Returns the `TaloChannel` and the `TaloPlayerAlias` that joined.
+
+- `Talo.Channels.OnChannelLeft`: Invoked when a player leaves a channel. Returns the `TaloChannel`, the `TaloPlayerAlias` that left and the `ChannelLeavingReason`.
+
+- `Talo.Channels.OnOwnershipTransferred`: Invoked when channel ownership is transferred. Returns the `TaloChannel` and the new owner's `TaloPlayerAlias`.
+
+- `Talo.Channels.OnChannelDeleted`: Invoked when a channel is deleted. Returns the `TaloChannel` that was deleted.
+
+- `Talo.Channels.OnChannelUpdated`: Invoked when a channel is updated. Returns the `TaloChannel` that was updated and a `string[]` of properties that were changed.
+
+- `Talo.Channels.OnChannelPropsRejected`: Invoked when channel props are rejected during creation or update. Returns an array of `RejectedProp` objects. See [Handling rejected props](#handling-rejected-channel-props) for more details.
+
+## Handling rejected channel props
+
+Talo validates props when creating or updating channels. If one or more props are invalid, the channel will not be created or updated.
+
+You can listen for rejected props using the `Talo.Channels.OnChannelPropsRejected` event:
+
+```csharp
+private void OnEnable()
+{
+ Talo.Channels.OnChannelPropsRejected += OnChannelPropsRejected;
+}
+
+private void OnDisable()
+{
+ Talo.Channels.OnChannelPropsRejected -= OnChannelPropsRejected;
+}
+
+private void OnChannelPropsRejected(RejectedProp[] rejectedProps)
+{
+ foreach (var prop in rejectedProps)
+ {
+ Debug.Log($"Rejected prop '{prop.key}': {prop.message} ({prop.error})");
+ }
+}
+```
+
+
+ This event only fires for channel props (set during `Create()` and `Update()`). For channel
+ **storage** prop errors, use `OnChannelStoragePropsFailedToSet` instead.
+
+
+See [Players and props - Prop rejection reasons](/docs/pre-1.0/unity/player-props#prop-rejection-reasons) for the full list of rejection reason codes.
+
+## Channel storage
+
+Channel storage is a shared pool of props (key/value pairs) that can be read, created, updated and deleted by all members of the channel.
+
+In an open world game, you could store a reference to all the gatherable resources using channel storage. When a resource is gathered, other players in the same channel can be automatically notified so their world can be synced with the global world state.
+
+### Setting storage props
+
+
+
+Any player can update the global store using `Talo.Channels.SetStorageProps()`:
+
+```csharp
+await Talo.Channels.SetStorageProps(
+ channel.id,
+ ("prop1", "value1"),
+ ("prop2", "value2")
+);
+```
+
+This function accepts any number of prop `(string, string)` tuples. You can set a prop value to `null` to delete it. Storage props that aren't being deleted will be upserted (updated if they exist, otherwise created).
+
+#### Handling failures
+
+Sometimes, setting storage props can fail. This usually happens when you set a prop key with a size over 128 characters or a prop value with a size over 512 characters.
+
+The `Talo.Channels.OnChannelStoragePropsFailedToSet` event lets you listen for these errors:
+
+```csharp
+void Start()
+{
+ Talo.Channels.OnChannelStoragePropsFailedToSet += (Channel channel, ChannelStoragePropError[] errors) => {
+ foreach (var prop in errors)
+ {
+ // shared-gold: Prop value length (596) exceeds 512 characters (PROP_VALUE_TOO_LONG)
+ Debug.Log($"{prop.key}: {prop.message} ({prop.error})");
+ }
+ };
+}
+```
+
+`ChannelStoragePropError` includes a `message` with a human-readable description of the error, alongside the `error` code. See [Players and props - Prop rejection reasons](/docs/pre-1.0/unity/player-props#prop-rejection-reasons) for the full list of rejection reason codes.
+
+### Setting storage prop arrays
+
+
+
+Prop arrays are a special type of prop that can store multiple values under the same key. This is useful for storing lists of items, player IDs or other data that can have multiple values.
+
+
+Array keys are internally suffixed with [] (e.g. a key of "world_items" is stored as "world_items[]").
+
+When using the prop-array-specific functions, you should reference the key without the [] suffix.
+
+
+
+```csharp
+var key = "world_items";
+var items = new string[] { "sword", "shield", "potion" };
+
+await Talo.Channels.SetStoragePropArray(channel.id, key, items);
+```
+
+Passing an empty array will delete all existing values for the prop.
+
+You can handle failures in the same way [described above](#handling-failures). Individual props have the same limitations as regular props and prop arrays have an additional limit of 1000 items.
+
+### Getting storage props
+
+
+
+To get a prop, use `Talo.Channels.GetStorageProp()`. In the example below, we're finding a channel for the player's guild and fetching the shared gold pool:
+
+```csharp
+var options = new GetSubscribedChannelsOptions() { propKey = "guildId", propValue = "157" };
+var res = await Talo.Channels.GetSubscribedChannels(options);
+
+var channel = res[0];
+var prop = await Talo.Channels.GetStorageProp(channel.id, "shared-gold")
+```
+
+After fetching a prop, you can access the `value`, `createdBy`, `lastUpdatedBy` (and more) from the `ChannelStorageProp` class.
+
+
+Getting storage props immediately after an update can return stale data. This is because the internal cache is only updated when the `OnChannelStoragePropsUpdated` event is invoked.
+
+We recommend using the event for prop updates, as described in the [Listening for storage updates](#listening-for-storage-updates) section.
+If you need to fetch the latest data immediately, you can skip the internal cache: see the [Cache-busting](#cache-busting) section below.
+
+
+
+#### Cache-busting
+
+Talo keeps an internal cache of storage props which is automatically updated whenever props are created, updated or deleted. By default, Talo will pull from the internal cache which is generally up-to-date.
+
+To guarantee fetching the freshest data, you can skip the internal cache with the final parameter of `GetStorageProp()`:
+
+```csharp
+// checks the internal cache first,
+// if the key isn't set, fetches the latest data directly from the database
+var prop = await Talo.Channels.GetStorageProp(channel.id, "shared-gold", false /* default */);
+
+// fetches the latest data directly from the database
+var freshProp = await Talo.Channels.GetStorageProp(channel.id, "shared-gold", true);
+```
+
+### Getting storage prop arrays
+
+
+
+Prop arrays are made up of multiple props with the same key. To fetch all items in a prop array, use `Talo.Channels.GetStoragePropArray()`:
+
+```csharp
+var propKey = "world_items";
+
+// checks the internal cache first
+var arrayItems = await Talo.Channels.GetStoragePropArray(channel.id, propKey, false /* default */);
+
+// fetches the latest data directly from the database
+var freshArrayItems = await Talo.Channels.GetStoragePropArray(channel.id, propKey, true);
+
+foreach (var prop in arrayItems)
+{
+ Debug.Log(prop.value);
+}
+```
+
+### Getting multiple storage props
+
+
+
+If you need to fetch multiple storage props, `Talo.Channels.ListStorageProps()` is much faster and more efficient than fetching them one by one:
+
+```csharp
+await Talo.Channels.SetStorageProps(channel.id,
+ ("storage_prop_1", "true"),
+ ("storage_prop_2", "hello world")
+);
+
+var propKeys = new string[] { "storage_prop_1", "storage_prop_2" };
+
+// without cache busting
+var results = await Talo.Channels.ListStorageProps(channel.id, propKeys);
+foreach (var prop in results)
+{
+ Debug.Log($"Prop: {prop.key} = {prop.value}");
+}
+
+// with cache busting
+var bustedResults = await Talo.Channels.ListStorageProps(channel.id, propKeys, true);
+foreach (var prop in bustedResults)
+{
+ Debug.Log($"Prop: {prop.key} = {prop.value}");
+}
+```
+
+This function will return a `ChannelStorageProp` array, allowing you to iterate through the results.
+
+
+ if a prop cannot be found, it will not appear in the list.
+
+
+#### Prop arrays
+
+If any of the keys you pass are [prop arrays](/docs/pre-1.0/unity/channels#getting-storage-prop-arrays), append `[]` to the key to fetch every item stored under it. Scalar and array keys can be mixed in a single call:
+
+```csharp
+// storage_prop_1 is a scalar, world_items[] is a prop array
+var mixedKeys = new string[] { "storage_prop_1", "world_items[]" };
+var results = await Talo.Channels.ListStorageProps(channel.id, mixedKeys);
+foreach (var prop in results)
+{
+ Debug.Log($"{prop.key} = {prop.value}");
+}
+```
+
+### Listening for storage updates
+
+
+
+The `Talo.Channels.OnChannelStoragePropsUpdated` event will invoke when a storage update is received. It will list the channel, upserted (inserted or updated) props and deleted props:
+
+```csharp
+void Start()
+{
+ Talo.Channels.OnChannelStoragePropsUpdated += OnChannelStoragePropsUpdated;
+}
+
+void OnChannelStoragePropsUpdated(Channel channel, ChannelStorageProp[] upsertedProps, ChannelStorageProp[] deletedProps)
+{
+ foreach (var prop in upsertedProps)
+ {
+ // e.g. "shared-gold: 80 upserted by jim"
+ Debug.Log($"{prop.key}:{prop.value} upserted by {prop.lastUpdatedBy.identifier}");
+ }
+
+ foreach (var prop in deletedProps)
+ {
+ // e.g. "shared-gold deleted by jim, previous value was 80"
+ Debug.Log($"{prop.key} deleted by {prop.lastUpdatedBy.identifier}, previous value was {prop.value}");
+ }
+}
+```
diff --git a/content/docs/pre-1.0/unity/continuity.mdx b/content/docs/pre-1.0/unity/continuity.mdx
new file mode 100644
index 0000000..f0a05ec
--- /dev/null
+++ b/content/docs/pre-1.0/unity/continuity.mdx
@@ -0,0 +1,120 @@
+---
+description: Talo is designed to work online and offline. Talo will automatically keep data in sync for offline players.
+title: Continuity and offline mode
+---
+
+import { ScopeBadges } from '@/components/documentation/ScopeBadges'
+
+## Continuity
+
+
+
+Talo Continuity is a resilience layer to keep your game in sync when Talo can't be reached.
+
+Continuity runs behind the scenes and catches server errors (like 502 Bad Gateway, 503 Service Unavailable or 504 Gateway Timeout) or [UnityWebRequest errors](https://docs.unity3d.com/ScriptReference/Networking.UnityWebRequest.Result.html).
+
+Failed requests are cached and replayed when network connectivity is re-established. Internally, a health check endpoint is polled to assess Talo's availability.
+
+
+ Continuity requires the `write:continuityRequests` API key scope. Without it, continuity won't work.
+
+
+### How does it work?
+
+When a network request to Talo fails, the request is cached to the player's disk. Continuity periodically checks if the network connection to Talo has been restored and if it has, the cached requests will be replayed in batches.
+
+Continuity also works between sessions: any pending continuity requests will get picked up when the player reopens your game.
+
+### Continuity timestamp
+
+Replayed requests send a special header indicating the original time when the request was made. Talo uses this header to set the `createdAt` of various entities like feedback, leaderboard entries and player stats.
+
+### Configuring Continuity
+
+As with anything on the client-side, despite being built with security in mind, Continuity could be exploited by bad actors. Depending on the type of game you're building, this could have varying consequences. You can disable continuity by unchecking `Continuity enabled` in your `Talo Settings` asset.
+
+### Testing Continuity
+
+Continuity happens in the background and there are no direct APIs exposed for it. You can simulate being offline and toggle Continuity in the Talo Playground.
+
+
+ toggling these options will automatically update your `Talo Settings` asset.
+
+
+## Network connection events
+
+Talo fires events when network connectivity changes, allowing you to respond to connection issues in your game.
+
+### `OnConnectionLost`
+
+Fired when Talo loses connection to the server. This happens when health checks fail or network errors occur.
+
+```csharp
+private void Start()
+{
+ Talo.OnConnectionLost += OnConnectionLost;
+}
+
+private void OnDestroy()
+{
+ Talo.OnConnectionLost -= OnConnectionLost;
+}
+
+private void OnConnectionLost()
+{
+ Debug.Log("Connection to Talo lost");
+ // handle offline state (e.g., show an offline indicator)
+}
+```
+
+### `OnConnectionRestored`
+
+Fired when connection to Talo is re-established after being lost.
+
+```csharp
+private void Start()
+{
+ Talo.OnConnectionRestored += OnConnectionRestored;
+}
+
+private void OnDestroy()
+{
+ Talo.OnConnectionRestored -= OnConnectionRestored;
+}
+
+private void OnConnectionRestored()
+{
+ Debug.Log("Connection to Talo restored");
+ // handle online state (e.g., hide an offline indicator)
+}
+```
+
+## Offline interactions
+
+### Checking if the player is offline
+
+`Talo.IsOffline()` returns a boolean indicating if offline mode is enabled. Offline mode occurs when Unity detects that the player is offline or when the [`offlineMode` setting](settings-reference#offlinemode) is enabled.
+
+Use this function to handle custom offline interactions, such as building your own offline caches.
+
+### Player identification
+
+After a successful online identification, player data is cached locally for future offline use. You can disable this interaction with the [`cachePlayerOnIdentify` setting](settings-reference#cacheplayeronidentify).
+
+
+ players cannot be created while offline.
+
+
+### Game saves
+
+All player saves are cached locally and automatically loaded when offline. Updates to save files are mirrored to the offline cache.
+
+Saves sync automatically when connectivity is restored. If the offline save is newer than the latest version of the online save, the offline save will become the new latest version.
+
+### Live config
+
+Live config data is cached locally after successful online queries. Updates are synced with the local cache, which is used when the player is offline.
+
+### Talo socket
+
+When connection is restored, the Talo socket automatically reconnects by repeating the socket token identification flow. This happens seamlessly in the background without requiring manual intervention.
diff --git a/content/docs/pre-1.0/unity/dev-data.mdx b/content/docs/pre-1.0/unity/dev-data.mdx
new file mode 100644
index 0000000..5e169a2
--- /dev/null
+++ b/content/docs/pre-1.0/unity/dev-data.mdx
@@ -0,0 +1,23 @@
+---
+description: Separate your development data from live data inside the Talo dashboard.
+title: Separating development data
+---
+
+## Debug builds
+
+It's very common to test your development builds against Talo services you've already configured like stats and leaderboards.
+Usually, you'll want to separate out development data from live data and Talo automates this for you.
+
+If the [`Debug.isDebugBuild`](https://docs.unity3d.com/ScriptReference/Debug-isDebugBuild.html) flag is set to true, any data sent from the Unity package is marked as development data and can be separated out in the dashboard.
+
+Similarly, if the flag is set, development data will be included when fetching entities like leaderboard entries.
+
+## "Dev" players
+
+Dev players are players created in development builds. Their data will be permanently marked as development data - regardless if they create any new data in release builds.
+
+In the dashboard, this is denoted by orange backgrounds:
+
+
+
+You can toggle a player's dev status from their profile.
diff --git a/content/docs/pre-1.0/unity/events.mdx b/content/docs/pre-1.0/unity/events.mdx
new file mode 100644
index 0000000..8ec69ba
--- /dev/null
+++ b/content/docs/pre-1.0/unity/events.mdx
@@ -0,0 +1,91 @@
+---
+description: Event tracking allows you to understand what players are doing inside your game.
+title: Events
+---
+
+import { ScopeBadges } from '@/components/documentation/ScopeBadges'
+
+## Tracking
+
+
+
+To track an event, simply call `Talo.Events.Track()` with an event name like "Level up". You can optionally attach properties to events by passing any number of extra `(string, string)` tuples to the function call:
+
+```csharp title="TrackLevelUpEvent.cs"
+private void LevelUp()
+{
+ level++;
+
+ Talo.Events.Track(
+ "Level up",
+ ("newLevel", $"{level}"),
+ ("timeTaken", $"{timeTaken}")
+ );
+
+ timeTaken = 0;
+}
+```
+
+### Array props
+
+You can attach multiple values under a single key by repeating the key with a `[]` suffix:
+
+```csharp title="TrackLevelUpEvent.cs"
+private void LevelUp()
+{
+ level++;
+
+ Talo.Events.Track(
+ "Level up",
+ ("equipment[]", "sword"),
+ ("equipment[]", "shield"),
+ ("equipment[]", "helmet")
+ );
+}
+```
+
+### Mixed props
+
+Array props can be mixed with scalar props in the same call:
+
+```csharp title="TrackLevelUpEvent.cs"
+private void LevelUp()
+{
+ level++;
+
+ Talo.Events.Track(
+ "Level up",
+ ("newLevel", $"{level}"),
+ ("timeTaken", $"{timeTaken}"),
+ ("equipment[]", "sword"),
+ ("equipment[]", "shield")
+ );
+}
+```
+
+
+ Check out this blog post on [how to track events in
+ Unity](https://trytalo.com/blog/event-tracking-unity?utm_source=docs&utm_medium=tip) for a
+ detailed walkthrough
+
+
+## Flushing
+
+Events are automatically sent to Talo in batches. You can manually call `Talo.events.flush()` to send the events currently waiting in the queue. Flushing also happens automatically when:
+
+1. The game loses focus
+2. The game is paused
+3. The game is closed
+4. In WebGL builds, after the time in seconds defined in the `webGLEventFlushRate` setting has elapsed. Unity doesn't implement the `OnApplicationQuit` function for WebGL builds and as a result event tracking is slightly more unreliable. You can increase or decrease the `webGLEventFlushRate` but be wary of the reasons why events are batched described above.
+
+## Meta props
+
+Talo sends some extra props with each event. These props are made up of the following metadata:
+
+- The player's operating system
+- The version of the game
+- The window mode (fullscreen, fullscreen windowed, maximized window or windowed)
+- The width of the game window
+- The height of the game window
+
+These properties are then attached as props to the player, providing you with extra tools for debugging possible bugs and configurations where they appear.
diff --git a/content/docs/pre-1.0/unity/feedback.mdx b/content/docs/pre-1.0/unity/feedback.mdx
new file mode 100644
index 0000000..5a1f1e4
--- /dev/null
+++ b/content/docs/pre-1.0/unity/feedback.mdx
@@ -0,0 +1,110 @@
+---
+description: Talo Feedback allows you to receive feedback directly from players. Feedback can be categorised, analysed and filtered to improve your game.
+title: Feedback
+---
+
+import { ScopeBadges } from '@/components/documentation/ScopeBadges'
+
+## Creating feedback categories
+
+To create a feedback category, visit [the feedback page](https://dashboard.trytalo.com/feedback) inside the Talo dashboard and click "Edit categories". Take note of the `Internal name` of the category you create as this is how you'll be referring to it inside your game.
+
+## Getting available categories
+
+
+
+To list all the feedback categories for your game, use the `GetCategories()` function:
+
+```csharp title="GetCategories.cs"
+using UnityEngine;
+using TaloGameServices;
+using System.Threading.Tasks;
+using System.Linq;
+
+public class GetCategories : MonoBehaviour
+{
+ public async void OnButtonClick()
+ {
+ await FetchCategories();
+ }
+
+ private async Task FetchCategories()
+ {
+ var categories = await Talo.Feedback.GetCategories();
+
+ if (categories.Length == 0)
+ {
+ Debug.Log("No categories found. Create some in the Talo dashboard!");
+ }
+ else
+ {
+ var mapped = categories.Select((c) => c.internalName);
+ Debug.Log($"Categories: " + string.Join(',', mapped)); // prints: "bugs, gameplay-feedback, terrain-issues"
+ }
+ }
+}
+```
+
+## Sending feedback
+
+
+
+To send feedback, call the `Send()` function with the player's comment and your chosen category's internal name:
+
+```csharp title="SendFeedback.cs"
+using UnityEngine;
+using TaloGameServices;
+
+public class SendFeedback : MonoBehaviour
+{
+ public string internalName, feedbackComment;
+
+ public async void OnButtonClick()
+ {
+ await Talo.Feedback.Send(internalName, feedbackComment);
+ Debug.Log($"Feedback sent for {internalName}: {feedbackComment}");
+ }
+}
+```
+
+### Additional context with props
+
+You can also provide extra information with `props` by providing `(string, string)` tuples as the final parameter(s) of `Send()`. Props are displayed in the Talo dashboard and can be used as filters. For example, you could look at all feedback from a specific game version.
+
+```csharp
+public async void OnButtonClick()
+{
+ var categoryInternalName = "worldgen_feedback";
+ var feedbackComment = "Too many spikes!";
+
+ await Talo.Feedback.Send(categoryInternalName, feedbackComment, ("seed", "432423432"), ("difficultyLevel", "EASY"));
+}
+```
+
+### Handling rejected props
+
+Talo validates feedback props. If one or more props are invalid, the feedback will not be saved.
+
+You can listen for rejected props using the `Talo.Feedback.OnPropsRejected` event:
+
+```csharp
+private void OnEnable()
+{
+ Talo.Feedback.OnPropsRejected += OnPropsRejected;
+}
+
+private void OnDisable()
+{
+ Talo.Feedback.OnPropsRejected -= OnPropsRejected;
+}
+
+private void OnPropsRejected(RejectedProp[] rejectedProps)
+{
+ foreach (var prop in rejectedProps)
+ {
+ Debug.Log($"Rejected prop '{prop.key}': {prop.message} ({prop.error})");
+ }
+}
+```
+
+See [Players and props - Prop rejection reasons](/docs/pre-1.0/unity/player-props#prop-rejection-reasons) for the full list of rejection reason codes.
diff --git a/content/docs/pre-1.0/unity/groups.mdx b/content/docs/pre-1.0/unity/groups.mdx
new file mode 100644
index 0000000..05c6417
--- /dev/null
+++ b/content/docs/pre-1.0/unity/groups.mdx
@@ -0,0 +1,56 @@
+---
+description: Talo groups allow you to apply filters on your player-base to create distinct segments between players. You can have groups for beta testers, players who completed your game or players with specific props.
+title: Player groups
+---
+
+import { ScopeBadges } from '@/components/documentation/ScopeBadges'
+
+## Creating groups
+
+To create a group, visit [the groups page](https://dashboard.trytalo.com) inside the Talo dashboard. Take note of the `ID` as this is how you'll be referring to your group.
+
+## Checking membership
+
+You can check if a player belongs to a group using `IsInGroupID()`. Alternatively you can search by group name using `IsInGroupName()` too.
+
+```csharp
+public bool IsPowerUser()
+{
+ return Talo.CurrentPlayer.IsInGroupID('9e56e835-eff6-4a6d-ac35-db8e7561af0e')
+}
+
+public bool IsBetaTester()
+{
+ return Talo.CurrentPlayer.IsInGroupName('beta-testers')
+}
+```
+
+## Fetching individual groups
+
+
+
+Groups in the `Player` class are stubs that only include an `id` and a `name`.
+
+To retrieve more data about a group, including its members, use the `Talo.PlayerGroups.Get()` function:
+
+```csharp
+var groupPage = await Talo.PlayerGroups.Get(groupId);
+
+if (groupPage.count == 0)
+{
+ Debug.Log("No players in group");
+ return;
+}
+
+var identifiers = new List();
+foreach (var player in groupPage.group.members)
+{
+ identifiers.Add(player.GetAlias().identifier);
+}
+
+Debug.Log($"Found {groupPage.group.count} members: {string.Join(", ", identifiers)}");
+```
+
+Group members will only be visible if you've enabled the setting on your group in the Talo dashboard. If `membersVisible` is `false`, `members` will always be an empty array.
+
+Group members must be paginated. You can do this by providing a page number to `Get()` which returns a `PlayerGroupsGetResponse` with information such as `count`, `itemsPerPage` and `isLastPage`.
diff --git a/content/docs/pre-1.0/unity/identifying.mdx b/content/docs/pre-1.0/unity/identifying.mdx
new file mode 100644
index 0000000..0a56966
--- /dev/null
+++ b/content/docs/pre-1.0/unity/identifying.mdx
@@ -0,0 +1,359 @@
+---
+description: The Talo Unity package allows you to authenticate players, create player aliases and sync identities with external identity providers.
+title: Identifying a player
+---
+
+import { ScopeBadges } from '@/components/documentation/ScopeBadges'
+
+
+## Player aliases
+
+In Talo, players can have multiple aliases.
+For example, a player could have Steam and Google Play accounts. Both accounts can be linked to the same player, allowing them to use either service to log in to your game.
+
+## Identifying
+
+
+
+You should identify a player _after_ you know who they are (post-authentication) but _before_ you attempt to track any events, add leaderboard entries or do anything related directly to the player.
+
+You can identify a player using `Talo.Players.Identify()`. The code sample below shows you how you could identify a player using a UI element (this example is also available in the Playground):
+
+```csharp title="IdentifyPlayer.cs"
+using TaloGameServices;
+
+public class IdentifyPlayer: MonoBehaviour
+{
+ public string service = 'username', identifier = '123456';
+
+ public void OnButtonClick()
+ {
+ Identify();
+ }
+
+ private async void Identify()
+ {
+ try
+ {
+ await Talo.Players.Identify(service, identifier);
+ }
+ catch (Exception ex)
+ {
+ Debug.LogError(ex.Message);
+ }
+ }
+}
+```
+
+
+You cannot use "Talo" for the `service` parameter as this is reserved for **Talo Player Authentication**.
+
+If you are using Talo Player Authentication, `Talo.Players.Identify()` will be invoked automatically and the `Talo.Players.OnIdentified` event will also invoke as normal.
+
+Visit the [Player authentication docs](/docs/pre-1.0/unity/player-authentication) to learn more about identifying players with authentication enabled.
+
+
+
+### The "OnIdentified" event
+
+After a successful identification, the `Talo.Players.OnIdentified()` event will invoke, returning the identified player. This allows you to, for example, immediately fetch that player's saves:
+
+```csharp
+Talo.Players.OnIdentified += async (player) =>
+{
+ await Talo.Saves.GetSaves();
+};
+```
+
+### The "OnIdentificationStarted" and "OnIdentificationFailed" events
+
+When `Talo.Players.Identify()` is called, the `Talo.Players.OnIdentificationStarted` event is invoked.
+
+If identification fails, the `Talo.Players.OnIdentificationFailed` event is invoked.
+
+## Checking identification
+
+You can use `Talo.IdentityCheck()` to verify whether a player has been identified - it throws an error if a player hasn't been identified yet:
+
+```csharp
+public void DoStuffIfIdentified()
+{
+ try
+ {
+ Talo.IdentityCheck();
+ }
+ catch (Exception ex)
+ {
+ return;
+ }
+
+ // do stuff
+}
+```
+
+## Clearing the identified player
+
+You can clear the current player using `Talo.Players.ClearIdentity()`.
+
+This will set `Talo.CurrentAlias` and `Talo.CurrentPlayer` to `null`. It will also clear any stored or pending data such as the [offline player cache](#offline-player-cache), pending events and continuity requests. For players using Talo authentication, it will also clear session data.
+
+Once all the relevant data has been cleared, the `Talo.Players.OnIdentityCleared` event will be fired.
+
+```csharp
+private async void ClearIdentity()
+{
+ try
+ {
+ await Talo.Players.ClearIdentity();
+ }
+ catch (Exception ex)
+ {
+ Debug.LogError($"Failed to clear identity: {ex.Message}");
+ }
+}
+
+// Listen for the identity cleared event
+void Start()
+{
+ Talo.Players.OnIdentityCleared += () =>
+ {
+ Debug.Log("Player identity has been cleared");
+ // Handle post-clear logic here
+ };
+}
+```
+
+## Merging players
+
+
+
+Sometimes you might start tracking a player's actions before you know their true identity. For example, you could be tracking events with an "anonymous" identifier and then later on the same player chooses their username before submitting a leaderboard entry. Since both of these players need to be identified, two players will be created.
+
+You can merge players using `Talo.Players.Merge()` by providing the IDs of both players. The merge process takes all the props, aliases, and associated data (events, leaderboard entries, saves, etc.) from **Player 2** and merges them into **Player 1**. This means that duplicate props in **Player 1** will be replaced by the ones from **Player 2**.
+
+
+Player merging has some limitations:
+
+- **Player 2** cannot have a Talo Player Authentication, Steam, Google Play Games or Apple Game Center alias.
+- If **Player 1** has a Talo Player Authentication, Steam, Google Play Games or Apple Game Center alias, the merge must be initiated while identified as **Player 1** (i.e. `Talo.CurrentAlias` must belong to Player 1). In this case, make sure your last `Talo.Players.Identify()` call before merging uses Player 1's alias.
+- Both players cannot have overlapping alias services. For example, if both players have an alias with the service "username", the merging process will fail.
+
+
+You can provide the `post_merge_identity_service` option to automatically re-identify the player once merging is complete:
+
+```csharp
+await Talo.Players.Identify("anonymous", Guid.NewGuid().ToString());
+var player1Id = Talo.CurrentPlayer.id;
+await Talo.Players.Identify("username", "guyman");
+var player2Id = Talo.CurrentPlayer.id;
+
+Debug.Log(Talo.CurrentAlias.Service) // "username"
+
+var mergedPlayer = await Talo.Players.Merge(player1Id, player2Id, new MergeOptions
+{
+ postMergeIdentityService = "anonymous" // go back to the anonymous alias
+});
+
+Debug.Log(Talo.CurrentAlias.Service) // "anonymous"
+```
+
+In the example above, the two players created with `Talo.Players.Identify()` are merged. Before merging, the current alias service was **"username"** (because that was the most recently identified player). Setting the `postMergeIdentityService` option will invoke `Talo.Players.Identify()` with the **"anonymous"** alias.
+
+## Steamworks integration
+
+
+ You can enable this integration on the [integrations
+ page](https://dashboard.trytalo.com/integrations).
+
+
+If you have the Steamworks integration enabled, Talo can sync a Steam player ([as described here](/docs/pre-1.0/integrations/steamworks#authentication)). You can do this via the `Talo.Players.IdentifySteam` function. Here's a modified version of an example [provided by Unity](https://docs.unity.com/ugs/en-us/manual/authentication/manual/platform-signin-steam) using Steamworks.NET:
+
+```csharp
+Callback m_AuthTicketForWebApiResponseCallback;
+string m_SessionTicket;
+string identity = "talo";
+
+void SignInWithSteam()
+{
+ // It's not necessary to add event handlers if they are
+ // already hooked up.
+ // Callback.Create return value must be assigned to a
+ // member variable to prevent the GC from cleaning it up.
+ // Create the callback to receive events when the session ticket
+ // is ready to use in the web API.
+ // See GetAuthSessionTicket document for details.
+ m_AuthTicketForWebApiResponseCallback = Callback.Create(OnAuthCallback);
+
+ SteamUser.GetAuthTicketForWebApi(identity);
+}
+
+void OnAuthCallback(GetTicketForWebApiResponse_t callback)
+{
+ m_SessionTicket = BitConverter.ToString(callback.m_rgubTicket).Replace("-", string.Empty);
+ m_AuthTicketForWebApiResponseCallback.Dispose();
+ m_AuthTicketForWebApiResponseCallback = null;
+
+ Talo.Players.IdentifySteam(m_SessionTicket, identity);
+}
+```
+
+The `identity` parameter is optional but strongly recommended as it ensures proper identification of the service verifying the ticket. It can be anything you like but must be the same as the `identity` passed to Steam when fetching the ticket.
+
+### Steamworks player props
+
+After successfully authenticating the player, several [props](/docs/pre-1.0/unity/player-props) will automatically be created for them:
+
+- `META_STEAMWORKS_VAC_BANNED` - "true" or "false"
+- `META_STEAMWORKS_PUBLISHER_BANNED` - "true" or "false"
+- `META_STEAMWORKS_OWNS_APP` - "true" or "false"
+- `META_STEAMWORKS_OWNS_APP_PERMANENTLY` - "true" or "false"
+- `META_STEAMWORKS_OWNS_APP_FROM_DATE` - ISO date e.g. "2025-11-08T17:30:00Z"
+- `META_STEAMWORKS_PERSONA_NAME` - the player's current display name
+- `META_STEAMWORKS_AVATAR_HASH` - e.g. "5bfdbd8cd4407d6cd37dfba06851b62a4856bb2d"
+
+You can build an avatar URL by replacing **[AVATAR_HASH]** with the `META_STEAMWORKS_AVATAR_HASH` prop value in the following URL: `https://avatars.steamstatic.com/[AVATAR_HASH]_full.jpg`.
+
+These props will be updated each time the player is identified using `Talo.Players.IdentifySteam()`.
+
+## Google Play Games integration
+
+
+ You can enable this integration on the [integrations
+ page](https://dashboard.trytalo.com/integrations).
+
+
+If you have the Google Play Games integration enabled, Talo can identify a player using an OAuth client. All you need to do is pass a server auth code to the `Talo.Players.IdentifyGooglePlayGames` function.
+
+The official Google Play Games Plugin has documentation for how to [request server auth codes](https://developer.android.com/games/pgs/unity/unity-start#retrieve-auth-codes).
+
+```csharp
+using GooglePlayGames.BasicApi;
+
+// Define selectedScope having additional identity scopes.
+private List selectedScopes = new List();
+
+// Add scopes you want to request.
+selectedScopes.Add(AuthScope.OPEN_ID);
+selectedScopes.Add(AuthScope.PROFILE);
+selectedScopes.Add(AuthScope.EMAIL);
+
+// Call RequestServerSideAccess with additional scopes and retrieve
+// authcode and grantedscopes list.
+PlayGamesPlatform.Instance.RequestServerSideAccess(
+ /* forceRefreshToken= */ false,
+ selectedScopes,
+ (AuthResponse authResponse) =>
+ {
+ string authCode = authResponse.GetAuthCode();
+ List grantedScopes = authResponse.GetGrantedScopes();
+
+ // Pass the auth code to Talo
+ // Alternatively, you can `await` the result
+ _ = Talo.Players.IdentifyGooglePlayGames(authCode);
+ }
+);
+```
+
+### Google Play Games player props
+
+After successfully authenticating the player, these [props](/docs/pre-1.0/unity/player-props) will automatically be created for them:
+
+- `META_GOOGLE_PLAY_GAMES_DISPLAY_NAME` - The player's chosen display name
+- `META_GOOGLE_PLAY_GAMES_AVATAR_URL` - A direct link to the player's avatar
+
+These props will be updated each time the player is identified using `Talo.Players.IdentifyGooglePlayGames()`.
+
+## Apple Game Center integration
+
+
+ You can enable this integration on the [integrations
+ page](https://dashboard.trytalo.com/integrations).
+
+
+If you have the Apple Game Center integration enabled, Talo can identify a player using Apple's identity verification signature. You can do this via the `Talo.Players.IdentifyGameCenter` function.
+
+You'll need to fetch the local player's identity verification signature using [`FetchItems`](https://github.com/apple/unityplugins/blob/main/plug-ins/Apple.GameKit/Apple.GameKit_Unity/Assets/Apple.GameKit/Source/GKLocalPlayer.cs) from the [Apple Game Kit Unity plugin](https://github.com/apple/unityplugins) and pass the resulting values to Talo. Here's a modified version of an example [provided by Unity](https://docs.unity.com/en-us/authentication/platform-signin/apple-game-center):
+
+```csharp
+using UnityEngine;
+using System.Threading.Tasks;
+using Apple.GameKit;
+
+public class IdentifyPlayer : MonoBehaviour
+{
+ private void Start()
+ {
+ Talo.Players.OnIdentified += OnIdentified;
+
+ GKLocalPlayer.AuthenticateError += (err) => {
+ Debug.LogError(err.LocalizedDescription);
+ };
+
+ GKLocalPlayer.AuthenticateUpdate += async (player) => {
+ if (player.IsAuthenticated)
+ {
+ await Identify();
+ }
+ };
+ }
+
+ public async void OnLoginClick()
+ {
+ await GKLocalPlayer.Authenticate();
+ }
+
+ private async Task Identify()
+ {
+ var fetchItemsResponse = await GKLocalPlayer.Local.FetchItemsForIdentityVerificationSignature();
+
+ var publicKeyUrl = fetchItemsResponse.PublicKeyUrl;
+ var signature = fetchItemsResponse.GetSignature();
+ var salt = fetchItemsResponse.GetSalt();
+ var timestamp = fetchItemsResponse.Timestamp;
+
+ await Talo.Players.IdentifyGameCenter(
+ publicKeyUrl,
+ signature,
+ salt,
+ timestamp,
+ GKLocalPlayer.Local.TeamPlayerId
+ );
+ }
+
+ private void OnIdentified(Player player)
+ {
+ Debug.Log($"Player identifier: {player.id}");
+ }
+}
+```
+
+## Offline player cache
+
+If the `cachePlayerOnIdentify` setting is enabled (default `true`), Talo will store player data locally. If a player tries to identify while offline, Talo will try to use local data if it exists.
+
+## Display names
+
+Every player alias has a `displayName` that provides a human-readable name for the player. You can read it from `Talo.CurrentAlias.displayName` after a player has been identified. Display names are resolved from a [player's props](/docs/pre-1.0/unity/player-props) - arbitrary properties persisted across all of a player's aliases.
+
+```csharp title="WelcomeLabel.cs"
+using TaloGameServices;
+
+public class WelcomeLabel : MonoBehaviour
+{
+ private void Start()
+ {
+ Talo.Players.OnIdentified += (player) =>
+ {
+ Debug.Log($"Welcome, {Talo.CurrentAlias.displayName}!");
+ };
+ }
+}
+```
+
+By default, a player's display name is their alias `identifier`. You can configure the **display name prop key** on the [game settings page](https://dashboard.trytalo.com/game-settings) in the Talo dashboard. When set, Talo resolves each alias's `displayName` to the value of the player's prop with that key.
+
+
+
+For example, if your game's display name prop key is `playerChosenName` and a player has a prop `playerChosenName = "TheGuy"`, their alias `displayName` will be "TheGuy".
+
+If a player doesn't have a prop matching the configured key, their display name falls back to their alias `identifier`. The display name is resolved by Talo on each identification, so it stays up to date as the player's props change.
diff --git a/content/docs/pre-1.0/unity/install.mdx b/content/docs/pre-1.0/unity/install.mdx
new file mode 100644
index 0000000..f5355fe
--- /dev/null
+++ b/content/docs/pre-1.0/unity/install.mdx
@@ -0,0 +1,52 @@
+---
+description: Learn how to add Talo's Unity package to your game.
+title: Installation
+---
+
+## Required Unity version
+
+Talo is built and tested with Unity 6.
+
+Talo takes advantage of features from C# 9.0+. If you receive compilation errors after installing the package, you may need to make some configuration changes in your Unity project. For more information, [check out this article](https://learn.microsoft.com/en-us/visualstudio/gamedev/unity/unity-scripting-upgrade).
+
+## Import into Unity
+
+### Using the Unity Asset Store (recommended)
+
+The easiest way to add Talo to your project is by downloading it from the Unity Asset Store. Simply visit the [Talo Asset Store page](https://assetstore.unity.com/packages/tools/game-toolkits/talo-game-services-292832) and click "Add to My Assets".
+
+To add Talo to your project, open the `Window > My Assets` window, find `Talo Game Services` and click `Download`.
+
+
+
+If you want to update your package, simply repeat the same process.
+
+### Downloading from itch.io
+
+You can download the latest version of the Unity package from our [itch.io page](https://sleepystudios.itch.io/talo-unity).
+
+Once downloaded, you can open the `talo.unitypackage` file to import it into your project.
+
+## Generate an API key
+
+Visit [the Talo dashboard](https://dashboard.trytalo.com), login or create an account (and confirm your email address), and visit the API Keys page.
+
+Choose the scopes available to your API key (you'll need the `read:players` and `write:players` scope to use the package) and create your API key.
+Save your API key somewhere securely.
+
+## Create a Talo asset
+
+Once the package has been imported, right-click in your Resources folder, click `Create > Talo > Settings Asset`. Paste your newly-created API key into the API key field and you're ready to go.
+
+
+ Your settings asset must always be at the top-level in the Resources folder and named `Talo
+ Settings`.
+
+
+### Self-hosting
+
+If you're self-hosting Talo, you can configure your custom endpoint here in the Settings Asset.
+
+### Settings reference
+
+You can visit the [settings reference](/docs/pre-1.0/unity/settings-reference) for a detailed overview of Talo's configuration options.
diff --git a/content/docs/pre-1.0/unity/leaderboards.mdx b/content/docs/pre-1.0/unity/leaderboards.mdx
new file mode 100644
index 0000000..5447e41
--- /dev/null
+++ b/content/docs/pre-1.0/unity/leaderboards.mdx
@@ -0,0 +1,317 @@
+---
+description: Talo leaderboards are highly customisable - sort modes, entry uniqueness and refresh intervals can all be easily configured per-leaderboard.
+title: Leaderboards
+---
+
+import { ScopeBadges } from '@/components/documentation/ScopeBadges'
+
+## Creating leaderboards
+
+To create a leaderboard, visit [the leaderboards page](https://dashboard.trytalo.com/leaderboards) inside the Talo dashboard. Take note of the `Internal name` as this is how you'll be referring to your leaderboard.
+
+
+ Check out this blog post on [how to add leaderboards to your Unity
+ game](https://trytalo.com/blog/leaderboards-unity?utm_source=docs&utm_medium=tip) for a detailed
+ walkthrough
+
+
+## Creating entries
+
+
+
+Use `Talo.Leaderboards.AddEntry()` to create an entry:
+
+```csharp title="AddEntry.cs"
+string internalName = 'time-survived';
+float score = 300f;
+
+public async void AddEntry()
+{
+ (LeaderboardEntry entry, bool updated) = await Talo.Leaderboards.AddEntry(internalName, score);
+ Debug.Log(entry.position);
+}
+```
+
+This function returns a tuple of the entry and whether the entry was updated.
+
+Updated entries are only relevant if the leaderboard is set to unique. Leaderboard entries won't be updated if they are not better (depending on the sort mode) than the player's previous entry.
+
+## Entry props
+
+Along with a score, you can also send a dictionary of `props` with an entry. The key/value pairs will be stringified and can be used to filter entries. For example, you could send a `team` prop:
+
+```csharp
+private async void OnPostClick()
+{
+ var username = root.Q().text;
+ var score = UnityEngine.Random.Range(0, 100);
+ var team = UnityEngine.Random.Range(0, 2) == 0 ? "Blue" : "Red";
+
+ await Talo.Players.Identify("username", username);
+ (LeaderboardEntry entry, bool updated) = await Talo.Leaderboards.AddEntry(
+ leaderboardName,
+ score,
+ ("team", team)
+ );
+
+ infoLabel.text = $"You scored {score} for the {team} team.";
+ if (updated) infoLabel.text += " Your highscore was updated!";
+
+ entriesList.Rebuild();
+}
+```
+
+You could then have a function that populates the leaderboard and checks if a team filter is active:
+
+```csharp
+private void BuildEntries()
+{
+ var entries = Talo.Leaderboards.GetCachedEntries(leaderboardName);
+
+ if (filter != "All") // e.g. "Blue" or "Red"
+ {
+ entries = entries.FindAll((entry) => entry.GetProp("team", "None") == filter);
+ }
+
+ foreach (var entry in entries)
+ {
+ CreateEntry(entry);
+ }
+}
+```
+
+The code above is available in the leaderboards sample included with the Talo Unity package.
+
+### Array props
+
+To send a prop array with an entry, repeat the key with a `[]` suffix for each value:
+
+```csharp
+(LeaderboardEntry entry, bool updated) = await Talo.Leaderboards.AddEntry(
+ leaderboardName,
+ score,
+ ("equipment[]", "sword"),
+ ("equipment[]", "shield"),
+ ("equipment[]", "helmet")
+);
+```
+
+### Mixed props
+
+Array props can be mixed with scalar props in the same call:
+
+```csharp
+(LeaderboardEntry entry, bool updated) = await Talo.Leaderboards.AddEntry(
+ leaderboardName,
+ score,
+ ("team", team),
+ ("equipment[]", "sword"),
+ ("equipment[]", "shield")
+);
+```
+
+### Handling rejected entry props
+
+Talo validates entry props. If one or more props are invalid, the entry will not be saved.
+
+You can listen for rejected props using the `Talo.Leaderboards.OnPropsRejected` event:
+
+```csharp
+private void OnEnable()
+{
+ Talo.Leaderboards.OnPropsRejected += OnPropsRejected;
+}
+
+private void OnDisable()
+{
+ Talo.Leaderboards.OnPropsRejected -= OnPropsRejected;
+}
+
+private void OnPropsRejected(RejectedProp[] rejectedProps)
+{
+ foreach (var prop in rejectedProps)
+ {
+ Debug.Log($"Rejected prop '{prop.key}': {prop.message} ({prop.error})");
+ }
+}
+```
+
+See [Players and props - Prop rejection reasons](/docs/pre-1.0/unity/player-props#prop-rejection-reasons) for the full list of rejection reason codes.
+
+## Getting entries
+
+
+
+Leaderboard entries are paginated: a maximum of 50 entries come back with each request. Use `Talo.Leaderboards.GetEntries()` to retrieve an array of entries:
+
+```csharp title="GetEntries.cs"
+string internalName = 'time-survived';
+int page = 0;
+
+public async void FetchEntries()
+{
+ var res = await Talo.Leaderboards.GetEntries(internalName, new GetEntriesOptions() { page = page });
+ LeaderboardEntry[] leaderboardEntries = res.entries;
+
+ if (res.isLastPage)
+ {
+ // Don't need to continue looping
+ }
+ else
+ {
+ page++;
+ }
+}
+```
+
+`GetEntries()` returns an object with `entries`, `itemsPerPage` and `isLastPage`.
+
+### Getting entries for the current player
+
+Since a player can have many aliases, you can use the `playerId` option to fetch entries across all of a player's aliases:
+
+```csharp
+var entries = await Talo.Leaderboards.GetEntries(internalName, new GetEntriesOptions() {
+ playerId = Talo.CurrentPlayer.id
+});
+```
+
+If you want to scope entries to a specific alias instead, use the `aliasId` option:
+
+```csharp
+var entries = await Talo.Leaderboards.GetEntries(internalName, new GetEntriesOptions() {
+ aliasId = Talo.CurrentAlias.id
+});
+```
+
+You can also filter entries by the service an alias belongs to using the `aliasService` option:
+
+```csharp
+var entries = await Talo.Leaderboards.GetEntries(internalName, new GetEntriesOptions() {
+ aliasService = "steam"
+});
+```
+
+### Getting archived entries
+
+If your leaderboard uses refresh intervals (i.e. daily, weekly, monthly, yearly), you can get archived entries using the `includeArchived` option:
+
+```csharp
+var entries = await Talo.Leaderboards.GetEntries(internalName, new GetEntriesOptions() {
+ page = page,
+ includeArchived = true
+});
+```
+
+## Entry cache
+
+After fetching your leaderboard entries you can take advantage of the internal cache to construct your UI, removing the need for any subsequent network requests.
+
+You can use `Talo.Leaderboard.GetCachedEntries()` in the same way as `GetEntries()` above. Every entry fetched previously using `GetEntries()` will exist in the cache.
+
+`GetCachedEntries()` also accepts an optional `GetCachedEntriesOptions` object to filter the cached results:
+
+```csharp
+// Filter cached entries by player
+var entries = Talo.Leaderboards.GetCachedEntries(internalName, new GetCachedEntriesOptions() {
+ playerId = Talo.CurrentPlayer.id
+});
+```
+
+```csharp
+// Filter cached entries by alias
+var entries = Talo.Leaderboards.GetCachedEntries(internalName, new GetCachedEntriesOptions() {
+ aliasId = Talo.CurrentAlias.id
+});
+```
+
+```csharp
+// Filter cached entries by alias service
+var entries = Talo.Leaderboards.GetCachedEntries(internalName, new GetCachedEntriesOptions() {
+ aliasService = "steam"
+});
+```
+
+Similarly, updated results from `AddEntry()` will also be reflected in the cache - the entry returned from the response will be upserted and the positions of the other entries in the cache will be updated.
+
+### Getting entries by their props
+
+The example above assumes we've fetched all of the leaderboard entries so we can filter on them. It's generally more efficient to filter by prop keys and values when fetching leaderboard entries.
+
+The following code will only fetch leaderboard entries that have the "team" key:
+
+```csharp
+var entries = await Talo.Leaderboards.GetEntries(internalName, new GetEntriesOptions() {
+ page = page,
+ propKey = "team"
+});
+```
+
+You can also filter by a prop value. This code will now make sure there is a "team" key and its value is "Blue":
+
+```csharp
+var entries = await Talo.Leaderboards.GetEntries(internalName, new GetEntriesOptions() {
+ page = page,
+ propKey = "team",
+ propValue = "Blue"
+});
+```
+
+### Filtering by dates
+
+You can provide a `startDate` and `endDate` to `GetEntriesOptions` to filter entries by their creation date. Dates can be provided in three formats: UTC Date (YYYY-MM-DD), DateTime (ISO 8601), or millisecond timestamp:
+
+```csharp
+// Get entries created between specific dates using UTC Date format
+var entries = await Talo.Leaderboards.GetEntries(internalName, new GetEntriesOptions()
+{
+ page = page,
+ startDate = "2025-09-01",
+ endDate = "2025-09-30"
+});
+// Returns entries created between September 1-30, 2025
+```
+
+Using DateTime (ISO 8601) format:
+
+```csharp
+var entries = await Talo.Leaderboards.GetEntries(internalName, new GetEntriesOptions()
+{
+ page = page,
+ startDate = "2025-09-01T00:00:00Z",
+ endDate = "2025-09-30T23:59:59Z"
+});
+```
+
+Using millisecond timestamps:
+
+```csharp
+var entries = await Talo.Leaderboards.GetEntries(internalName, new GetEntriesOptions()
+{
+ page = page,
+ startDate = "1756684800000", // September 1, 2025 00:00:00 UTC
+ endDate = "1759276799000" // September 30, 2025 23:59:59 UTC
+});
+```
+
+You can also use just one of the date filters:
+
+```csharp
+// Get entries created on or after a specific date
+var entries = await Talo.Leaderboards.GetEntries(internalName, new GetEntriesOptions()
+{
+ page = page,
+ startDate = "2025-09-15"
+ // endDate is omitted - returns all entries created from September 15, 2025 00:00:00 UTC onwards
+});
+```
+
+```csharp
+// Get entries created on or before a specific date
+var entries = await Talo.Leaderboards.GetEntries(internalName, new GetEntriesOptions()
+{
+ page = page,
+ endDate = "2025-09-30"
+ // startDate is omitted - returns all entries created before October 1, 2025 00:00:00 UTC
+});
+```
diff --git a/content/docs/pre-1.0/unity/live-config.mdx b/content/docs/pre-1.0/unity/live-config.mdx
new file mode 100644
index 0000000..dd40485
--- /dev/null
+++ b/content/docs/pre-1.0/unity/live-config.mdx
@@ -0,0 +1,80 @@
+---
+description: Live config lets you push data to your game from the Talo dashboard without releasing a new update.
+title: Live config
+---
+
+import { ScopeBadges } from '@/components/documentation/ScopeBadges'
+
+## Getting the live config
+
+
+
+You must fetch the live config using `Talo.GameConfig.Get()` before querying its current state. This invokes the `Talo.GameConfig.OnLiveConfigLoaded` event, which returns the newly initialised config.
+
+```csharp
+void Start()
+{
+ Talo.GameConfig.OnLiveConfigLoaded += OnLiveConfigLoaded;
+ // first fetch the config
+ _ = Talo.GameConfig.Get();
+}
+
+void OnLiveConfigLoaded(TaloLiveConfig config)
+{
+ // now you can query the config
+}
+```
+
+You can fetch the game config any time to refresh the state.
+
+
+ Check out this blog post on [how to release Unity game updates without new
+ builds](https://trytalo.com/blog/live-config-unity?utm_source=docs&utm_medium=tip) for a detailed
+ walkthrough
+
+
+## Querying the live config
+
+You can query properties of the config using `Talo.LiveConfig.GetProp()`, for example if you wanted to get a boolean value:
+
+```csharp
+var halloweenEventEnabled = Talo.LiveConfig.GetProp("halloweenEventEnabled");
+```
+
+Or a number:
+
+```csharp
+var maxLevel = Talo.LiveConfig.GetProp("maxLevel");
+```
+
+This function also has a second argument which is the default value to return if the property is not found.
+
+### Offline cache
+
+If the player is offline (determined using `Talo.IsOffline()`), an offline copy of the live config will be returned instead. The cached version is updated after a successful online `Get()` call.
+
+
+ The offline cache can be `null` if the live config hasn't been successfully queried before.
+
+
+You can check when the offline config was last updated using `Talo.LiveConfig.GetOfflineConfigLastModified()` which returns a Unix timestamp.
+
+## Listening for live config updates
+
+
+
+You can listen for live config updates by connecting to the `Talo.GameConfig.OnLiveConfigUpdated` event:
+
+```csharp
+void Start()
+{
+ var textUI = GetComponent();
+
+ Talo.GameConfig.OnLiveConfigUpdated += (liveConfig) =>
+ {
+ textUI.text = liveConfig.GetProp("liveString", "Not set!");
+ };
+}
+```
+
+The `OnLiveConfigUpdated` event is invoked (via the [Talo Socket](/docs/pre-1.0/socket)) whenever the live config is updated.
diff --git a/content/docs/pre-1.0/unity/meta.json b/content/docs/pre-1.0/unity/meta.json
new file mode 100644
index 0000000..8b881cd
--- /dev/null
+++ b/content/docs/pre-1.0/unity/meta.json
@@ -0,0 +1,25 @@
+{
+ "pages": [
+ "install",
+ "settings-reference",
+ "dev-data",
+ "request-verification",
+ "continuity",
+ "identifying",
+ "player-authentication",
+ "player-props",
+ "events",
+ "leaderboards",
+ "saves",
+ "stats",
+ "groups",
+ "live-config",
+ "feedback",
+ "socket",
+ "channels",
+ "player-presence",
+ "player-relationships"
+ ],
+ "title": "Unity package",
+ "icon": "unity"
+}
diff --git a/content/docs/pre-1.0/unity/player-authentication.mdx b/content/docs/pre-1.0/unity/player-authentication.mdx
new file mode 100644
index 0000000..a5ee8af
--- /dev/null
+++ b/content/docs/pre-1.0/unity/player-authentication.mdx
@@ -0,0 +1,331 @@
+---
+description: Talo player authentication fully manages player login credentials, registrations and email verifications.
+title: Player authentication
+---
+
+import { ScopeBadges } from '@/components/documentation/ScopeBadges'
+
+
+## Included samples
+
+The "AuthenticationDemo" sample included with the Unity package shows how to:
+* Register players with a username, password and optional email.
+* Log players in with their chosen credentials.
+* Verify login requests.
+
+## Registering
+
+
+
+To register a player, you can use `Talo.PlayerAuth.Register()`. At a minimum, you need to provide a username and a password for the player.
+
+If you want the player to be able to verify their logins via email (two factor authentication), you will also need to provide an email and set the `verificationEnabled` flag to `true`.
+
+```csharp
+private async void OnRegisterClick()
+{
+ var username = root.Q("username").text;
+ var password = root.Q("password").text;
+ var enableVerification = root.Q("enable-verification").value;
+ var email = root.Q("email").text;
+
+ var validationLabel = root.Q