> ## Documentation Index
> Fetch the complete documentation index at: https://docs.coreflux.org/llms.txt
> Use this file to discover all available pages before exploring further.

# Firebase Realtime Database and Cloud Firestore

> Client-mode REST API connectors for Firebase: write device state, append events, and query history in the Realtime Database, and upsert, merge, and query Cloud Firestore documents from MQTT

Technical Preview

## Overview

Firebase has two databases with different REST APIs and different hosts, so this package ships **two connectors** that you can deploy together or on their own:

* `FirebaseRealtimeDatabaseRestApi` talks to the [Realtime Database REST API](https://firebase.google.com/docs/database/rest/start) on `https://<database>.firebaseio.com`. Any path plus `.json` is an endpoint, and plain JSON goes in and out.
* `CloudFirestoreRestApi` talks to the [Cloud Firestore REST API](https://firebase.google.com/docs/firestore/use-rest-api) on `https://firestore.googleapis.com`. Documents use Firestore typed values.

### Realtime Database events

| Connector event | Method and endpoint | Notes |
| - | - | - |
| `SetDeviceState` | `PUT /{path}.json` | Write or replace the value at a path, such as `devices/press-01/state`. [Save data](https://firebase.google.com/docs/database/rest/save-data) |
| `AppendEvent` | `POST /{path}.json` | Append a child with a generated push key. Returns `{"name":"-P38..."}`. |
| `UpdateFields` | `PATCH /{path}.json` | Merge only the given children. |
| `ReadPath` | `GET /{path}.json` | Read the value at a path. |
| `QueryLatest` | `GET /{path}.json?orderBy=...&limitToLast=N` | Latest N children ordered by `$key` or an indexed child. [Retrieve data](https://firebase.google.com/docs/database/rest/retrieve-data) |

Topics use `data/firebase/rtdb/<object>/<verb>`.

### Cloud Firestore events

| Connector event | Method and endpoint | Notes |
| - | - | - |
| `UpsertDocument` | `PATCH /v1/projects/{p}/databases/(default)/documents/{document_path}` | Create or replace a document. [patch](https://firebase.google.com/docs/firestore/reference/rest/v1/projects.databases.documents/patch) |
| `MergeFields` | Same, with `?updateMask.fieldPaths=...` | Update only the listed fields and keep the rest. |
| `CreateDocument` | `POST .../documents/{collection}` | New document with a generated ID. [createDocument](https://firebase.google.com/docs/firestore/reference/rest/v1/projects.databases.documents/createDocument) |
| `GetDocument` | `GET .../documents/{document_path}` | Read one document. |
| `ListDocuments` | `GET .../documents/{collection}?pageSize=N` | List a collection page. |
| `RunQuery` | `POST .../documents:runQuery` | Structured query, such as all events for one device. [runQuery](https://firebase.google.com/docs/firestore/reference/rest/v1/projects.databases.documents/runQuery) |

Topics use `data/firebase/firestore/<object>/<verb>`. Replies for both connectors arrive on `<topic>/response`.

## Auth

Both connectors send `Authorization: Bearer {value.json.access_token}`. Mint the token outside the connector (a Python action, edge service, or SDK) and keep the service account key or token in Hub secrets, for example `FIREBASE_ACCESS_TOKEN`. Never commit keys.

* **Realtime Database:** a Google OAuth2 access token from a service account with scopes `https://www.googleapis.com/auth/userinfo.email` and `https://www.googleapis.com/auth/firebase.database`. The API also accepts the token as `?access_token=`, and a Firebase Auth ID token as `?auth=<ID_TOKEN>`; edit the endpoints if you use those. See [Authenticate REST requests](https://firebase.google.com/docs/database/rest/auth).
* **Cloud Firestore:** a Google OAuth2 access token for a service account with a Datastore role such as `roles/datastore.user` (scope `https://www.googleapis.com/auth/datastore` or `cloud-platform`). Service account tokens bypass security rules. A Firebase Auth ID token also works and is checked against your rules.

Access tokens expire after about an hour, so refresh them before they lapse.

## Basic Syntax

### Realtime Database

```lot wrap theme={"theme":"css-variables","languages":{"custom":["/languages/lot.json"]}}
DEFINE ROUTE FirebaseRealtimeDatabaseRestApi WITH TYPE REST_API
    ADD REST_API_CONFIG
        WITH BASE_ADDRESS "https://YOUR_DATABASE-default-rtdb.firebaseio.com"
        WITH ENABLE_CLIENT "true"
        WITH USE_SSL "true"
        WITH REQUEST_TIMEOUT '60'
        WITH DEFAULT_CONTENT_TYPE "application/json"
        # Smoke: GET /devices.json?shallow=true with Bearer → 200 (401 Permission denied = token/rules)
    ADD EVENT SetDeviceState
        WITH SOURCE_TOPIC "data/firebase/rtdb/state/set"
        WITH DESTINATION_TOPIC "data/firebase/rtdb/state/set/response"
        WITH METHOD "PUT"
        WITH ENDPOINT "/{value.json.path}.json"
        WITH HEADERS
            ADD "Accept" "application/json"
            ADD "Content-Type" "application/json"
            ADD "Authorization" "Bearer {value.json.access_token}"
        WITH BODY "{value.json.data}"
    ADD EVENT AppendEvent
        WITH SOURCE_TOPIC "data/firebase/rtdb/event/append"
        WITH DESTINATION_TOPIC "data/firebase/rtdb/event/append/response"
        WITH METHOD "POST"
        WITH ENDPOINT "/{value.json.path}.json"
        WITH HEADERS
            ADD "Accept" "application/json"
            ADD "Content-Type" "application/json"
            ADD "Authorization" "Bearer {value.json.access_token}"
        WITH BODY "{value.json.data}"
    ADD EVENT UpdateFields
        WITH SOURCE_TOPIC "data/firebase/rtdb/fields/update"
        WITH DESTINATION_TOPIC "data/firebase/rtdb/fields/update/response"
        WITH METHOD "PATCH"
        WITH ENDPOINT "/{value.json.path}.json"
        WITH HEADERS
            ADD "Accept" "application/json"
            ADD "Content-Type" "application/json"
            ADD "Authorization" "Bearer {value.json.access_token}"
        WITH BODY "{value.json.data}"
    ADD EVENT ReadPath
        WITH SOURCE_TOPIC "data/firebase/rtdb/path/get"
        WITH DESTINATION_TOPIC "data/firebase/rtdb/path/get/response"
        WITH METHOD "GET"
        WITH ENDPOINT "/{value.json.path}.json"
        WITH HEADERS
            ADD "Accept" "application/json"
            ADD "Authorization" "Bearer {value.json.access_token}"
    ADD EVENT QueryLatest
        # order_by must be URL-encoded and quoted, e.g. %22%24key%22 ("$key") or %22ts%22 ("ts", needs .indexOn rule)
        WITH SOURCE_TOPIC "data/firebase/rtdb/path/query"
        WITH DESTINATION_TOPIC "data/firebase/rtdb/path/query/response"
        WITH METHOD "GET"
        WITH ENDPOINT "/{value.json.path}.json?orderBy={value.json.order_by}&limitToLast={value.json.limit}"
        WITH HEADERS
            ADD "Accept" "application/json"
            ADD "Authorization" "Bearer {value.json.access_token}"
```

### Cloud Firestore

```lot wrap theme={"theme":"css-variables","languages":{"custom":["/languages/lot.json"]}}
DEFINE ROUTE CloudFirestoreRestApi WITH TYPE REST_API
    ADD REST_API_CONFIG
        WITH BASE_ADDRESS "https://firestore.googleapis.com"
        WITH ENABLE_CLIENT "true"
        WITH USE_SSL "true"
        WITH REQUEST_TIMEOUT '60'
        WITH DEFAULT_CONTENT_TYPE "application/json"
        # Smoke: GET /v1/projects/{p}/databases/(default)/documents/devices?pageSize=1 → 200
    ADD EVENT UpsertDocument
        WITH SOURCE_TOPIC "data/firebase/firestore/document/upsert"
        WITH DESTINATION_TOPIC "data/firebase/firestore/document/upsert/response"
        WITH METHOD "PATCH"
        WITH ENDPOINT "/v1/projects/{value.json.project_id}/databases/(default)/documents/{value.json.document_path}"
        WITH HEADERS
            ADD "Accept" "application/json"
            ADD "Content-Type" "application/json"
            ADD "Authorization" "Bearer {value.json.access_token}"
        WITH BODY "{value.json.document}"
    ADD EVENT MergeFields
        WITH SOURCE_TOPIC "data/firebase/firestore/document/merge"
        WITH DESTINATION_TOPIC "data/firebase/firestore/document/merge/response"
        WITH METHOD "PATCH"
        WITH ENDPOINT "/v1/projects/{value.json.project_id}/databases/(default)/documents/{value.json.document_path}?{value.json.update_mask}"
        WITH HEADERS
            ADD "Accept" "application/json"
            ADD "Content-Type" "application/json"
            ADD "Authorization" "Bearer {value.json.access_token}"
        WITH BODY "{value.json.document}"
    ADD EVENT CreateDocument
        WITH SOURCE_TOPIC "data/firebase/firestore/document/create"
        WITH DESTINATION_TOPIC "data/firebase/firestore/document/create/response"
        WITH METHOD "POST"
        WITH ENDPOINT "/v1/projects/{value.json.project_id}/databases/(default)/documents/{value.json.collection}"
        WITH HEADERS
            ADD "Accept" "application/json"
            ADD "Content-Type" "application/json"
            ADD "Authorization" "Bearer {value.json.access_token}"
        WITH BODY "{value.json.document}"
    ADD EVENT GetDocument
        WITH SOURCE_TOPIC "data/firebase/firestore/document/get"
        WITH DESTINATION_TOPIC "data/firebase/firestore/document/get/response"
        WITH METHOD "GET"
        WITH ENDPOINT "/v1/projects/{value.json.project_id}/databases/(default)/documents/{value.json.document_path}"
        WITH HEADERS
            ADD "Accept" "application/json"
            ADD "Authorization" "Bearer {value.json.access_token}"
    ADD EVENT ListDocuments
        WITH SOURCE_TOPIC "data/firebase/firestore/documents/list"
        WITH DESTINATION_TOPIC "data/firebase/firestore/documents/list/response"
        WITH METHOD "GET"
        WITH ENDPOINT "/v1/projects/{value.json.project_id}/databases/(default)/documents/{value.json.collection}?pageSize={value.json.page_size}"
        WITH HEADERS
            ADD "Accept" "application/json"
            ADD "Authorization" "Bearer {value.json.access_token}"
    ADD EVENT RunQuery
        WITH SOURCE_TOPIC "data/firebase/firestore/documents/query"
        WITH DESTINATION_TOPIC "data/firebase/firestore/documents/query/response"
        WITH METHOD "POST"
        WITH ENDPOINT "/v1/projects/{value.json.project_id}/databases/(default)/documents:runQuery"
        WITH HEADERS
            ADD "Accept" "application/json"
            ADD "Content-Type" "application/json"
            ADD "Authorization" "Bearer {value.json.access_token}"
        WITH BODY "{value.json.query}"
```

Set `BASE_ADDRESS` on the Realtime Database connector to your instance URL: `https://<database>-default-rtdb.firebaseio.com` for US instances, or `https://<database>-default-rtdb.<region>.firebasedatabase.app` elsewhere. The Firestore connector targets the `(default)` database; edit the endpoints for a named database.

## Payload examples

### Realtime Database

Set device state, with a server-side timestamp, on `data/firebase/rtdb/state/set`:

```json theme={"theme":"css-variables","languages":{"custom":["/languages/lot.json"]}}
{"access_token": "ya29....", "path": "devices/press-01/state", "data": {"state": "RUN", "temp": 21.5, "lastSeen": {".sv": "timestamp"}}}
```

Append an event on `data/firebase/rtdb/event/append`:

```json theme={"theme":"css-variables","languages":{"custom":["/languages/lot.json"]}}
{"access_token": "ya29....", "path": "devices/press-01/events", "data": {"ts": 1791155235, "code": "E2"}}
```

Merge one field on `data/firebase/rtdb/fields/update`:

```json theme={"theme":"css-variables","languages":{"custom":["/languages/lot.json"]}}
{"access_token": "ya29....", "path": "devices/press-01/state", "data": {"state": "IDLE"}}
```

Latest two events by `ts` on `data/firebase/rtdb/path/query` (`order_by` is URL-encoded and quoted, here `"ts"`):

```json theme={"theme":"css-variables","languages":{"custom":["/languages/lot.json"]}}
{"access_token": "ya29....", "path": "devices/press-01/events", "order_by": "%22ts%22", "limit": 2}
```

Ordering by a child needs an index rule such as `".indexOn": ["ts"]`; ordering by `%22%24key%22` (`"$key"`) needs none.

### Cloud Firestore

Upsert a device document on `data/firebase/firestore/document/upsert`:

```json theme={"theme":"css-variables","languages":{"custom":["/languages/lot.json"]}}
{"access_token": "ya29....", "project_id": "my-project", "document_path": "devices/press-01",
 "document": {"fields": {"state": {"stringValue": "RUN"}, "temp": {"doubleValue": 21.5}, "count": {"integerValue": "42"}}}}
```

Merge two fields on `data/firebase/firestore/document/merge`:

```json theme={"theme":"css-variables","languages":{"custom":["/languages/lot.json"]}}
{"access_token": "ya29....", "project_id": "my-project", "document_path": "devices/press-01",
 "update_mask": "updateMask.fieldPaths=state&updateMask.fieldPaths=lastSeen",
 "document": {"fields": {"state": {"stringValue": "IDLE"}, "lastSeen": {"timestampValue": "2026-10-05T00:10:00Z"}}}}
```

Create an event document on `data/firebase/firestore/document/create`:

```json theme={"theme":"css-variables","languages":{"custom":["/languages/lot.json"]}}
{"access_token": "ya29....", "project_id": "my-project", "collection": "events",
 "document": {"fields": {"deviceId": {"stringValue": "press-01"}, "ts": {"integerValue": "1791155235"}}}}
```

Query events for one device on `data/firebase/firestore/documents/query`:

```json theme={"theme":"css-variables","languages":{"custom":["/languages/lot.json"]}}
{"access_token": "ya29....", "project_id": "my-project",
 "query": {"structuredQuery": {"from": [{"collectionId": "events"}],
   "where": {"fieldFilter": {"field": {"fieldPath": "deviceId"}, "op": "EQUAL", "value": {"stringValue": "press-01"}}}, "limit": 5}}}
```

Firestore integers are strings (`"integerValue": "42"`), and timestamps are RFC 3339 (`timestampValue`).

## Errors and limits

* **Realtime Database:** `401 {"error":"Permission denied"}` means a missing or bad token or a rules denial. `404 {"error":"404 Not Found"}` usually means the `BASE_ADDRESS` instance URL is wrong. Add `print=silent` to write endpoints to get `204 No Content` instead of the echoed data.
* **Cloud Firestore:** `401 UNAUTHENTICATED` (bad or expired token), `403 PERMISSION_DENIED` (missing role, rules, or API not enabled on the project), `404 NOT_FOUND`, `400 INVALID_ARGUMENT` (bad typed value or mask). `MergeFields` needs a non-empty `update_mask`; send an upsert instead if you want to replace the whole document.
* Firestore writes to a single document are limited to about one per second sustained; spread high-rate telemetry across documents or use the Realtime Database.
* Realtime Database streaming (Server-Sent Events) is not covered by these client connectors.

## Testing

Tested on 2026-10-05 (PT):

* **Firebase Local Emulator Suite** (firebase-tools, Realtime Database and Firestore emulators): all 11 connector events returned `200` with the exact methods, endpoints, headers and bodies the connectors send, and requests without a token were rejected by security rules (`401` and `403`).
* **Real Realtime Database:** `GET` on a public Firebase instance returned `200` for path reads and `orderBy`/`limitToFirst` queries; `PUT`, `POST` and `PATCH` without credentials returned `401 Permission denied`.
* **Real Firestore:** requests reached `firestore.googleapis.com` and returned `401 UNAUTHENTICATED` for an invalid token.

Not yet tested: a real Firebase project with a service account token, and an MQTT round-trip through a deployed broker connector.

## Related

* [REST API connectors](/latest/lot-language/routes/rest-api-routes)
* [REST API app catalog](/latest/lot-language/routes/rest-api-apps/index)
* [Firebase Cloud Messaging](/latest/lot-language/routes/rest-api-apps/firebase-cloud-messaging)


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.