inmake DOCS
Docs / API / Admin

Admin

Generic document and collection access for private tokens.

Generic document and collection access for private tokens on a server: read, list, count, create, update, delete, batch writes, file uploads and folders — nothing else: every platform operation on the project (provider switches, staff actions, the payment catalog, shipping, the traffic reports, the web address and its hosting) lives under Project. Paths are Firestore paths under store/{sid}/…, so this group can reach every entity CRM & Studio edits.

Image dimensions. An image upload (PNG, JPEG, GIF, WebP, SVG) answers with width and height on its entity, read from the file header on the server — on the original and on every variant, on both upload paths. Nothing to send; a format the parser does not size simply has no fields.

Large files. /admin/upload takes one request of at most 25 MB. Larger files (up to 1 GiB) go straight to storage: /admin/upload/start returns a Cloud Storage resumable session URI for one object path, you PUT the bytes to it in chunks (Content-Range: bytes a-b/total, 8 MiB each; 308 means send the next, 200/201 means done; after a network error an empty PUT with bytes */total tells you where to resume), and /admin/upload/finish registers the file exactly as a normal upload. /admin/upload/abort drops an unfinished one. The session URI is a write credential for that one file, valid 7 days; an unfinished session is discarded after 24 h. The SDK's Files.upload does all of this for you above 4 MiB (20 MiB before 2026-10-27). The one-request path is also bounded per API instance: when four such uploads are already being processed there, the fifth is answered 503 {error: "upload_busy"} with Retry-After: 2 before its body is read — the SDK retries it three times with a short back-off; a raw caller should do the same, or use the resumable path, which never meets that limit.

POST/api/admin/createwrites data

All operations are allowed (restricted by token). Should be exclusively used on backend.

Parameters

NameTypeRequiredDescription
storestringoptionalOnly when store is not included in header and path points to any other collection.
pathstringrequiredPath where to write value. /store/{storeId}/products/{productId}. When path is other then store/... store header or param is required. Path should have even number of parts - so it points to document.
mergebooleanoptionalfalse - to override whole document. true by default - merge given data with original document. Default: true
valueobjectrequireddata to write into document in given path.

Response

object — Returns result of write operation (confirmation).

Sample request

{
  "path": "/store/{storeId}/data/en_US/products/tshirt-blue",
  "value": {
    "name": "Blue T-shirt",
    "price": 24.9,
    "tags": [
      "apparel",
      "new"
    ]
  }
}

Writes one document at path (merged into an existing one by default). Add merge: false to replace the whole document.

POST/api/admin/read

Parameters

NameTypeRequiredDescription
storestringoptionalOptional. Only when store is not included in header and path points to any other collection.
pathstringrequiredPath where to write value. When path is other then store/... store header or param is required. Path should have even number of parts - so it points to document.
masklistoptionalMasks fields in response. Returns just subset of stored object.

Response

object — Returns object for given path.

Sample request

{
  "path": "/store/{storeId}/data/en_US/products/tshirt-blue",
  "mask": [
    "name",
    "price"
  ]
}
POST/api/admin/updatewrites data

Parameters

NameTypeRequiredDescription
storestringoptionalOptional. Only when store is not included in header and path points to any other collection.
pathstringrequiredPath where to write value. When path is other then store/... store header or param is required. Path should have even number of parts - so it points to document.
valueobjectrequireddata to write into document in given path. Use dot operator (param.field) to update data within objects. Use actions [union, remove], [increment], [update, delete] to edit document data.

Response

object — Returns result of write operation (confirmation).

Sample request

{
  "path": "/store/{storeId}/data/en_US/products/tshirt-blue",
  "value": {
    "price": 19.9,
    "views": {
      "item": 1,
      "action": "increment"
    },
    "tags": {
      "item": "sale",
      "action": "union"
    },
    "meta.draft": {
      "action": "delete"
    }
  }
}

