Skip to main content

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/progress
  • GET 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 (for PUT, POST, and PATCH with 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, and DELETE on /docs/{path} require {path} to be a document path (even segment count). Odd segment counts return 400 with "Path must point to a document (even number of segments)".
  • parent_path on POST /collections/{collection} and GET /collections/{collection} must be empty (root) or a document path (even segments). Invalid parent_path returns 400 with "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": []
}
FieldNotes
idUUID of the stored document
pathNormalized path after write/read
dataJSON object payload
createdPresent on PUT upsert and POST create: true if inserted, false if updated (PUT only)
collectionsPopulated 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: true on insert, created: false on 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": ""
}
FieldNotes
dataJSON object to store; defaults to {}
parent_pathOptional. 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} when parent_path is set, else {collection}/{uuid}.
  • Always inserts a new row; created is always true.
  • Invalid parent_path400.

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".
  • collections lists 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:

ParamDefaultNotes
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 path ascending.
  • Invalid parent_path400.
  • Pagination: the backend loads page 1 with page size 20 internally. The client API response includes only documents (no page, page_size, or total fields). You cannot pass page query params on this endpoint today. If you have more than 20 direct children, use narrower parent_path scopes or GET /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:

ParamNotes
whereRepeatable. 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 data JSON column via Django-style data__{field}__{lookup} keys.
  • Values are coerced: true, false, null; integers/floats if numeric; otherwise string.
  • Invalid clause (missing :, or missing __ in field part) → 400 with a format hint.
  • No pagination; all matches returned, ordered by path.

Query filter format

Each where clause must look like:

field__lookup:value
ClauseMeaning
level__gte:10data.level >= 10
score__lte:100data.score <= 100
active__exact:trueboolean true
nickname__icontains:alicase-insensitive contains

Reference: Query collection

Client API vs Admin Document Store

AspectClient (/documents)Admin (/document-store/{org_id}/{project_id}/...)
Project scopex-client-id / x-client-secret headersorg_id and project_id in URL + org check
Permissionsclient.documents for all operationsdocument.view (read) / document.manage (write)
List collection namesNot exposedGET .../collections
List documents paginationFixed internal page 1, size 20; metadata not in responsepage and page_size query params

Common Error Codes

  • 400:
    • path or parent_path is not a valid document path (odd segment count)
    • invalid where query clause format
  • 401: missing or invalid x-client-id / x-client-secret, or missing/invalid/expired bearer token
  • 403: authenticated user lacks client.documents permission
  • 404: document not found
  • 422: 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 PUT when you own the full path (deterministic id in the path). Use POST /collections/{collection} when the server should assign a UUID segment.
  • On first load, GET may return 404; initialize with PUT upsert or handle empty state in the client.
  • Treat PATCH as top-level key merge only; for nested structures, read-merge-write in the client or replace the whole data via PUT.
  • 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 DELETE removes a subtree; delete nested documents explicitly.
  • Use GET + collections to drive UI navigation into subcollections without hard-coding collection names.
  • Backend storage is the source of truth; project isolation is enforced by client credentials.