Skip to content

Data API ​

Data API is a Firebase-style document database service that allows third-party applications to read and write structured JSON data via HTTP requests. Each application can create multiple Collections, each containing multiple Documents.

Overview ​

ItemDescription
Base Path/data/api/
AuthenticationBasic (app mode) or Bearer (user mode)
Data FormatJSON
Document Size Limit512 KB
Pagination LimitUp to 100 items per page

Authentication ​

Data API supports two authentication modes, distinguished by the Authorization header:

App Mode (Basic) ​

Authenticate using the application's client_id and client_secret. App mode has full read/write access to all documents in a collection, regardless of permission settings.

Authorization: Basic base64(client_id:client_secret)

⚠️ Security Warning: The client_secret grants full administrative access to your application. Never embed it in client-side applications distributed to users (game builds, desktop apps, etc.) — secrets can be extracted through reverse engineering. App mode should only be used in trusted server environments or internal development tools.

Recommended Architecture: User-facing clients should use Bearer mode (user OIDC login) combined with Cloud Functions and Permission Groups for business logic.

User Mode (Bearer) ​

Authenticate using the user's OAuth access_token. User mode access depends on the collection's permission settings.

Authorization: Bearer <access_token>

🔒 Access Note: Collections are private by default — users cannot access them via API directly. To allow client-side calls, set the collection's visibility to public in the developer dashboard. It is recommended to use cloud functions for data operations.

Replay Protection ​

All API requests must include the following headers to prevent replay attacks:

HeaderDescription
X-TimestampCurrent Unix timestamp (seconds), must be within ±5 minutes of server time
X-NonceRandom unique string, 16-64 alphanumeric characters, different for each request

Visibility Model ​

Each collection is controlled by visibility (a bitmask) and publicEditOp (public edit operation) to manage user-mode access.

Collections are private by default — only app mode (Basic) and cloud functions can access them.

visibility Bitmask ​

ValueMeaning
0Private (default) — user mode cannot access
1Public read-only — user mode can read documents but not write
3Public read-write — user mode can read and write according to publicEditOp rules

publicEditOp (Edit Operation) ​

Only effective when visibility = 3. Controls what write operations user mode is permitted to perform:

ValueMeaning
addOnly allow creating/updating documents (PUT / PATCH)
removeOnly allow deleting documents (DELETE)
allAllow creating, updating, and deleting (default)

App mode (Basic auth) is not restricted by visibility and can always read/write all documents.

Collection Management ​

Collections are created and managed through the developer management panel and are not exposed as public API endpoints.

Each collection can have an optional description of up to 512 characters for developer notes and purpose labels. Descriptions are visible only through management APIs, the dashboard, and developer tools; they are not returned by the public Data API.

Collection name rules:

  • Must start with a letter or underscore
  • May contain letters, digits, underscores, and hyphens
  • Length: 1–128 characters
  • Regex: /^[a-zA-Z_][a-zA-Z0-9_-]{0,127}$/

Ordered Index Collections ​

Regular collections keep the legacy behavior: the full document JSON is stored in data_document.data, and the list endpoint can sort by one JSON field. For leaderboards, speedrun boards, price boards, and other high-frequency ordered lists, set the collection data capability to indexed and define one or more ordered indexes in settings.indexes.

Ordered indexes do not store the whole board in one document. The model remains “one ranked entry = one document”. The server projects configured sort fields into the internal data_document_index table. Each document still has the 512 KB document limit, while collection size can grow and ranking queries use the index projection.

Example: a generic partitioned best-score ranking. One collection can hold many independent rankings split by groupId + variant:

json
{
  "indexes": [
    {
      "name": "best_score",
      "kind": "ordered",
      "partition": [
        { "path": "groupId", "type": "string" },
        { "path": "variant", "type": "string" }
      ],
      "fields": [
        { "path": "score", "type": "int", "order": "desc" },
        { "path": "completedAt", "type": "int", "order": "asc" }
      ]
    }
  ],
  "writePolicy": {
    "mode": "keepBest",
    "index": "best_score"
  }
}

Field reference:

FieldDescription
indexes[].nameIndex name used by query APIs
indexes[].kindCurrently supports ordered
indexes[].partitionOptional partition fields. Passing the same partition in queries creates an independent ranking scope
fields[].pathSort path. Defaults to document.data; also supports data.xxx, documentId, ownerUid, createdAt, and updatedAt
fields[].typeint, number, string, or boolean
fields[].orderasc or desc
fields[].decimalsDecimal scaling precision for number, 0–6
fields[].requiredDefaults to true. Set false to place missing values by missing
writePolicy.modeoverwrite or keepBest
writePolicy.indexOrdered index used by keepBest to decide whether new data ranks higher

keepBest applies to PUT / PATCH / $data.set / $data.update / management run scripts. If the new data does not rank ahead of the old document under the configured index, the server keeps the old document and returns kept: true. Single-document edits in the developer panel are treated as maintenance writes and rebuild the index directly.