Plain values overwrite a field and dotted keys (meta.draft) reach into nested objects. An {item, action} object applies an operation instead - increment, union / remove for arrays, delete to drop the field.

POST/api/admin/deletewrites data

Parameters

NameTypeRequiredDescription
storestringoptionalOptional. Only when store is not included in header and path points to any other collection.
pathstringrequiredPath where to write value. When path is other then store/... store header or param is required. Path should have even number of parts - so it points to document.

Response

object — Returns result of delete operation (confirmation).

Sample request

{
  "path": "/store/{storeId}/data/en_US/products/tshirt-blue"
}
POST/api/admin/list

Parameters

NameTypeRequiredDescription
storestringoptionalOnly when store is not included in header and path points to any other collection.
pathstringrequiredPath where to look up. When path is other then store/... store header or param is required. Path should have odd number of parts - so it points to collection.
queryobjectoptionalSet of conditions to filter out documents.
offsetstringoptionalID of last document.
limitintegeroptionalMaximum number of documents per page.
order_byobjectoptionalField name and direction - [asc, desc].
masklistoptionalMasks fields in response. Returns just subset of stored object.

Response

object — Returns list of objects for given path and query.

Sample request

{
  "path": "/store/{storeId}/data/en_US/products",
  "query": {
    "category": "apparel",
    "price": {
      "<=": 50
    }
  },
  "order_by": {
    "price": "asc"
  },
  "limit": 20
}

A scalar in query is an equality shorthand; an operator object such as {"<=": 50} applies that Firestore operator. Page by passing the last returned id as offset.

POST/api/admin/batchwrites data

Parameters

NameTypeRequiredDescription
storestringoptionalOnly when store is not included in header and path points to any other collection.
pathlistrequiredList of paths where to look up. When path is other then store/... store header or param is required
masklistoptionalMasks fields in response. Returns just subset of stored object.
metabooleanoptionaltrue - the response also carries _meta: {[path]: {update_time}}, each found document's Firestore update time (nanosecond-exact ISO 8601). Pass it back as precondition.update_time to /admin/commit to fence a later write on the version you read. Default: false

Response

object — Returns list of objects for given paths (keyed by path; plus _meta when meta is true).

Sample request

{
  "path": [
    "/store/{storeId}/data/en_US/products/tshirt-blue",
    "/store/{storeId}/data/en_US/products/mug-white"
  ],
  "mask": [
    "name",
    "price"
  ]
}
POST/api/admin/commitwrites data

Up to 100 preconditioned writes applied atomically (all or nothing). Every path must lie under the request's own store (store/{storeId}/…) and pass the token's path scope as a write. create fails when the document exists; set overwrites (with precondition: {exists: false} it is a create); update takes the same dotted keys and {item, action} operations as /admin/update; update and delete may be fenced on precondition.exists or on the exact update_time an earlier commit or /admin/batch {meta: true} returned. A refused precondition answers 409 {error: "precondition_failed", index, code} (code = ALREADY_EXISTS | FAILED_PRECONDITION) and nothing is written.

Parameters

NameTypeRequiredDescription
storestringrequiredThe store every path belongs to (header or param).
writeslistrequiredList of at most 100 writes {path, op, value?, precondition?} — op is create | set | update | delete; path points to a document under store/{storeId}/; value is required for every op but delete; precondition is {exists?: boolean, update_time?: string}.

Response

object — Returns {ok: true, results: [{path, update_time}]} — one entry per write, in request order; update_time is the document's new Firestore update time (the value a later precondition takes).

Sample request

{
  "writes": [
    {
      "path": "/store/{storeId}/modules/echo/state/cursor",
      "op": "create",
      "value": {
        "n": 1
      }
    },
    {
      "path": "/store/{storeId}/orders/o-1",
      "op": "update",
      "value": {
        "modules.echo.synced": true
      },
      "precondition": {
        "update_time": "2026-09-25T10:00:00.123456000Z"
      }
    }
  ]
}

