Magicweave: Cloud Store
This guide is for game developers storing structured JSON data scoped to your MagicWeave project.
The client_documents module (Cloud Store) gives you:
- upsert JSON at a fixed document path
- create documents with server-generated IDs under a collection
- read documents and discover nested collection names
- shallow-merge updates into existing document data
- delete individual documents
- list direct child documents under a collection
- query documents in a collection by JSON field filters
This is a JSON document store (Firestore-style paths), not binary file or blob upload storage. It is separate from the Player Store (/store), which handles gem redemption and catalog items.
Base URL and Route Prefix
Base URL:
https://mw-client-api.wonderfulcoast-218d579d.centralindia.azurecontainerapps.io/
Document routes are under:
/documents
Examples:
PUT https://mw-client-api.wonderfulcoast-218d579d.centralindia.azurecontainerapps.io/documents/docs/players/42/progressGET https://mw-client-api.wonderfulcoast-218d579d.centralindia.azurecontainerapps.io/documents/collections/users?parent_path=
Required Authentication and Headers
All document endpoints require:
x-client-id: <your_project_client_id>x-client-secret: <your_project_client_secret>Authorization: Bearer <magicweave_access_token>Content-Type: application/json(forPUT,POST, andPATCHwith a body)
The authenticated user must have the client.documents permission.
The default player role includes client.documents, so signed-in players can use these endpoints after normal client auth signup/login.
Project scope comes from client headers: every operation is limited to the project matching x-client-id / x-client-secret. Documents from other projects are never visible.
Document Paths (Firestore-style)
Paths are slash-separated strings. Leading and trailing slashes are stripped; empty segments are collapsed.
Document vs collection
- A document path has an even number of segments (collection/id pairs).
- A collection name is a single path segment that groups documents at the next level.
Examples:
users/alice ← document (2 segments)
users/alice/inventory/item-uuid ← document (4 segments)
Hierarchy mental model:
users/{userId} ← document
users/{userId}/inventory/{itemId} ← document under parent document
Rules enforced by the API
PUT,GET,PATCH, andDELETEon/docs/{path}require{path}to be a document path (even segment count). Odd segment counts return400with"Path must point to a document (even number of segments)".parent_pathonPOST /collections/{collection}andGET /collections/{collection}must be empty (root) or a document path (even segments). Invalidparent_pathreturns400with"parent_path must point to a document (even number of segments)".- Paths are normalized before storage (e.g.
/users/alice/→users/alice).
Nested collections on read
GET /docs/{path} includes a collections array: sorted unique collection names that exist one level below that document, derived from all paths in the project (Firestore-style discovery).
Delete behavior
DELETE /docs/{path} removes only that document row. It does not cascade to documents under nested paths. Delete child documents explicitly if you use deep paths.
Response Models
DocumentResponse
Used by PUT, POST, GET, and PATCH.
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"path": "players/42/progress",
"data": { "level": 10, "xp": 4500 },
"created": true,
"collections": []
}
| Field | Notes |
|---|---|
id | UUID of the stored document |
path | Normalized path after write/read |
data | JSON object payload |
created | Present on PUT upsert and POST create: true if inserted, false if updated (PUT only) |
collections | Populated on GET only; child collection names at this path |
DocumentListResponse
Used by GET /collections/{collection} and GET /query/{collection}.
{
"documents": [
{
"id": "550e8400-e29b-41d4-a716-446655440001",
"path": "users/alice",
"data": { "display_name": "Alice" }
}
]
}
Delete response
{
"deleted": "players/42/progress"
}
Endpoint 1: Upsert Document at Path
PUT /documents/docs/{path}
Creates or replaces the document at {path}. {path} may contain multiple segments (greedy path parameter).
Request body:
{
"data": {
"level": 10,
"xp": 4500
}
}
data defaults to {} if omitted.
Response shape:
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"path": "players/42/progress",
"data": { "level": 10, "xp": 4500 },
"created": true
}
Behavior:
- Path is normalized; collection name is derived from the path (second-to-last segment).
- Uses upsert: existing row at
(project, path)is updated; otherwise inserted. created: trueon insert,created: falseon update.- Invalid document path (odd segment count) →
400.
Reference: Upsert document at path
Endpoint 2: Create Document with Auto ID
POST /documents/collections/{collection}
Creates a new document under {collection} with a server-generated UUID as the document id segment.
Request body:
{
"data": { "display_name": "Alice", "level": 1 },
"parent_path": ""
}
| Field | Notes |
|---|---|
data | JSON object to store; defaults to {} |
parent_path | Optional. Empty string = root collection. Otherwise must be a document path (even segments), e.g. users/alice |
Response shape:
{
"id": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
"path": "users/7c9e6679-7425-40de-944b-e07fc1f90ae7",
"data": { "display_name": "Alice", "level": 1 },
"created": true
}
Behavior:
- Generated path:
{parent_path}/{collection}/{uuid}whenparent_pathis set, else{collection}/{uuid}. - Always inserts a new row;
createdis alwaystrue. - Invalid
parent_path→400.
Example with parent:
{
"data": { "item": "sword", "qty": 1 },
"parent_path": "users/alice"
}
→ path like users/alice/inventory/<uuid>.
Reference: Create document with auto ID
Endpoint 3: Get Document
GET /documents/docs/{path}
Returns one document by path.
Response shape:
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"path": "players/42/progress",
"data": { "level": 10, "xp": 4500 },
"collections": ["inventory", "achievements"]
}
Behavior:
- Missing document →
404"Document not found". collectionslists child collection names at this document (may be empty).
Reference: Get document
Endpoint 4: List Collection Documents
GET /documents/collections/{collection}
Lists direct child documents in {collection} under an optional parent.
Query parameters:
| Param | Default | Notes |
|---|---|---|
parent_path | "" | Document path prefix; empty = root-level {collection}/... |
Response shape:
{
"documents": [
{
"id": "550e8400-e29b-41d4-a716-446655440001",
"path": "users/alice",
"data": { "display_name": "Alice" }
}
]
}
Behavior:
- Returns only immediate children in that collection (not deeper descendants).
- Results ordered by
pathascending. - Invalid
parent_path→400. - Pagination: the backend loads page 1 with page size 20 internally. The client API response includes only
documents(nopage,page_size, ortotalfields). You cannot pass page query params on this endpoint today. If you have more than 20 direct children, use narrowerparent_pathscopes orGET /query/{collection}with filters.
Reference: List collection documents
Endpoint 5: Patch Document
PATCH /documents/docs/{path}
Shallow-merges fields into the existing data object.
Request body:
{
"data": {
"level": 11
}
}
Response shape:
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"path": "players/42/progress",
"data": { "level": 11, "xp": 4500 }
}
Behavior:
- Missing document →
404. - Merge is shallow at the top level: nested object keys in the patch replace the entire nested value rather than deep-merging inside it.
Reference: Patch document
Endpoint 6: Delete Document
DELETE /documents/docs/{path}
Removes a single document.
Response shape:
{
"deleted": "players/42/progress"
}
Behavior:
- Missing document →
404. - Does not delete other documents whose paths are prefixed with this path.
Reference: Delete document
Endpoint 7: Query Collection
GET /documents/query/{collection}
Filters documents in {collection} for the current project using JSON field lookups.
Query parameters:
| Param | Notes |
|---|---|
where | Repeatable. Each value: field__lookup:value (e.g. level__gte:10) |
Examples:
?where=level__gte:10?where=active__exact:true&where=region__exact:us
Response shape: same as list — { "documents": [ ... ] }.
Behavior:
- Filters apply to the
dataJSON column via Django-styledata__{field}__{lookup}keys. - Values are coerced:
true,false,null; integers/floats if numeric; otherwise string. - Invalid clause (missing
:, or missing__in field part) →400with a format hint. - No pagination; all matches returned, ordered by
path.
Query filter format
Each where clause must look like:
field__lookup:value
| Clause | Meaning |
|---|---|
level__gte:10 | data.level >= 10 |
score__lte:100 | data.score <= 100 |
active__exact:true | boolean true |
nickname__icontains:ali | case-insensitive contains |
Reference: Query collection
Client API vs Admin Document Store
| Aspect | Client (/documents) | Admin (/document-store/{org_id}/{project_id}/...) |
|---|---|---|
| Project scope | x-client-id / x-client-secret headers | org_id and project_id in URL + org check |
| Permissions | client.documents for all operations | document.view (read) / document.manage (write) |
| List collection names | Not exposed | GET .../collections |
| List documents pagination | Fixed internal page 1, size 20; metadata not in response | page and page_size query params |
Common Error Codes
400:- path or
parent_pathis not a valid document path (odd segment count) - invalid
wherequery clause format
- path or
401: missing or invalidx-client-id/x-client-secret, or missing/invalid/expired bearer token403: authenticated user lacksclient.documentspermission404: document not found422: invalid request body (validation)
cURL Examples
Upsert at path
curl -X PUT "https://mw-client-api.wonderfulcoast-218d579d.centralindia.azurecontainerapps.io/documents/docs/players/42/progress" \
-H "x-client-id: <client-id>" \
-H "x-client-secret: <client-secret>" \
-H "Authorization: Bearer <access-token>" \
-H "Content-Type: application/json" \
-d '{"data":{"level":10,"xp":4500}}'
Create with auto ID
curl -X POST "https://mw-client-api.wonderfulcoast-218d579d.centralindia.azurecontainerapps.io/documents/collections/users" \
-H "x-client-id: <client-id>" \
-H "x-client-secret: <client-secret>" \
-H "Authorization: Bearer <access-token>" \
-H "Content-Type: application/json" \
-d '{"data":{"display_name":"Alice"},"parent_path":""}'
Get document
curl -X GET "https://mw-client-api.wonderfulcoast-218d579d.centralindia.azurecontainerapps.io/documents/docs/players/42/progress" \
-H "x-client-id: <client-id>" \
-H "x-client-secret: <client-secret>" \
-H "Authorization: Bearer <access-token>"
List collection
curl -X GET "https://mw-client-api.wonderfulcoast-218d579d.centralindia.azurecontainerapps.io/documents/collections/users?parent_path=" \
-H "x-client-id: <client-id>" \
-H "x-client-secret: <client-secret>" \
-H "Authorization: Bearer <access-token>"
Patch document
curl -X PATCH "https://mw-client-api.wonderfulcoast-218d579d.centralindia.azurecontainerapps.io/documents/docs/players/42/progress" \
-H "x-client-id: <client-id>" \
-H "x-client-secret: <client-secret>" \
-H "Authorization: Bearer <access-token>" \
-H "Content-Type: application/json" \
-d '{"data":{"level":11}}'
Delete document
curl -X DELETE "https://mw-client-api.wonderfulcoast-218d579d.centralindia.azurecontainerapps.io/documents/docs/players/42/progress" \
-H "x-client-id: <client-id>" \
-H "x-client-secret: <client-secret>" \
-H "Authorization: Bearer <access-token>"
Query collection
curl -X GET "https://mw-client-api.wonderfulcoast-218d579d.centralindia.azurecontainerapps.io/documents/query/users?where=level__gte:10&where=active__exact:true" \
-H "x-client-id: <client-id>" \
-H "x-client-secret: <client-secret>" \
-H "Authorization: Bearer <access-token>"
Integration Notes for Game Teams
- Use predictable paths for per-player state, e.g.
players/{userId}/progress, so reads and writes align with your auth user id. - Use
PUTwhen you own the full path (deterministic id in the path). UsePOST /collections/{collection}when the server should assign a UUID segment. - On first load,
GETmay return404; initialize withPUTupsert or handle empty state in the client. - Treat
PATCHas top-level key merge only; for nested structures, read-merge-write in the client or replace the wholedataviaPUT. - Plan for the 20-document cap on list-by-collection until client pagination is added; split data across collections or parents, or use query filters.
- Never assume
DELETEremoves a subtree; delete nested documents explicitly. - Use
GET+collectionsto drive UI navigation into subcollections without hard-coding collection names. - Backend storage is the source of truth; project isolation is enforced by client credentials.