Normalize important sort values such as time, score, or price as integers when practical to avoid floating-point sort drift.

Management Run Command ​

The developer management panel includes a Run Command tool for running SQL-like commands or embedded JavaScript scripts against an application's database. This uses the management API and requires a logged-in user with data edit permission for the application. It is not a public Data API endpoint and cannot be called with a Basic app secret or user Bearer token.

Each operation can run in one of two modes:

ModeBehavior
previewComputes the affected rows and returns before / after changes without writing to the database
executeWrites to the database and returns the affected count plus the change list

Important response fields:

FieldDescription
affectedNumber of matched or modified documents
resultCommand or script return value
changesWrite diff list with operation, collection, documentId, before, and after
logsCaptured console.log/info/warn/error output from scripts

maxRows limits how many documents can be scanned or modified in one run. The default is 100; the maximum is 5000.

Command Mode ​

Command mode is not full SQL and never executes raw SQL. It supports a safe document-oriented subset:

sql
SELECT * FROM players WHERE level >= 10 LIMIT 20;
UPDATE players SET reviewed = true, season = 'S1' WHERE score > 1000 LIMIT 100;
DELETE FROM players WHERE banned = true LIMIT 50;

Supported condition operators:

SyntaxMeaning
=Equals
!= / <>Not equals
> / >=Greater than / greater than or equal
< / <=Less than / less than or equal

Field names point to top-level fields in document data by default. You can also use documentId, ownerUid, or nested paths such as data.profile.level. UPDATE SET can only modify document data fields and cannot change documentId.

JS Script Mode ​

Scripts run in a controlled VM and can use the built-in db object and console. Return values must be returned with return:

javascript
const docs = await db.select('players', {
  where: { level: { $gte: 10 } },
  limit: 100
})

for (const doc of docs) {
  await db.update('players', {
    where: { documentId: doc.documentId },
    patch: { reviewed: true }
  })
}

console.log('reviewed', docs.length)
return { reviewed: docs.length }

Available methods:

MethodDescription
db.select(collection, { where, limit, offset, sortBy, sortOrder })Reads documents and returns documentId, ownerUid, data, and timestamps
db.update(collection, { where, patch, limit })Shallow merge update; patch also supports $set, $unset, $inc, $push, and $pull
db.delete(collection, { where, limit })Deletes matched documents
db.set(collection, documentId, data, { ownerUid })Creates or replaces one document
db.indexTop(collection, indexName, { limit, offset, ownerUid, ownerUids, partition })Reads the top ordered-index entries or a filtered ranking
db.indexAround(collection, indexName, documentId, { before, after, partition })Reads the ranking window around a document

Script where objects support { field: value } and { field: { $gt: 1 } }. Operators include $eq, $ne, $gt, $gte, $lt, $lte, and $in. Field path rules are the same as command mode.

API Endpoints ​

List Documents ​

http
GET /data/api/{collectionName}

Query Parameters:

ParameterTypeDescription
ownerUidstringOptional. Filter by owner
limitnumberOptional. Items per page, default 50, max 100
offsetnumberOptional. Offset, default 0
sortBystringOptional. Sort by a field in data
sortOrderstringOptional. Sort direction: asc or desc
wherestringOptional. JSON filter string (e.g. {"level":{"$gt":5}})

Response:

json
{
  "documents": [
    {
      "documentId": "player_001",
      "ownerUid": "R10001",
      "createdAt": "1713168000",
      "updatedAt": "1713168000"
    }
  ],
  "total": 42,
  "limit": 50,
  "offset": 0
}

Note: list does not return the data field to save bandwidth. The data field would include all document JSON content and can be large. Use GET /data/api/{collectionName}/{documentId} to retrieve the full document.

List Top Entries by Ordered Index ​

http
GET /data/api/{collectionName}/indexes/{indexName}/top

Query Parameters:

ParameterTypeDescription
limitnumberOptional. Default 50, max 100
offsetnumberOptional. Pagination offset
ownerUidstringOptional. Filter to one owner
ownerUidsstringOptional. Comma-separated owner UID list, useful for friend rankings
partitionJSON stringOptional partition query, such as {"groupId":"item_001","variant":"hard"}

The response includes full data and rank:

json
{
  "indexName": "best_score",
  "documents": [
    {
      "rank": 1,
      "documentId": "uid_001",
      "ownerUid": "uid_001",
      "data": {
        "score": 1000000,
        "label": "Entry 001",
        "completedAt": 1713168000
      },
      "createdAt": "1713168000",
      "updatedAt": "1713168000"
    }
  ],
  "total": 1,
  "limit": 50,
  "offset": 0
}

Get My or a Document's Ranking Window ​

http
GET /data/api/{collectionName}/indexes/{indexName}/me?before=5&after=5&partition={"groupId":"item_001","variant":"hard"}
GET /data/api/{collectionName}/indexes/{indexName}/around/{documentId}?before=5&after=5&partition={"groupId":"item_001","variant":"hard"}