The first write creates a document (409 ALREADY_EXISTS when it is already there); the second patches an order only if it is still the version whose update_time was read back with /admin/batch {meta: true} (409 FAILED_PRECONDITION otherwise). Either failure leaves both untouched.

POST/api/admin/addwrites data

Parameters

NameTypeRequiredDescription
storestringoptionalOnly when store is not included in header and path points to any other collection.
pathstringrequiredPath where to write data. When path is other then store/... store header or param is required. Path should have odd number of parts - so it points to collection.
valueobjectrequireddata to write into document in given path.

Response

object — Returns result of write operation (confirmation) - "id" and "ref" of new document

Sample request

{
  "path": "/store/{storeId}/data/en_US/articles",
  "value": {
    "title": "Hello world",
    "status": "draft"
  }
}

Appends a document with a generated id to the collection at path; the response carries the new id and ref.

POST/api/admin/count

Parameters

NameTypeRequiredDescription
storestringoptionalOnly when store is not included in header and path points to any other collection.
pathstringrequiredPath to a collection. When path is other then store/... store header or param is required. Path should have odd number of parts - so it points to collection.
queryobjectoptionalField-keyed map of conditions to filter documents before counting. Each value is an operator object like {"==": "ready"}, {">": 100}, or an array of them; see QueryObject helpers in core-lib. Omit for a total collection count.

Response

object — Returns {count: number} — number of documents that match the query (or the total when no query was provided).

Sample request

{
  "path": "/store/{storeId}/orders",
  "query": {
    "status": {
      "==": "ready"
    }
  }
}
POST/api/admin/aggregatewrites data

Parameters

NameTypeRequiredDescription
storestringoptionalOnly when store is not included in header and path points to any other collection.
pathstringrequiredPath to a collection (odd number of parts). Same rules as count.
queryobjectoptionalField-keyed map of conditions, exactly as for list / count. A value shaped {"_seconds": n, "_nanoseconds": n} (the way timestamps leave the API) is compared as a Firestore timestamp, so {"timestamp": {">=": {"_seconds": 1780000000, "_nanoseconds": 0}}} bounds a window.
sumlistoptionalNumeric fields to total over the matching documents (dot paths allowed, e.g. invoice.total). A string is accepted for one field.
avglistoptionalNumeric fields to average over the matching documents. null in the result when no document carries the field.
bucketsobjectoptionalA SERIES instead of one number - {field?: "timestamp", start: {"_seconds": n, "_nanoseconds": 0}, step?: 86400, count: 1..92} splits the aggregation into count consecutive buckets of step seconds from start on the timestamp field (which the query must then not filter itself; 400 buckets_invalid). This is how a chart gets its daily points - never by listing the window.

Response

object — Returns {count, sum: {field: number}, avg: {field: number | null}} from ONE aggregation query — billed one document read per 1000 matching index entries, however many fields are aggregated. The rule for dashboard numbers: aggregate, never list-and-count. With buckets the answer also carries buckets: [{count, sum, avg}, ...] (one per bucket, in order) and mode, and costs 1 + min(n, count) reads for n matching documents - nothing more when the range is empty (mode: empty), the n documents listed once when they are fewer than the buckets (docs), else one aggregation per bucket (buckets).

Sample request

{
  "path": "/store/{storeId}/orders",
  "query": "{\"status\": {\"in\": [\"completed\", \"closed\"]}, \"timestamp\": {\">=\": {\"_seconds\": 1780000000, \"_nanoseconds\": 0}}}",
  "sum": "[\"invoice.total\"]"
}
POST/api/admin/uploadwrites data

Upload a file. Send multipart/form-data with a binary file part plus text fields (dir, name?, content_type?, sid?, id?) — or JSON with dir, name, content_type?, sid?, id? and base64-encoded data. The destination is store/{sid}/files/{dir}/{id} (up to 5 levels of folders). Missing folders are auto-created.

