Overview
One SDK context for every service
Kiwi SDK is a developer preview for Unity projects. A single KiwiSdkConfig selects the SDK app and environment used by accounts, remote config, cloud saves, analytics, diagnostics, and tenant-scoped realtime services.
0.3.0 remains the package used by live products. Cross-platform identity, economy, targeted LiveOps, and competition are staged for 0.4.0-preview.1; they are not available to a live game until its backend and Unity package are deliberately upgraded.dev is separate from staging and production. The app ID and environment travel with each SDK request.Recommended integration path
- 1Create an SDK app
Open the SDK Portal, create an app, and copy its public app ID.
- 2Install the tagged package
Add the Kiwi SDK v0.3.0 Git URL and its two direct Git dependencies to the Unity project manifest.
- 3Start in dev
Initialize against
dev, authenticate a test account, and verify each service independently. - 4Promote deliberately
Publish config and validate data separately in staging before selecting production.
Getting started
Requirements and package access
The current package is com.kiwistudios.sdk version 0.3.0 and targets Unity 2022.3 or newer. The tagged developer-preview package is available from GitHub.
kiwi-sdk-v0.3.0 rather than an unversioned branch so package updates remain deliberate and reproducible.Required packages
| Package | Purpose | Declared version |
|---|---|---|
com.cysharp.unitask | Async Unity operations | 2.5.10 |
com.itisnajim.socketiounity | Realtime Socket.IO transport | 1.1.5 |
com.unity.nuget.newtonsoft-json | JSON serialization | 3.2.2 |
com.unity.modules.unitywebrequest | HTTP transport | 1.0.0 |
The dependencies are declared in the Kiwi package manifest. Unity cannot resolve transitive Git dependencies, so Git installs must also add UniTask and SocketIOUnity directly to the project manifest. Registry consumers may configure the com.cysharp and com.itisnajim OpenUPM scopes instead.
Install the tagged Git package
https://github.com/ArtemisMoysen/GDW4.git?path=Packages/com.kiwistudios.sdk#kiwi-sdk-v0.3.0
In Packages/manifest.json, add the tagged Kiwi SDK URL plus UniTask 2.5.10 and SocketIOUnity 1.1.5 as direct dependencies.
Or add the package from disk
- Put the approved
com.kiwistudios.sdkfolder in a stable local location, normally your project'sPackagesdirectory. - In Unity, open Window → Package Manager.
- Select +, then Add package from disk….
- Select
com.kiwistudios.sdk/package.json.
Packages/com.kiwistudios.sdk/package.json
Getting started
Create an app in the SDK Portal
Sign in to the SDK Portal and create one SDK app for the game. The generated ID starts with ksa_ and does not change.
Environments
Every app begins with dev, staging, and production. Remote config, cloud saves, analytics, achievements, crash data, presence, lobbies, queues, and matches are scoped by the selected app and environment. Friend relationships remain account-global.
Getting started
Initialize once at application boot
Create KiwiSdkConfig before calling any service client. The service origin is owned by the package; the public constructor accepts the app ID, environment, request timeouts, and optional session cache keys.
using KiwiStudios.Sdk;
using UnityEngine;
public sealed class KiwiSdkBootstrap : MonoBehaviour
{
private void Awake()
{
if (KiwiSdk.IsInitialized) { return; }
var _config = new KiwiSdkConfig(
"ksa_replace_with_your_app_id",
"dev",
20,
10);
KiwiSdk.Initialize(_config);
}
private void OnDestroy()
{
KiwiSdk.Shutdown();
}
}
Accounts
Authenticate, cache, then resolve the SDK session
Interactive sign-in uses LoginStep1 followed by LoginStep2. After a successful second step, call SetAuthenticated, CacheSession, and ResolveSdkSession. Resolution verifies that the signed-in account can establish the configured app-and-environment context.
RestoreSession validates cached credentials online and stores a rotated token after success. Offline identity is opt-in: it can restore a recent locally cached identity after a network failure, but only from native secure storage and never for cloud writes, realtime, or other online services.
using Cysharp.Threading.Tasks;
using KiwiStudios.Sdk;
using System.Threading;
public sealed class KiwiSessionService
{
public async UniTask<KiwiResult<KiwiAuthResponse>> Restore(CancellationToken _ct)
{
KiwiResult<KiwiAuthResponse> _session = await KiwiSdk.Auth.RestoreSession(_ct);
if (!_session.Result || !KiwiSdk.Auth.CanAccessOnlineServices)
{
return _session;
}
KiwiResult<KiwiSdkSessionResolveResponse> _resolved =
await KiwiSdk.Auth.ResolveSdkSession(_session.Data.UserId, _session.Data.Token, _ct);
if (!_resolved.Result)
{
return KiwiResult<KiwiAuthResponse>.Failure(_resolved.Error, _resolved.StatusCode);
}
return _session;
}
}
KiwiSecureSessionStore for macOS Keychain, iOS Keychain, Android Keystore, or Windows Credential Manager. Then opt in with _allowOfflineIdentity: true, set a short maximum age, and gate network features on CanAccessOnlineServices. PlayerPrefs is never accepted for offline identity.0.4 staged preview
Start anonymously, then link a verified platform
The staged identity client supports anonymous device accounts and verified Apple, Epic, Google, and Steam credentials. Provider credentials are verified by Kiwi before an identity is authenticated or linked; the player build never receives provider configuration secrets.
KiwiSdk.Identity does not exist in the live 0.3.0 package. Do not switch a live product until the provider configuration is ready, the backend is deployed, and the new package passes that product's login and account-recovery tests.string _deviceSecret = _secureCredentialStore.Load("kiwi_device_secret");
if (string.IsNullOrWhiteSpace(_deviceSecret))
{
_deviceSecret = KiwiPlatformIdentityClient.CreateAnonymousDeviceSecret();
_secureCredentialStore.Save("kiwi_device_secret", _deviceSecret);
}
KiwiResult<KiwiPlatformIdentityData> _result =
await KiwiSdk.Identity.AuthenticateAnonymous(_deviceId, _deviceSecret);
- Generate one stable random device ID and one device secret per installation. Persist the secret in native secure storage before the first authentication request.
- Use
AuthenticateProviderfor Apple, Epic, Google, or Steam sign-in, then useLinkProvideronly from an already authenticated Kiwi session. - Kiwi rejects unlinking the final recoverable identity unless the account still has a native login or an active anonymous-device credential.
- Console identity is not implemented in this preview and requires platform-holder approval and verification configuration.
Core services
Load cached values before fetching updates
Publish a JSON object from the SDK Portal. At runtime, load the encrypted local cache first, then fetch the selected environment. A successful changed response is written to the cache before its hash is committed.
using Cysharp.Threading.Tasks;
using KiwiStudios.Sdk;
using Newtonsoft.Json.Linq;
using UnityEngine;
public sealed class GameConfigLoader
{
public async UniTask Load()
{
KiwiSdk.RemoteConfig.TryLoadCache();
KiwiResult<JObject> _result = await KiwiSdk.RemoteConfig.Fetch();
if (!_result.Result)
{
Debug.LogWarning(_result.Error);
return;
}
int _dailyMatchLimit = KiwiSdk.RemoteConfig.GetInt("dailyMatchLimit", 5);
Debug.Log(_dailyMatchLimit);
}
}
Remote config is delivered to player clients. Do not publish match keys, credentials, private endpoints, or values that must remain secret.
0.4 staged preview
Layer rules and experiments over base config
LiveOps keeps the published environment document as the base, then applies active rules in priority order. Rules can match build and custom attributes, use start and end times, and roll out to a deterministic percentage of players. Experiments assign one stable weighted variant per player.
var _attributes = new KiwiLiveOpsAttributes
{
BuildId = Application.version,
Custom = new Dictionary<string, object>
{
["region"] = "na",
["skillTier"] = "gold"
}
};
KiwiResult<JObject> _result =
await KiwiSdk.RemoteConfig.FetchTargeted(_attributes);
Fetch() method continues to receive only the published base config. Updating the Unity package is required to opt into targeting, submit build or custom attributes, or inspect LastAppliedRules and LastAssignments.Targeted responses are online-only in this preview and are not written to the shared base-config cache. This prevents one signed-in player's rules or experiment variant from being restored for another account on the same device. Use gameplay defaults when a targeted fetch is unavailable.
Core services
Choose JSON data or file slots
Cloud save provides two surfaces. User data is a small JSON record for profile-shaped values. Slots are immutable file commits for larger save payloads, with a manifest and SHA-256 integrity metadata.
User data protection labels
The signed-in player can write it. Use it for data that is safe to show outside the owner account.
The signed-in player can currently write it. Treat it as owner-scoped, client-authoritative data.
Normal player writes cannot change it, but the owning player receives it when loading. It is not a secret store.
Version and pass it to SaveUserDataVersioned; stale writes return 409 cloud_save_version_conflict.using System.Collections.Generic;
using Cysharp.Threading.Tasks;
using KiwiStudios.Sdk;
public sealed class PlayerProfileSave
{
public async UniTask<KiwiResult<KiwiCloudSaveLoadResponse>> SaveAndLoad(
string _userId,
string _token)
{
var _publicData = new Dictionary<string, object>
{
["selectedTitle"] = "pathfinder"
};
var _protectedData = new Dictionary<string, object>
{
["tutorialComplete"] = true
};
KiwiResponse _save = await KiwiSdk.CloudSave.SaveUserData(
_userId,
_token,
_publicData,
_protectedData);
if (!_save.Result)
{
return KiwiResult<KiwiCloudSaveLoadResponse>.Failure(_save.Message, _save.StatusCode);
}
return await KiwiSdk.CloudSave.LoadUserData(_userId, _token);
}
}
File slots
Slot uploads commit a complete version atomically. Files are sent as base64 inside one JSON request; uploads and downloads are not streamed. Set ExpectedVersion to 0 when creating a slot or to the last observed version when replacing it. A stale upload or delete returns 409 cloud_save_version_conflict without changing the committed slot.
using System;
using System.Collections.Generic;
using System.Text;
using Cysharp.Threading.Tasks;
using KiwiStudios.Sdk;
public sealed class WorldSlotSave
{
public async UniTask<KiwiResult<KiwiCloudSaveSlotCommitResponse>> Upload(
string _userId,
string _token,
string _json,
long _lastKnownVersion)
{
var _request = new KiwiCloudSaveUploadRequest
{
SlotId = "autosave",
DisplayName = "Autosave",
ExpectedVersion = _lastKnownVersion,
Manifest = new Dictionary<string, object> { ["scene"] = "village" },
Files = new List<KiwiCloudSaveFileUploadData>
{
new KiwiCloudSaveFileUploadData
{
Path = "world/save.json",
ContentBase64 = Convert.ToBase64String(Encoding.UTF8.GetBytes(_json))
}
}
};
return await KiwiSdk.CloudSave.UploadSlot(_userId, _token, _request);
}
}
Current default slot limits
| Boundary | Default |
|---|---|
| Slots per player, app, and environment | 10 |
| Files per slot | 32 |
| Decoded bytes per file | 512 KiB |
| Decoded bytes per slot | 700 KiB |
| Total slot bytes per player | 5 MiB |
| Serialized manifest | 64 KiB |
| Slot ID length | 96 characters |
| File path length | 191 characters |
Limits are service defaults and may change during the preview. Design save formats below every boundary because base64 and JSON add request overhead.
Core services
Track lightweight achievement progress
Use the portal to describe lightweight client achievements, then call Unlock, Increment, or List from an authenticated player session. For trusted achievements, attach a rule to a statistic definition; Kiwi marks that achievement server-authoritative and only the portal or authenticated match-report statistic path can satisfy it.
KiwiResponse _response = await KiwiSdk.Achievements.Increment(
_userId,
_token,
"matches_played",
1);
0.4 staged preview
Make every balance change a transaction
The staged economy publishes versioned catalogs containing currencies, items, stores, and store entries. Player balances and inventory change through a locked transaction ledger, and virtual purchases require a player-scoped idempotency key.
KiwiResult<KiwiEconomyTransactionData> _result =
await KiwiSdk.Economy.Purchase(
_userId,
_token,
"featured",
"starter_bundle",
_purchaseAttemptId);
0.3.0 package has only the first-party inventory reader. Apple, Google, and Steam real-money purchases must stay disabled until the external verifier services and store credentials are configured and tested.0.4 staged preview
Write trusted results from the match server
Define statistic aggregation as replace, sum, maximum, or minimum. Seasonal statistics accept writes only while their season is active; leaderboards use dense ranks so tied values share a place; tournaments attach a dated competition window and catalog-backed reward ranges to a leaderboard.
KiwiSdk.Competition reads a player's statistics, leaderboard entries, and competition configuration. Stat updates come from the portal or the already authorized one-time match report, not from a player-write API.KiwiResult<KiwiLeaderboardData> _result =
await KiwiSdk.Competition.ReadLeaderboard(
_userId,
_token,
"ranked_wins",
100);
For an authorized match player, add KiwiStatistics to that player's game-data entry. Each item contains StatId, Value, and an optional SeasonId. Existing reports that omit the field keep their current behavior.
KiwiSdk.Competition or submit typed statistic fields.Core services
Send intentional telemetry
Analytics can send immediately or persist a local queue. The queue holds up to 500 events and flushes at most 100 per request. The server returns an accepted count rather than per-event IDs; the client removes that many events from the front of the submitted batch. Failed and unaccepted events remain queued for retry.
The SDK Portal provides app-scoped Dashboards, Events, and Exports views. Dashboard layouts are shared across dev, staging, and production; the environment selector changes only the data being queried. App owners can combine up to four safe measures with count, distinct count, sum, average, minimum, maximum, and source-defined calculations such as match win rate. The builder never accepts SQL, JavaScript, arbitrary joins, or custom formulas.
Scalar event payload fields may be registered to a maximum object depth of four. Arrays, mixed-type fields, and deeper objects remain visible in the raw Events inspector, but they cannot be aggregated in the first dashboard release. Server receipt time in UTC is canonical for trends; client time remains display and export metadata. Events can be narrowed by dashboard range, event, schema, build, player, and an exact UTC received-time window, then advanced in 50-row cursor pages.
A panel may include up to four measures and two grouping dimensions. Metric, line, stacked bar, and table displays can show multiple measures; other visualizations require one measure so their encoding remains unambiguous. Registered fields that do not exist in the selected environment remain visible as unavailable rather than being removed from the app-scoped dashboard definition.
Shared dashboards belong to the app; personal dashboards are visible only to their creator. Either type can be selected as that operator's default, and a shared dashboard can be duplicated into a personal copy. Saves use revision checks: 409 analytics_definition_conflict lets the operator reload the saved revision or keep the draft as a new personal dashboard.
Dashboard definitions can be exported and imported as app-layout JSON without moving environment data. Data exports support CSV and JSON for a filtered panel, generic events, and the six match-report datasets. They are capped at 50,000 rows; operators may use the dashboard range or the full retained range.
Crash reports and logs are manual API calls. The package does not automatically subscribe to Unity log callbacks or unhandled exceptions.
var _payload = new Dictionary<string, object>
{
["mode"] = "practice",
["durationSeconds"] = 84
};
KiwiSdk.Analytics.QueueEvent("match_completed", _payload, "match.v1");
KiwiResponse _flush = await KiwiSdk.Analytics.FlushQueuedEvents(
_userId,
_token,
Application.version);
KiwiSdk.Analytics. Its dashboard reads match players, damage, healing, deaths, and distinct matches. Its raw Events view may therefore be empty, and match history is not copied into generic events.Realtime
First-party multiplayer foundations
The current realtime layer exposes low-level Socket.IO subscriptions plus convenience emitters for friends, presence, lobbies, and matchmaking. It is shaped by Kiwi Studios' first-party integration, not yet a general-purpose multiplayer contract.
- Subscribe to events before calling
ConnectAndIdentify. - Friends are account-global. Presence, lobbies, queue state, and matches are scoped to the configured app and environment.
- Multiple handlers may subscribe to the same event. Use
Off(eventName, handler)to remove one orOff(eventName)to remove all. ConnectAndIdentifyreturns aKiwiResponse, accepts cancellation, and uses the configured authentication timeout.- Reconnects emit state callbacks and automatically re-identify with the last supplied session. Dispose the client during SDK shutdown.
- Use
JObjectfor presence and lobby payloads in new integrations. Current convenience DTOs do not cover every live backend shape.
using Cysharp.Threading.Tasks;
using KiwiStudios.Sdk;
using Newtonsoft.Json.Linq;
using System.Threading;
using UnityEngine;
public sealed class KiwiRealtimeBootstrap
{
public async UniTask Connect(string _userId, string _token, CancellationToken _ct)
{
KiwiSdk.Realtime.OnAuthFailed += _error => Debug.LogError(_error);
KiwiSdk.Realtime.On("lobby:members_updated", _json =>
{
JObject _lobby = KiwiRealtimeClient.DeserializePayload<JObject>(_json);
Debug.Log(_lobby);
});
KiwiResponse _response = await KiwiSdk.Realtime.ConnectAndIdentify(_userId, _token, _ct);
if (!_response.Result)
{
Debug.LogError(_response.Message);
}
}
}
Queue options, game-mode fields, ranked rules, allocation payloads, and several response shapes still reflect the current first-party integration. Validate the exact wire payloads in a test environment before building reusable gameplay UI around them.
Trusted server
Consume the one-time match key once
The allocated match supplies a match ID and one-time match authentication key. The report must match the allocated app and environment and arrive while the match is running. Duplicate player IDs are rejected.
var _report = new KiwiMatchReportPayload
{
MatchId = _matchId,
MatchAuthKey = _matchAuthKey,
GameMode = _gameMode,
Map = _map,
AnalyticsData = _analyticsData,
GameData = _gameData
};
KiwiResponse _response = await KiwiSdk.MatchReport.SendReport(_report);
The package writes SdkAppId and Environment from the active config before sending. Match and player payload models remain tied to the current first-party allocation contract.
Backend and portal
Operate each app and environment as a data boundary
The SDK Portal can export or delete one player's app-scoped data, set telemetry retention, apply retention immediately, create encrypted logical snapshots, restore an exact snapshot with explicit confirmation, and track public or internal incidents. Account settings also provide account-wide export and deletion for players who do not own SDK apps.
RESTORE:<snapshot-id>.cleanup_pending privacy request or restored_cleanup_pending restore.These controls are portal and backend features. They do not require a Unity package update and do not change live RIVE request payloads.
Release boundary
Deploy backend capability before opting a game into 0.4
- 1Keep RIVE pinned
Leave the live game on
kiwi-sdk-v0.3.0. Do not replace that tag or publish0.4.0-preview.1under a stable version. - 2Configure backend dependencies
Set the backup key, identity provider audiences and credentials, receipt-verifier endpoints, and monitoring before enabling the corresponding product surface.
- 3Deploy additive backend routes
Existing authentication, cloud-save, config, and match-report payloads remain accepted. Keep economy purchase entry points disabled until receipt validation is ready.
- 4Test 0.4 in a separate RIVE branch
Verify login recovery, anonymous-secret persistence, catalog purchases, targeted config, leaderboard reads, and legacy match reports in
devbefore updating the game. - 5Publish and promote deliberately
Create a new immutable package tag only after the backend and game checks pass, then promote the game through staging before production.
Reference
Check the result before reading data
Write-style calls return KiwiResponse. Typed reads return KiwiResult<T>. A failed response is data, not an exception contract: inspect Result, retain StatusCode, and show a product-safe message.
KiwiResult<KiwiCloudSaveLoadResponse> _result =
await KiwiSdk.CloudSave.LoadUserData(_userId, _token);
if (!_result.Result)
{
Debug.LogWarning($"cloud save failed ({_result.StatusCode}): {_result.Error}");
return;
}
KiwiCloudSaveLoadResponse _save = _result.Data;
The package retries transient failures only for retry-safe reads, using bounded exponential backoff. Authentication, analytics, increments, uploads, match reports, and other side-effecting writes are not retried automatically. Treat validation failures, quota errors, cloud-save 409 conflicts, and consumed match keys as state that requires a different action.
Reference
Know which side is authoritative
| Value or surface | Boundary |
|---|---|
| SDK app ID and environment | Public client configuration; together they select the tenant boundary. |
| Player session token | Sensitive account credential. KiwiSecureSessionStore uses native platform credential storage; PlayerPrefs remains compatibility-only. |
| Match authentication key | One-time game-server credential for one allocated match report. |
| Protected user data | Client-writable in the current Kiwi contract. |
| Private user data | Not player-writable through the normal endpoint, but returned to its owning player. |
| Achievements | Client-authoritative; unsuitable for trusted rewards. |
| Economy | Server-authoritative transaction ledger in the staged 0.4 preview. Real-money entry points require configured receipt verification. |
| Competition writes | Portal or allocated match-server authority only. Player clients receive read APIs. |
| Anonymous device secret | Installation credential. Persist it only through native secure storage and never submit it to telemetry. |
- Never put secrets in remote config, analytics, logs, crash payloads, achievements, or cloud save.
- Validate game-impacting values on a trusted server before granting competitive or paid outcomes.
- Use separate apps for separate games and separate environments for the delivery lifecycle.
- Collect only the player data your game needs and disclose telemetry in your player-facing privacy materials.
Reference
Available and staged client surfaces
| Client | Current use | Status |
|---|---|---|
Auth | Kiwi account registration, two-step login, rotating session restore, and opt-in offline identity | Preview |
RemoteConfig | Published environment JSON with encrypted local cache | Preview |
CloudSave | Shallow-merged user data and version-checked bounded file slots | Preview |
Analytics / Crash | Explicit events, queued batches, manual crash reports, and logs | Preview |
Achievements | Client progress for lightweight goals and server-authoritative statistic rules for competitive goals | Mixed authority |
Realtime / Friends / Presence / Lobby / Matchmaker | Socket.IO and first-party multiplayer foundations | First-party preview |
MatchReport | One-time authenticated report from an allocated game server | First-party preview |
Inventory | Kiwi wallet and inventory reads | First-party only |
Identity | Anonymous/device, Apple, Epic, Google, Steam, and identity linking | 0.4 staged |
Economy | Catalog, balances, inventory, virtual purchases, receipt purchases, and transaction history | 0.4 staged |
Competition | Player statistics, leaderboard reads, seasons, and tournaments | 0.4 staged |
RemoteConfig.FetchTargeted | Build and custom targeting attributes plus rule and experiment metadata | 0.4 staged |
Preview APIs, quotas, payloads, and availability can change before a stable release. Pin the package version and verify release notes before updating a project.
Support
Bring an app ID and a reproducible case
For package access or integration help, include the SDK version, Unity version, app ID, environment, failing client method, status code, and a redacted response. Never send a session token or match key.
Available package 0.3.0 · Staged package 0.4.0-preview.1 · Updated July 13, 2026