Collections HTTP API
Use the Collections HTTP API when a dedicated server or backend service needs to read, insert, update, or delete collection data directly. For normal player-fac...
# Collections HTTP API
Use the Collections HTTP API when a dedicated server or backend service needs to read, insert, update, or delete collection data directly. For normal player-facing gameplay, prefer server-authoritative endpoints. Direct collection writes are powerful and should be kept server-side.
## Authentication
Every request includes the project ID in the URL and a Network Storage secret key. Public keys cannot call direct collection APIs; game clients should use endpoints or queries.
Dedicated-server or backend access uses a secret key with **Collections: Execute** permission:
```http
x-api-key: sbox_sk_YOUR_SECRET_KEY
```
Secret keys with **Collections: Execute** can access collections through this API. Read/write key permissions are for the Sync Tool and Management API; execute is the runtime permission for hosted servers and backend systems.
Secret-key collection API calls are treated as trusted dedicated-server/backend authentication and s&box auth tokens are not required.
## Per-player collection records
Base URL:
```text
https://api.sboxcool.com/v3/storage/{projectId}/{collectionId}/{key}
```
`collectionId` may be the collection id or collection name from the dashboard/YAML Source. `key` is usually a Steam ID, a `playerKey`, or a save-slot key such as `{steamId}_{recordId}`.
### Read a record
```http
GET /v3/storage/{projectId}/{collectionId}/{key}
x-api-key: sbox_sk_...
```
Response is the saved JSON document.
### Insert or replace a record
```http
POST /v3/storage/{projectId}/{collectionId}/{key}
content-type: application/json
x-api-key: sbox_sk_...
```
Body shape, shown as YAML here for documentation (send the equivalent JSON object at runtime):
```yml
playerName: Ada
xp: 1200
gold: 45
```
This creates the row when it does not exist, or replaces the full document when it already exists. The final document validates against the collection schema, applies save-version protection when enabled, updates unique indexes, writes ledger entries for ledger-tracked fields, and returns the saved document.
### Patch with operations
Send an operations body instead of a full document:
```http
POST /v3/storage/{projectId}/{collectionId}/{key}
content-type: application/json
x-api-key: sbox_sk_...
```
Body shape, shown as YAML here for documentation (send the equivalent JSON object at runtime):
```yml
ops:
- op: inc
path: xp
value: 50
source: dedicated-server
reason: Match reward
- op: set
path: lastMatchId
value: match_123
```
Operations are applied to the existing document, then the final document is schema-validated and saved. Use this for edits/patches when you do not want to send the entire document.
### Delete a record document
```http
DELETE /v3/storage/{projectId}/{collectionId}/{key}
x-api-key: sbox_sk_...
```
For public-key/client calls, the collection must have record deletion enabled. Secret keys with **Collections: Execute** can delete rows/documents for dedicated-server diagnostics and backoffice cleanup even when public record deletion is disabled. This removes the saved JSON document, ledger file, unique-index claims, and the save-slot index entry when the key matches a save-slot record.
This deletes a row/record only. Collection definitions themselves can only be deleted from the website dashboard, where two-step verification is enforced.
## Save-slot helper routes
For collections with multiple records per player:
```http
GET /v3/storage/{projectId}/{collectionId}/{steamId}/records
POST /v3/storage/{projectId}/{collectionId}/{steamId}/records
PATCH /v3/storage/{projectId}/{collectionId}/{steamId}/records/{recordId}
DELETE /v3/storage/{projectId}/{collectionId}/{steamId}/records/{recordId}
```
These list, create, rename, and delete save-slot records. `DeleteRecord` deletes an individual save slot and its document data; it does not delete the collection definition.
## Global collections
Global collections use append/list/get routes:
```http
POST /v3/storage/{projectId}/{collectionId}/append
GET /v3/storage/{projectId}/{collectionId}/list?limit=50
GET /v3/storage/{projectId}/{collectionId}/record/{recordId}
```
`append` validates and stores a new global record with server-managed `_id`, `_timestamp`, and `_writerId` fields.
## Official s&box library helpers
The official library wraps the HTTP routes above:
```csharp
// Read a document. If documentId is omitted, the current player's Steam ID is used.
var player = await NetworkStorage.GetDocument( "players", "76561198000000001" );
// Create or replace a full document.
await NetworkStorage.SaveDocument( "players", "76561198000000001", new
{
playerName = "Ada",
xp = 1200,
gold = 45
} );
// Edit an existing document with server-side operations.
await NetworkStorage.UpdateDocument( "players", "76561198000000001",
NetworkStorageOperation.Increment( "xp", 50, source: "dedicated-server", reason: "Match reward" ),
NetworkStorageOperation.Set( "lastMatchId", "match_123" ) );
// Delete one row/document.
await NetworkStorage.DeleteDocument( "players", "76561198000000001" );
```
Save-slot helper methods:
```csharp
var records = await NetworkStorage.ListRecords( "saves", steamId );
var created = await NetworkStorage.CreateRecord( "saves", steamId, "Slot 1" );
await NetworkStorage.RenameRecord( "saves", recordId, "Renamed Slot", steamId );
await NetworkStorage.DeleteRecord( "saves", recordId, steamId );
```
Dedicated servers load the runtime secret from `+network_storage_secret_key sbox_sk_...`; see [Dedicated Server Runtime](/wiki/network-storage-v3/dedicated-server-runtime).
## Errors
Common errors:
| Code | Meaning |
|------|---------|
| `UNAUTHORIZED` | Missing or invalid key. |
| `FORBIDDEN` | Secret key does not have Collections: Execute permission. |
| `ENDPOINT_ONLY` | Public key tried to access an endpoint-controlled collection directly. Use a secret key with Collections: Execute or route through an endpoint. |
| `SCHEMA_VALIDATION_FAILED` | The final document does not match the collection schema. |
| `RECORD_DELETE_DISABLED` | Public/client delete attempted on a collection that does not allow record deletion. Dedicated secret keys with `collections: x` can delete rows for server/backoffice cleanup. |
| `UNIQUE_CONFLICT` | A `_unique` field value is already claimed by another record. |
| `STALE_SAVE` | Save-version protection rejected an outdated save. |