Parameters

NameTypeRequiredDescription
dirstringrequiredRelative directory under store/{sid}/files. Accepts "", "articles", "/articles/abc". Validated against //, .., trailing /, max depth 5, and segment charset [A-Za-z0-9_\-.]{1,64}.
sidstringoptionalTarget store id. Required when the request uses a master token (store='*' header); otherwise inferred from the auth context.
idstringoptionalDocument id under the resolved folder. Server mints a sortable id when omitted.
namestringoptionalOriginal filename (drives extension). Defaults to the multipart file's name.
content_typestringoptionalMIME type. Defaults to the multipart file's mimetype or application/octet-stream.
variantstringoptionalVariant key (e.g. thumb, large). When set, the file is stored alongside the default and variants[<key>] is patched on the doc instead of replacing it. Requires id to point at an existing file.
filefilerequiredBinary file part (multipart/form-data field named file). Alternative to JSON data.
datastringoptionalBase64-encoded file bytes (JSON shape only). Use the multipart file part for binary uploads instead.
metadataobjectoptionalFree-form key/value map (author, description, note, title, …). Stored on the default doc. In multipart, send as a JSON-stringified text field.
keep_namebooleanoptionalStore the object under a slug of name instead of the minted id — store/{sid}/{dir}/urbanity-esg-report-2024.pdf (lower-case ASCII, dashes, ≤ 80 chars + the extension). The doc id is unaffected. A basename another object already holds gets a short random suffix (never overwritten); the same id re-uploaded with the same name replaces its object in place. Default upload only. In multipart, send as the text true.
unzipstringoptionalSet to design to ingest the uploaded zip as a template bundle instead of storing it as a single file: entries are unzipped, filtered (docs/maps/php/dotfiles dropped), stored under the archive node's Storage prefix store/{sid}/{dir}/{id}/…, and ONE type=archive file doc (manifest: file list + extracted design signals) is written into the files tree at store/{sid}/files/{dir}/{id} — visible in the Files view and counted by the folder summaries. dir defaults to templates; a DesignAssetRef is upserted (replace-by-id, version bumped) into setup/design.assets. variant/metadata are ignored; returns {path, id, name, count, bytes, skipped, signals}.
createBundlebooleanoptionalJSON shape only. Set to true to create an EMPTY asset bundle: writes a type=archive file doc with a zero-entry manifest at store/{sid}/files/{dir}/{id} (dir defaults to templates, id minted when omitted) and upserts its DesignAssetRef into setup/design.assets. Requires name. This is the bootstrap entry writes need (they 404 on a missing archive). Re-using an existing archive id is idempotent (keeps entries, refreshes name/ref). Returns {path, id, name, count, bytes, skipped, signals}.
entrystringoptionalJSON shape only. Path of ONE entry inside an existing archive doc (id = the archive, dir defaults to templates) — overwrites/creates the object at the archive's storage prefix and upserts the manifest row, bumping the bundle's DesignAssetRef.version. Text entries get a short (5 min) cache-control so edits propagate. Requires base64 data (max 4 MB, extension must be in the bundle allow-list) — or remove: true to drop the entry instead. Returns {path, id, entry, size?, content_type?, removed?, version}.
removebooleanoptionalWith entry: delete the entry (drops the manifest row; the files trigger reaps the Storage object) instead of writing it.

Response

object — Returns {path, id, entity} — an image's entity carries width / height read from the file header (PNG, JPEG, GIF, WebP, SVG; the variant shape too) — the resolved Firestore doc path, the document id, and the stored FileEntity (or FileVariant for variant uploads). Every object is stored with Content-Disposition: inline; filename="<name>" (+ RFC 5987 filename* for non-ASCII), so a browser opens it in place but saves it under its uploaded name — /assets/** on the hosted sites forwards the header. With unzip=design, returns the ingest summary {path, id, name, count, bytes, skipped, signals} instead; with entry, the entry-write summary {path, id, entry, size?, content_type?, removed?, version}.

