Sync Tools
The Network Storage library includes a built-in editor tool for
# Sync Tools
The Network Storage library includes a built-in editor tool for
managing your project's public endpoints, internal reusable flows,
collections, and game configuration directly from the s&box editor.
YAML Source is the current standard for synced endpoint, workflow,
and collection definitions. Prefer `.yml` filenames for new YAML
Source files. `.yaml` remains accepted and treated the same. Use
YAML Source for new synced definitions.
## Opening the Sync Tool
**Editor → Network Storage → Sync Tool**
Or click the **Sync Tool** menu item — it opens a dockable window showing all your local data files with push/pull controls.
## Editor Menu
The Network Storage library adds several windows to the s&box editor:
| Menu Item | Description |
|-----------|-------------|
| **Editor → Network Storage → Setup** | Configure Project ID, Public Key, and Secret Key |
| **Editor → Network Storage → Sync Tool** | Push/pull endpoints, collections, and workflows |
| **Editor → Network Storage → Settings** | Toggle proxy mode, configure project settings |
| **Editor → Network Storage → Generate Code** | Generate strongly-typed C# from your schemas |
## Setup
First time? Click the **Setup** button in the toolbar:
- **Project ID** selects which project the editor should connect to
- **Public API Key** identifies the runtime project that owns the data
- **Secret Key** authorizes sync changes to collections, endpoints, workflows, and other management resources
- The secret key is editor-only and never gets exported to published game files
1. Enter your **Project ID** from the sboxcool.com dashboard
2. Enter your **Public API Key** (`sbox_ns_...`)
3. Enter your **Secret Key** (`sbox_sk_...`, 128 characters) — editor only, never ships. The key field is masked by default; click **Show** to reveal it.
4. Click **Save Configuration**
5. Click **Test Connection** to verify all three credentials
Credentials are stored in `Editor/Network Storage/config/` (the secret file is gitignored and never published).
> **Key upgrade notice**: If you created your secret key before March 2026, it uses the old format and will stop working. Generate a new key from the [dashboard](/tools/network-storage) — the Test Connection button will tell you if your key needs upgrading. The `KEY_UPGRADE_REQUIRED` error code indicates this condition.
> **Key validation**: The Setup window shows key permissions after a successful test (for example `Endpoints: RWX | Collections: RWX | Workflows: RW`). Keys without standard prefixes (`sbox_ns_` / `sbox_sk_`) are accepted with an orange warning.
### Key Permissions
Secret keys can be scoped with permissions.
- **Read** and **Write** are management permissions used by the Sync Tool and CI/CD to pull or push definitions.
- **Execute** is runtime permission used by dedicated servers/backends to call secret-gated endpoints, run secret-gated queries, or use the direct Collections HTTP API.
For the sync tool to work, your key needs at minimum **Read+Write** access on:
- **Endpoints** (push/pull endpoint definitions)
- **Collections** (push/pull collection schemas)
- **Workflows** (push/pull workflow definitions)
For hosted server runtime calls, create a separate key with only the needed **Execute** permissions. Dedicated-server calls authenticated with a secret key do not need s&box auth tokens. You can configure permissions when creating a key or edit them later from the API Keys page on the dashboard.
## Directory Structure
All sync data lives in your project's `Editor/` folder (excluded from publishing):
```
Editor/Network Storage/
config/
public/projectConfig.json # Project ID, public key, base URL
secret/secret_key.json # Secret key only (gitignored)
endpoints/
load-player.endpoint.yml # One YAML Source file per endpoint
mine-ore.endpoint.yml
sell-ore.endpoint.yml
buy-upgrade.endpoint.yml
workflows/
validate-purchase.workflow.yml
collections/
players.collection.yml # Player data collection + schema
game_values.collection.yml # Game config (constants + tables)
```
## Checking for Changes
Click **Check for Updates** to compare your local files against the server:
| Status | Meaning |
|--------|---------|
| ✓ Synced | Local matches remote |
| ▲ Local, not pushed | Exists locally but not on server |
| ▼ Remote only | Exists on server but not locally |
| ● Changed | Both exist but content differs |
Each changed item shows a breakdown: cosmetic changes (name, description) vs structural changes (steps, input schema, response).
## Pushing Changes
- **Push All** — runs a backend preflight validation, then pushes all endpoints and collections only if validation passes
- **Push** (per-item) — pushes just that endpoint or collection
- If the remote version is newer, you'll see an **Overwrite/Cancel** confirmation
Preflight validation uses `POST /v3/manage/:projectId/sync/preflight` and never writes resources. It returns the same per-resource diagnostics as push, so errors such as `LEGACY_FLATTEN_FAILED` are shown before the tool changes the remote project. For `LEGACY_FLATTEN_FAILED`, make sure every top-level and nested endpoint step has a non-empty string `id` (for example `steps[0].id`).
New collections that don't exist on the server are created automatically on push. Removing a local collection source file does **not** delete the server collection; delete collections from the website dashboard so the required two-step verification can run.
## Error Cards and Retry
Sync errors are shown as readable cards grouped by **endpoints**, **collections**, **workflows**, and **sync**. Each card includes severity, what happened, how to fix it, affected paths, copy buttons for summaries/raw JSON/agent prompts, and retry actions for the affected resource. Verification mismatches include a diff action and split schema differences from metadata differences so harmless metadata drift is easier to resolve.
## Backend Source Upgrade Flow
Every push goes through a backend compile-and-upgrade pass before the definition is saved. Treat the website/backend compiler as the source-of-truth normalizer for modern flow features.
- The backend accepts both **flat top-level YAML** and legacy **`definition:` wrapper YAML** for upgrade input.
- It canonicalizes modern flow features such as `exposure`, `routes.true` / `routes.false`, `sleep`, `retry`, `goto`, `run`, and legacy `onFail` aliases.
- Route validation runs against the current project flows, so missing reusable-flow targets or unsafe loops are rejected before the server saves the definition.
- Successful upgrades are written back as flat canonical YAML.
- When backend diagnostics are clean, the response is marked **safe auto-write** and the editor can replace your local file automatically with the upgraded source.
- Any warning, error, or ambiguous top-level field disables safe auto-write. In that case, keep the existing local file, review the diagnostics/diff, and merge manually.
Safe auto-write is for normalization only. If the backend warns that a source field is ambiguous or unrecognized, fix the source yourself instead of relying on an automatic rewrite.
## Pulling from Server
- **Pull** buttons appear next to items that differ from the server
- Click **Pull** to download the server version to your local file
- Confirmation dialog warns before overwriting local files
- **Merge Meta** button (collections only) — pulls metadata (description, accessMode, rateLimits) without touching your schema
## Viewing Diffs
Click **View Diff** on any changed item to open a side-by-side diff window:
- Left = your local file, Right = server version
- Color-coded: yellow = changed, green = added, red = removed
- Scroll with arrow keys, Page Up/Down, or click-drag
## Endpoint File Format
Each new endpoint should be a YAML Source file with the full endpoint definition:
```yml
sourceVersion: "1"
kind: endpoint
slug: mine-ore
name: Mine Ore
description: Report mined ore. Validates PhaserDex tier, awards XP.
enabled: true
method: POST
input:
type: object
properties:
ore_id:
type: string
kg:
type: number
min: 0.1
required:
- ore_id
- kg
steps:
- id: player
type: read
collection: players
key: "{{playerKey}}"
- id: ore
type: lookup
source: values
table: ore_types
where:
field: ore_id
op: "=="
value: "{{input.ore_id}}"
- id: xp_reward
type: transform
expression: "round({{ore.xp_per_kg}} * {{input.kg}})"
- id: save
type: write
collection: players
key: "{{playerKey}}"
ops:
- op: inc
path: xp
value: "{{xp_reward}}"
- op: inc
path: "ores.{{input.ore_id}}"
value: "{{input.kg}}"
response:
status: 200
body:
xp: "{{player.xp}}"
level: "{{level}}"
```
## Collection File Format
Each new collection file should use YAML Source and include the full collection config, schema, and optionally constants/tables for game values:
```yml
sourceVersion: "1"
kind: collection
name: players
description: Player progression and inventory
collectionType: per-steamid
accessMode: endpoint
maxRecords: 1
schema:
type: object
properties:
xp:
type: number
min: 0
_ledger: true
gold:
type: number
min: 0
_ledger: true
```
### Game Values as Collections
Game constants (XP per level, prices, etc.) and data tables (ore types, shop items) are stored as `constants` and `tables` on a collection:
```yml
name: game_values
collectionType: global
accessMode: endpoint
constants:
- id: progression
name: Progression
entries:
xp_per_level: 1000
max_level: 50
tables:
- id: ore_types
name: Ore Types
columns:
- id: ore_id
type: string
- id: value
type: number
rows:
- ore_id: copper
value: 5
```
Endpoints reference these via `{{values.progression.xp_per_level}}` and `source: "values", table: "ore_types"` — the server merges collection constants/tables into the endpoint execution context automatically.
## Post-Push Verification
After pushing endpoints or collections, the sync tool automatically re-fetches the remote version and compares it to your local file. Each log entry shows a status:
| Status | Meaning |
|--------|---------|
| Pushed | File was sent to the server |
| Verified ✓ | Remote matches local — push was clean |
| Mismatch ▲ | Remote differs from local — see diff |
Mismatches happen when the server adds default fields (e.g. `version`, `collectionType`, `rateLimits`) that weren't in your local file. Use the **Merge View** window to review and merge these differences.
## Merge View
When a push results in server-added fields, the **Merge View** window appears automatically. It shows:
- **Added fields** — fields present on the server but not in your local file
- **Changed fields** — fields with different values between local and remote
The merge view recognizes known server defaults (like `version`, `collectionType`, `rateLimits`, `maxRecords`, `schema`) and classifies diffs that contain only these as "defaults only" — safe to merge without review.
Click **Merge Changes** to apply server-added fields to your local file, or **Cancel** to keep your version.
## Code Generator
**Editor → Network Storage → Generate Code**
Generates strongly-typed C# classes from your project's schemas, so you get compile-time safety and IntelliSense instead of magic strings.
### Generated Files
Files are written to `Code/Data/NetworkStorage/`:
| File | Contents |
|------|----------|
| `autoGenerated_Config.cs` | `NSConfig` static class with `ProjectId`, `PublicKey`, `BaseUrl`, `ApiVersion`, `ProxyEnabled` |
| `autoGenerated_Collections.cs` | Collection schema classes with nested field paths |
| `autoGenerated_Endpoints.cs` | `NSEndpoints` static class with slug constants + `Input` nested classes |
| `autoGenerated_Workflows.cs` | `NSWorkflows` static class with workflow ID constants |
| `autoGenerated_Readme.cs` | Usage documentation |
### NSConfig Fallback
When `network-storage.credentials.json` isn't found (published games, non-host clients), the library automatically falls back to the auto-generated `NSConfig` class via `TypeLibrary.GetType("NSConfig")`. This means published games don't need the credentials JSON — the code generator provides the config at compile time.
## Parallel Operations
The sync tool fetches endpoints, collections, and workflows **in parallel** for faster check and push operations. Push operations are also parallelized, significantly reducing sync time for projects with many resources.
## Management API Authentication
The Sync Tool communicates with the Management API through the library's editor tooling. The Project ID, public key, and secret key are saved by **Editor > Network Storage > Setup**. The secret key stays editor-only and is never shipped with your game.
### Sync Tool Operations
| Operation | Required permission | Description |
|-----------|---------------------|-------------|
| Pull endpoints | `endpoints: r` | Fetch endpoint YAML Source definitions |
| Push endpoints | `endpoints: rw` | Upload endpoint YAML Source definitions |
| Pull collections | `collections: r` | Fetch collection schemas and settings |
| Push collections | `collections: rw` | Upload collection schemas and settings |
| Pull workflows | `workflows: r` | Fetch workflow YAML Source definitions |
| Push workflows | `workflows: rw` | Upload workflow YAML Source definitions |
| Pull game values | `game_values: r` | Fetch constants and lookup tables |
| Push game values | `game_values: rw` | Upload constants and lookup tables |
| Pull rate limits | `rate_limits: r` | Fetch rate limit rules |
| Push rate limits | `rate_limits: rw` | Upload rate limit rules |
| Project settings | `settings: rw` | Sync runtime settings such as auth sessions and encrypted requests |
| Validate credentials | none | Test the configured key pair |
| Destroy key pair | none | Remove a generated key pair from the project |
### Authentication Error Codes
| Code | Meaning |
|------|---------|
| `MISSING_SECRET_KEY` | `x-api-key` header missing or does not start with `sbox_sk_` |
| `MISSING_PUBLIC_KEY` | `x-public-key` header missing or does not start with `sbox_ns_` |
| `INVALID_SECRET_KEY` | Secret key not recognized or does not belong to this project |
| `INVALID_PUBLIC_KEY` | Public key not recognized or does not belong to this project |
| `PROJECT_MISMATCH` | Secret key belongs to a different project than the one in the URL |
| `KEY_DISABLED` | The API key has been disabled on the dashboard |
| `KEY_UPGRADE_REQUIRED` | Secret key uses the old format -- generate a new one from the dashboard |
| `FORBIDDEN` | Key lacks the required permission scope for this operation |
| `PROJECT_NOT_FOUND` | No project found with the given ID |
Error responses include an `expected` object showing the required headers:
```yml
ok: false
error: MISSING_SECRET_KEY
message: Missing or invalid x-api-key header. Must be a secret key (sbox_sk_*).
expected:
headers:
x-api-key: sbox_sk_... (128-character secret key)
x-public-key: sbox_ns_... (public API key)
```
### Key Permissions Reference
Secret keys can be scoped to limit access. Permission letters are additive:
| Level | Meaning |
|-------|---------|
| `none` | No access |
| `r` | Read sync/management access |
| `rw` | Read and write sync/management access |
| `x` | Execute runtime access |
| `rx`, `rwx` | Combined access |
| Scope | Controls |
|-------|----------|
| `endpoints` | Read/write endpoint definitions; execute secret-gated endpoints |
| `queries` | Execute secret-gated queries |
| `collections` | Read/write collection schemas; execute direct collection data HTTP API |
| `workflows` | Validation workflow definitions |
| `game_values` | Constants and lookup tables |
| `rate_limits` | Rate limit rules |
| `settings` | Project runtime settings |
A key with `null` permissions has full access to all scopes.
Collection definitions cannot be deleted through the Sync Tool or Management API. Delete collection definitions from the website dashboard, where the two-step verification flow is enforced. Deleting individual collection records through the data API is supported for public/client calls when record deletion is enabled; dedicated secret keys with `collections: x` may delete individual rows/documents for diagnostics and backoffice cleanup.