me requires Bearer user authentication and finds the best indexed document for the current user's uid. around/{documentId} works when the target document ID is known. The response includes the target rank, target document, and the surrounding documents window.

Get Document ​

http
GET /data/api/{collectionName}/{documentId}

Response:

json
{
  "document": {
    "documentId": "player_001",
    "ownerUid": "R10001",
    "data": { "score": 9800, "level": 15 },
    "createdAt": "2026-03-10 12:00:00",
    "updatedAt": "2026-03-15 08:30:00"
  }
}

document.data is your application JSON payload. The outer document object carries metadata such as document ID, owner, and timestamps.

Create or Replace Document ​

Uses PUT semantics — replaces the document if it exists, creates it if it doesn't.

http
PUT /data/api/{collectionName}/{documentId}
Content-Type: application/json

Request Body:

json
{
  "data": { "score": 9800, "level": 15 },
  "ownerUid": "R10001"
}
FieldTypeDescription
dataobjectRequired. Document content (any JSON object)
ownerUidstringOptional. Only available in app mode to specify document owner. In user mode, automatically set to the current user

Response:

json
{
  "status": "ok",
  "documentId": "player_001",
  "created": true
}

Document ID rules:

  • May contain letters, digits, underscores, hyphens, colons, and dots
  • Length: 1–128 characters
  • Regex: /^[a-zA-Z0-9_\-:.]{1,128}$/

Note: PUT writes the whole document. If the document already exists, data is replaced by the new object from the request body; old fields that are not present in the new data are removed. PUT does not fail just because a business field is missing.

Partial Update Document ​

Uses PATCH semantics — performs a shallow merge on the document data.

http
PATCH /data/api/{collectionName}/{documentId}
Content-Type: application/json

Request Body:

json
{
  "data": { "level": 16 }
}

Only the specified fields are updated; other fields remain unchanged. For example, the original document's score will not be affected.

If PATCH data contains a top-level field that does not exist yet, that field is added automatically. If the document itself does not exist, the API returns 404 not_found. The shallow merge only applies to top-level fields; "profile.name" is not interpreted as a nested path.

Response:

json
{
  "status": "ok",
  "documentId": "player_001"
}

Delete Document ​

http
DELETE /data/api/{collectionName}/{documentId}

Response:

json
{
  "status": "ok"
}

Error Responses ​

All errors are returned in JSON format with error and error_description fields:

json
{
  "error": "not_found",
  "error_description": "Document not found"
}

Common error codes:

HTTP StatuserrorDescription
400invalid_document_idInvalid document ID format
400payload_too_largeDocument size exceeds 512 KB limit
401unauthorizedMissing Authorization header
401invalid_clientBasic auth failed (invalid client_id or secret)
401invalid_tokenBearer token is invalid or expired
403forbiddenInsufficient permissions (e.g., user accessing a private collection, or writing to a read-only collection)
403service_not_enabledData service not enabled for this application
404not_foundCollection or document not found

Examples ​

cURL — App Mode Write ​

bash
# Write a document
curl -X PUT https://api.starecho.net/data/api/player_data/player_001 \
  -H "Authorization: Basic $(echo -n 'your_client_id:your_client_secret' | base64)" \
  -H "Content-Type: application/json" \
  -d '{"data": {"score": 9800, "level": 15}, "ownerUid": "R10001"}'

# Read a document
curl https://api.starecho.net/data/api/player_data/player_001 \
  -H "Authorization: Basic $(echo -n 'your_client_id:your_client_secret' | base64)"

cURL — User Mode Read/Write ​

bash
# User writes their own document
curl -X PUT https://api.starecho.net/data/api/player_data/my_settings \
  -H "Authorization: Bearer eyJhbGciOiJSUzI1NiIs..." \
  -H "Content-Type: application/json" \
  -d '{"data": {"theme": "dark", "language": "zh-CN"}}'

# User reads their own document
curl https://api.starecho.net/data/api/player_data/my_settings \
  -H "Authorization: Bearer eyJhbGciOiJSUzI1NiIs..."

JavaScript (Fetch API) ​

js
const CLIENT_ID = 'your_client_id'
const CLIENT_SECRET = 'your_client_secret'
const BASE_URL = 'https://api.starecho.net/data/api'

// Write a document
await fetch(`${BASE_URL}/player_data/player_001`, {
  method: 'PUT',
  headers: {
    'Authorization': `Basic ${btoa(`${CLIENT_ID}:${CLIENT_SECRET}`)}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    data: { score: 9800, level: 15 },
    ownerUid: 'R10001',
  }),
})

// List documents
const res = await fetch(`${BASE_URL}/player_data?limit=20&offset=0`, {
  headers: {
    'Authorization': `Basic ${btoa(`${CLIENT_ID}:${CLIENT_SECRET}`)}`,
  },
})
const { documents, total } = await res.json()

© 2026 StarEcho