Sample request

{
  "dir": "images",
  "name": "pixel.png",
  "content_type": "image/png",
  "data": "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg=="
}

The JSON shape - data is the file base64-encoded (here a 1x1 PNG). For real files send multipart/form-data with a binary file part plus the same text fields.

POST/api/admin/upload/startwrites data

Start a resumable upload of a large file (above ~20 MB, up to 1 GiB) — the bytes go from the client straight to Cloud Storage, never through the API. Returns a Google Cloud Storage resumable session URI for ONE object path: PUT the file to it in chunks (Content-Range: bytes a-b/total, every chunk but the last a multiple of 256 KiB — session.chunk_bytes is the size to use; 308 = send the next chunk, 200/201 = done; after a network error PUT an empty body with Content-Range: bytes \*\/total and resume from the Range header), then call /admin/upload/finish. Same destination rules as /admin/upload (store/{sid}/files/{dir}/{id}, keep_name, replace in place with id); the object's content type, cache and disposition are fixed here. The session URI is a write credential for that one object for 7 days — keep it private. A pending session expires after 24 h.

Parameters

NameTypeRequiredDescription
dirstringrequiredRelative directory under store/{sid}/files — same validation as /admin/upload.
namestringrequiredOriginal filename (drives the extension and the download name).
sizenumberrequiredExact file size in bytes (max 1073741824). Cloud Storage refuses an upload of any other length.
content_typestringoptionalMIME type. Defaults to application/octet-stream.
sidstringoptionalTarget store id. Required when the request uses a master token (store='*' header); otherwise inferred from the auth context.
idstringoptionalFile document id. Server mints one when omitted; an existing file's id replaces its object in place (the current object stays intact until the new one is complete).
keep_namebooleanoptionalStore the object under a slug of name instead of the id, as on /admin/upload. Two concurrent uploads of the same name never share an object (the later one gets a suffix).
metadataobjectoptionalFree-form key/value map stored on the file doc at finish.
originstringoptionalThe browser origin the chunk PUTs will come from (location.origin) — Cloud Storage answers their CORS against it. Omit for server-side clients.

Response

object — Returns {upload, id, path, storage_path, session: {uri, chunk_bytes, expires}} — upload is the session id finish / abort take, path the file doc finish will write.

Sample request

{
  "dir": "video",
  "name": "showreel.mp4",
  "size": 734003200,
  "content_type": "video/mp4"
}
POST/api/admin/upload/finishwrites data

Finish a resumable upload: checks the object is complete (the declared size, a new object generation) and writes the file doc exactly as /admin/upload does — folder summaries, the files hook and the public URL follow as for any upload. 409 upload_incomplete = not all bytes arrived; resume the PUTs and call again. 404 upload_not_found after a lost response: read the file doc at the path start returned.

Parameters

NameTypeRequiredDescription
uploadstringrequiredThe session id /admin/upload/start returned.
sidstringoptionalTarget store id (master tokens).

Response

object — Returns {path, id, entity} — an image's entity carries width / height, read back from the object's header — the same result as /admin/upload.

Sample request

{
  "upload": "mhb2k1x0AbCdEfGhIjKl"
}
POST/api/admin/folderwrites data

Create an empty folder under store/{sid}/files. Auto-creates every missing ancestor (mkdir -p). Triggers maintain the summary aggregates.

Parameters

NameTypeRequiredDescription
dirstringrequiredRelative folder path. Same validation as /admin/upload's dir. Must not be empty (use a leaf segment).
sidstringoptionalTarget store id. Required for master-token requests; otherwise inferred.
namestringoptionalDisplay name. Defaults to the leaf path segment.

Response

object — Returns {path, type, name} of the leaf folder doc.

Sample request

{
  "dir": "images/products",
  "name": "Products"
}