DigitalChip Platform API Reference
API referenca za DigitalChip platformu
Complete Cloud Functions API — IoT gateway, mobile client, B2B, tenant and admin callables.
Kompletan Cloud Functions API — IoT gateway, mobilni klijent, B2B, tenant i admin callable funkcije.
Overview
Pregled
Backend is Firebase Cloud Functions (v2). There are two transport styles:
Backend su Firebase Cloud Functions (v2). Postoje dva tipa transporta:
-
HTTP
onRequest: Plain REST (e.g. IoTgatewayPing) — custom headers, JSON body, HTTP status codes. Običan REST (npr. IoTgatewayPing) — custom headeri, JSON body, HTTP status kodovi. -
HTTPS Callable
onCall: Firebase Auth ID token automatically attached by the client SDK. Payload wrapped as{ "data": { ... } }; response underresult. Firebase Auth ID token automatski šalje client SDK. Payload u{ "data": { ... } }; odgovor uresult.
Base URL pattern for HTTP functions:
Osnovni URL obrazac za HTTP funkcije:
https://us-central1-yosh-digitalchip.cloudfunctions.net/<functionName>
API Catalog
Katalog API-ja
| Function | Type | Who | Ko | Purpose | Svrha |
|---|---|---|---|---|---|
gatewayPing |
POST | IoT controller | IoT kontroler | Heartbeat + fetch pending commands (still fully supported) | Heartbeat + preuzimanje komandi (i dalje potpuno podržano) |
gatewayStream |
GET/POST SSE | IoT controller | IoT kontroler | Outbound push stream for commands (no static IP) | Outbound push stream za komande (bez statične IP) |
MQTT + mqttAuth |
MQTT / POST | IoT + EMQX | IoT + EMQX | Recommended push for larger fleets (~500 devices) | Preporučeni push za veće flote (~500 uređaja) |
activateService |
onCall | USER / guest | USER / gost | Spend tokens & queue START_SERVICE | Potroši tokene i stavi START_SERVICE u red |
getUserProfile |
onCall | Any signed-in user | Bilo koji ulogovani | Read own profile | Čitanje vlastitog profila |
updateUserProfile |
onCall | Any signed-in user | Bilo koji ulogovani | Update name & phone | Ažuriranje imena i telefona |
changeUserPassword |
onCall | Email/password users | Email/lozinka nalozi | Change password securely | Sigurna promjena lozinke |
onboardEmployee |
onCall | B2B_ADMIN / SUPER_ADMIN | B2B_ADMIN / SUPER_ADMIN | Create/link employee account | Kreiranje/povezivanje zaposlenog |
dispenseTokens |
onCall | B2B_ADMIN / SUPER_ADMIN | B2B_ADMIN / SUPER_ADMIN | Company wallet → employee wallet | Prenos kompanija → zaposleni |
getCompanySpendingReport |
onCall | B2B_ADMIN / SUPER_ADMIN | B2B_ADMIN / SUPER_ADMIN | Corporate DEBIT spend report | Izvještaj potrošnje kompanije |
listVehicles / createVehicle / updateVehicle / deactivateVehicle |
onCall | Owner / B2B_ADMIN / SUPER | Vlasnik / B2B_ADMIN / SUPER | Personal or company fleet CRUD | Lična / kompanijska flota |
getVehicleWashStats |
onCall | B2B_ADMIN / SUPER_ADMIN | B2B_ADMIN / SUPER_ADMIN | Per-vehicle wash / token stats | Statistika pranja po vozilu |
listWashHistory |
onCall | Signed-in user | Ulogovani korisnik | Wash history (+ optional vehicle filter) | Istorija pranja (+ filter vozila) |
createTenantWithAdmin |
onCall | SUPER_ADMIN | SUPER_ADMIN | Create tenant + TENANT_ADMIN login | Kreiranje tenanta + TENANT_ADMIN |
rotateDeviceApiKey |
onCall | SUPER_ADMIN / TENANT_ADMIN | SUPER_ADMIN / TENANT_ADMIN | Rotate parent device API key | Rotacija API ključa uređaja |
getEarningsReport |
onCall | SUPER_ADMIN / TENANT_ADMIN | SUPER_ADMIN / TENANT_ADMIN | Earnings (gross / per-tenant fee / net) | Zarada (bruto / fee po tenantu / neto) |
creditOwnWallet |
onCall | Any signed-in user | Bilo koji ulogovani | Credit / replace own wallet balance | Dopuna / zamjena vlastitog stanja |
updateSpendingRules |
onCall | B2B_ADMIN / SUPER_ADMIN | B2B_ADMIN / SUPER_ADMIN | Update company spending rules | Ažuriranje pravila potrošnje |
createB2bCompany |
onCall | SUPER_ADMIN | SUPER_ADMIN | Create B2B company + wallet | Kreiranje B2B kompanije + novčanik |
deleteB2bCompany |
onCall | SUPER_ADMIN | SUPER_ADMIN | Delete company & unlink employees | Brisanje kompanije i odvajanje zaposlenih |
assignCompanyAdmin |
onCall | SUPER_ADMIN | SUPER_ADMIN | Promote user to B2B_ADMIN | Postavljanje B2B_ADMIN |
removeCompanyEmployee |
onCall | B2B_ADMIN / SUPER_ADMIN | B2B_ADMIN / SUPER_ADMIN | Unlink employee from company | Uklanjanje zaposlenog iz kompanije |
registerDevice |
onCall | SUPER_ADMIN / TENANT_ADMIN | SUPER_ADMIN / TENANT_ADMIN | Register parent device (+ apiKey) | Registracija uređaja (+ apiKey) |
registerSubDevice |
onCall | SUPER_ADMIN / TENANT_ADMIN | SUPER_ADMIN / TENANT_ADMIN | Register bay / sub-device | Registracija bay / poduređaja |
deleteDevice / deleteSubDevice |
onCall | SUPER_ADMIN / TENANT_ADMIN | SUPER_ADMIN / TENANT_ADMIN | Delete device or sub-device | Brisanje uređaja ili poduređaja |
linkDeviceToTenant |
onCall | SUPER_ADMIN | SUPER_ADMIN | Move device to another tenant | Premještanje uređaja na drugi tenant |
adminUpdateUser |
onCall | SUPER_ADMIN | SUPER_ADMIN | Update role / company / tenant link | Uloga / kompanija / tenant veza |
creditUserWallet |
onCall | SUPER_ADMIN | SUPER_ADMIN | Credit any user wallet | Dopuna bilo kojeg novčanika |
deleteUserAccount |
onCall | SUPER_ADMIN | SUPER_ADMIN | Delete Auth user + profile + wallet | Brisanje Auth + profil + novčanik |
deleteTenantCascade |
onCall | SUPER_ADMIN | SUPER_ADMIN | Cascade delete tenant + devices | Kaskadno brisanje tenanta + uređaja |
IoT Device Gateway Ping
Pingovanje IoT uređaja (Gateway)
The physical controller on-site reports its telemetry and can fetch pending relay commands. This endpoint remains fully supported. For lower-latency command delivery without a static IP, also open gatewayStream.
Fizički kontroler na lokaciji prijavljuje telemetriju i može preuzeti zapovijedi za releje. Ovaj endpoint ostaje potpuno podržan. Za bržu isporuku komandi bez statične IP, otvorite i gatewayStream.
Authentication required
Obavezna autentifikacija
Every ping must include the parent device API key in the HTTP header. The key is generated automatically when the controller is registered and is unique per parent device (not per sub-device).
Svaki ping mora sadržati API ključ parent uređaja u HTTP headeru. Ključ se automatski generiše pri registraciji kontrolera i jedinstven je po parent uređaju (ne po poduređaju).
Required Headers
Obavezni headeri
| Header | Type | Requirement | Obaveznost | Description | Opis |
|---|---|---|---|---|---|
Content-Type |
String | RequiredObavezno | application/json |
||
X-Device-Api-Key |
String | RequiredObavezno | Secret API key stored on the parent device document (devices/{deviceId}.apiKey). Alias header X-Api-Key is also accepted. |
Tajni API ključ sa parent device dokumenta (devices/{deviceId}.apiKey). Prihvata se i alias header X-Api-Key. |
curl -X POST https://us-central1-yosh-digitalchip.cloudfunctions.net/gatewayPing \
-H "Content-Type: application/json" \
-H "X-Device-Api-Key: dc_dev_wash_01_api_key_demo_2026" \
-d '{
"deviceId": "dev_wash_01",
"status": "ONLINE",
"subDevices": [
{ "id": "bay_1", "status": "ACTIVE" },
{ "id": "bay_2", "status": "INACTIVE" }
]
}'
Request Payload
Tijelo zahtjeva (Request Payload)
| Field | Type | Requirement | Obaveznost | Description | Opis |
|---|---|---|---|---|---|
deviceId |
String | RequiredObavezno | Unique hardware UUID or MAC address. | Jedinstveni UUID ili MAC adresa kontrolera. | |
status |
String | RequiredObavezno | "ONLINE" | "OFFLINE" |
||
subDevices |
Array | RequiredObavezno | Current state list of connected sub-modules (e.g. wash bays, washing machines). | Trenutno stanje povezanih jedinica (mjesta za pranje, releja). | |
subDevices[].id |
String | RequiredObavezno | Sub-device identifier (e.g., "bay_1"). | ID poduređaja (npr. "bay_1"). | |
subDevices[].status |
String | RequiredObavezno | "ACTIVE" | "INACTIVE" | "ERROR" |
{
"deviceId": "dev_wash_01",
"status": "ONLINE",
"subDevices": [
{ "id": "bay_1", "status": "ACTIVE" },
{ "id": "bay_2", "status": "INACTIVE" }
]
}
Gateway Response
Odgovor mrežnog prolaza
| Field | Type | Description | Opis |
|---|---|---|---|
timestamp |
String | Server ISO timestamp. | Serverska ISO vremenska oznaka. |
commands |
Array | List of active activation commands (maximum 5). Status automatically changes to SENT on delivery. | Lista aktivnih zapovijedi za aktivaciju (najviše 5). Status automatski prelazi u SENT. |
commands[].id |
String | Command ID token. | ID oznaka zapovijedi. |
commands[].action |
String | "START_SERVICE" or "STOP_SERVICE" |
|
commands[].subDeviceId |
String | Specific sub-device port/bay. | Port/relej poduređaja koji treba pokrenuti. |
commands[].value |
Number | Token amount / service units (calculated pulses) to apply on the device. | Količina tokena / jedinica usluge (izraženo u impulsima) za uređaj. |
{
"timestamp": "2026-07-14T10:52:45.312Z",
"commands": [
{
"id": "cmd_8xL92kPz1",
"action": "START_SERVICE",
"subDeviceId": "bay_1",
"value": 50
}
]
}
{
"timestamp": "2026-07-14T10:52:48.125Z",
"commands": []
}
IoT Command Stream (gatewayStream)
IoT stream komandi (gatewayStream)
Outbound Server-Sent Events (SSE) stream. The controller opens a long-lived connection to the cloud (works behind NAT / without a static IP). When a wash is activated, pending commands are pushed immediately using the same JSON shape as gatewayPing. Keep gatewayPing for telemetry / online status (and as command fallback).
Outbound Server-Sent Events (SSE) stream. Kontroler otvara dugotrajnu konekciju ka cloudu (radi iza NAT-a / bez statične IP). Kad se aktivira pranje, PENDING komande se šalju odmah u istom JSON formatu kao gatewayPing. Zadržite gatewayPing za telemetriju / online status (i kao fallback za komande).
Also accepts POST with JSON body {"deviceId":"..."}. Response Content-Type: text/event-stream.
Prihvata i POST sa JSON tijelom {"deviceId":"..."}. Odgovor Content-Type: text/event-stream.
Why not a device webhook URL?
Zašto ne webhook URL na uređaju?
Most controllers have no public/static IP. The cloud cannot reliably POST to the device. The device must open an outbound connection instead.
Većina kontrolera nema javnu/statičnu IP. Cloud ne može pouzdano POST-ovati na uređaj. Uređaj mora otvoriti outbound konekciju.
Required Headers
Obavezni headeri
| Header | Type | Requirement | Obaveznost | Description | Opis |
|---|---|---|---|---|---|
X-Device-Api-Key |
String | RequiredObavezno | Same parent-device API key as gatewayPing. Alias X-Api-Key accepted. |
Isti API ključ parent uređaja kao za gatewayPing. Prihvata se i X-Api-Key. |
curl -N "https://us-central1-yosh-digitalchip.cloudfunctions.net/gatewayStream?deviceId=dev_wash_01" \
-H "X-Device-Api-Key: dc_dev_wash_01_api_key_demo_2026" \
-H "Accept: text/event-stream"
SSE event format
Format SSE događaja
Each command push is one SSE data: line whose JSON matches the gatewayPing response body exactly:
Svaki push komandi je jedna SSE data: linija čiji JSON je identičan tijelu odgovora gatewayPing:
data: {"timestamp":"2026-07-30T04:52:45.312Z","commands":[{"id":"cmd_8xL92kPz1","action":"START_SERVICE","subDeviceId":"bay_1","value":50}]}
: keepalive 2026-07-30T04:53:00.000Z
commands— same fields as ping (id,action,subDeviceId,value). MarkedSENTwhen pushed.commands— ista polja kao ping (id,action,subDeviceId,value). Označene kaoSENTkad se pushaju.- Comment lines (
: keepalive …) every ~15s — ignore them; use to detect a live connection. - Komentar linije (
: keepalive …) svakih ~15s — ignorišite ih; služe za detekciju žive veze. - After ~55 minutes the server may send
{"type":"reconnect",…}and close — reconnect immediately. - Nakon ~55 minuta server može poslati
{"type":"reconnect",…}i zatvoriti vezu — odmah se ponovo povežite.
Recommended firmware pattern
Preporučeni pattern u firmveru
-
Open
gatewayStreamand keep it open; on disconnect, reconnect with backoff. OtvoritegatewayStreami držite otvorenim; na disconnect reconnect sa backoff-om. -
On each
data:JSON with a non-emptycommandsarray, execute commands exactly as forgatewayPing. Na svakidata:JSON sa nepraznimcommands, izvršite komande isto kao zagatewayPing. -
Continue
gatewayPingfor bay status / Live Tracking (every 3s still OK; with stream you may slow ping to 30–60s if desired). NastavitegatewayPingza status boksova / Live Tracking (3s i dalje OK; uz stream možete usporiti ping na 30–60s).
MQTT command delivery (recommended for fleets)
MQTT isporuka komandi (preporučeno za flote)
For larger fleets (tens to hundreds of controllers), prefer MQTT over polling every 3s or long-lived SSE. The device opens an outbound MQTT connection to a managed broker (e.g. EMQX Cloud). DigitalChip publishes commands when a wash is activated — same JSON as gatewayPing. Keep a slower gatewayPing for bay telemetry / Live Tracking.
Za veće flote (desetine do stotine kontrolera) preferirajte MQTT umjesto pinga svake 3s ili dugog SSE. Uređaj otvara outbound MQTT konekciju ka managed brokeru (npr. EMQX Cloud). DigitalChip publish-uje komande kad se aktivira pranje — isti JSON kao gatewayPing. Zadržite sporiji gatewayPing za telemetriju boksova / Live Tracking.
Architecture
Arhitektura
activateService / stopService → Firestore PENDING command → Cloud Function onDeviceCommandCreated publishes to topic → device executes. If MQTT is not configured or publish fails, the command stays/reverts to PENDING and gatewayPing / gatewayStream still deliver it.
activateService / stopService → Firestore PENDING komanda → Cloud Function onDeviceCommandCreated publish na topic → uređaj izvršava. Ako MQTT nije podešen ili publish padne, komanda ostaje/vrati se u PENDING i isporučuju je gatewayPing / gatewayStream.
1. Broker setup (EMQX Cloud)
1. Podešavanje brokera (EMQX Cloud)
-
Create an EMQX Cloud deployment (Serverless is fine to start). Note the
mqtts://…:8883endpoint. Kreirajte EMQX Cloud deployment (Serverless je OK za početak). Zabilježitemqtts://…:8883endpoint. -
Create a publisher user (e.g.
digitalchip_publisher) with ACL allowing publish todigitalchip/+/commands. Kreirajte publisher korisnika (npr.digitalchip_publisher) sa ACL-om koji dozvoljava publish nadigitalchip/+/commands. -
Add an HTTP authentication authenticator pointing at
mqttAuth(below) so devices log in withdeviceId+apiKey. Dodajte HTTP authentication authenticator kamqttAuth(ispod) da se uređaji prijave sadeviceId+apiKey. -
Device ACL (in EMQX): each client may subscribe only to
digitalchip/${username}/commands(and usually not publish). ACL uređaja (u EMQX): klijent smije samo subscribe nadigitalchip/${username}/commands(obično bez publish-a).
2. DigitalChip Cloud params
2. DigitalChip Cloud parametri
Set environment params for Cloud Functions (file functions/.env.yosh-digitalchip or Google Cloud Console → Cloud Functions / Cloud Run env):
Postavite env parametre za Cloud Functions (fajl functions/.env.yosh-digitalchip ili Google Cloud Console → Cloud Functions / Cloud Run env):
MQTT_URL=mqtts://xxxxx.emqxsl.com:8883
MQTT_USERNAME=digitalchip_publisher
MQTT_PASSWORD=your_publisher_password
If MQTT_URL is empty, MQTT publish is skipped (no error) — ping/stream keep working.
Ako je MQTT_URL prazan, MQTT publish se preskače (bez greške) — ping/stream i dalje rade.
3. HTTP auth endpoint for EMQX
3. HTTP auth endpoint za EMQX
EMQX sends device credentials. DigitalChip checks devices/{username}.apiKey.
EMQX šalje kredencijale uređaja. DigitalChip provjerava devices/{username}.apiKey.
| Field | Description | Opis |
|---|---|---|
username |
Must equal registered deviceId. |
Mora biti registrovani deviceId. |
password |
Parent device apiKey (same as X-Device-Api-Key on ping). |
Parent apiKey (isti kao X-Device-Api-Key na pingu). |
{
"username": "dev_wash_01",
"password": "dc_…",
"clientid": "dev_wash_01-fw"
}
{
"result": "allow",
"is_superuser": false,
"acl": [
{ "permission": "allow", "action": "subscribe", "topic": "digitalchip/dev_wash_01/commands" },
{ "permission": "deny", "action": "all", "topic": "#" }
]
}
Deny / unknown device → {"result":"deny"} (HTTP 200 — EMQX convention).
Deny / nepoznat uređaj → {"result":"deny"} (HTTP 200 — EMQX konvencija).
4. Device MQTT client
4. MQTT klijent na uređaju
| Setting | Postavka | Value |
|---|---|---|
| Broker URL | Broker URL | mqtts://…:8883 (from EMQX) |
| Username | Korisničko ime | deviceId |
| Password | Lozinka | apiKey |
| Subscribe topic | Topic za subscribe | digitalchip/{deviceId}/commands |
| QoS | QoS | 1 recommended |
| Client ID | Client ID | {deviceId}-fw |
5. Message payload (same as gatewayPing)
5. Payload poruke (isti kao gatewayPing)
UTF-8 JSON body on the topic (not wrapped in SSE). Process exactly like a ping response:
UTF-8 JSON na topicu (nije SSE). Obrada identična odgovoru na ping:
{
"timestamp": "2026-07-30T05:52:45.312Z",
"commands": [
{
"id": "cmd_8xL92kPz1",
"action": "START_SERVICE",
"subDeviceId": "bay_1",
"value": 50
}
]
}
- On successful MQTT delivery the command is marked
SENTwithdeliveredVia: "mqtt". - Na uspješan MQTT delivery komanda ide u
SENTsadeliveredVia: "mqtt". - Deduplicate by
commands[].id(ping/stream/MQTT must not double-fire the same relay). - Deduplikujte po
commands[].id(ping/stream/MQTT ne smiju dvaput okiniti isti relej). - Reconnect with backoff if the broker drops; keep
gatewayPingat 30–60s for online + bay status. - Reconnect sa backoff-om ako broker padne; držite
gatewayPingna 30–60s za online + status boksova.
6. Cloud Functions involved
6. Uključene Cloud Functions
| Function | Type | Role | Uloga |
|---|---|---|---|
mqttAuth |
HTTP POST | EMQX HTTP authenticator | EMQX HTTP authenticator |
onDeviceCommandCreated |
Firestore trigger | Publish on new PENDING command | Publish na novu PENDING komandu |
Firmware Integration Flow
Tok integracije na kontroleru
Recommended stack: MQTT for commands + gatewayPing every 30–60s for telemetry. gatewayStream and 3s ping remain fully supported fallbacks.
Preporučeni stack: MQTT za komande + gatewayPing svakih 30–60s za telemetriju. gatewayStream i 3s ping ostaju potpuno podržani fallback-ovi.
-
Boot & Connect
Pokretanje i mrežna veza:
Connect to network (Wi-Fi/LTE), load the provisioned
deviceId+apiKeyfrom secure storage, and sync time. Povežite se na mrežu (Wi-Fi/LTE), učitajte dodijeljenideviceId+apiKeyiz sigurnog skladišta i sinhronizujte sat ako je moguće. -
MQTT (recommended)
MQTT (preporučeno):
Connect to the broker with username=
deviceId, password=apiKey. Subscribe todigitalchip/{deviceId}/commands. On message, parse JSON and runcommandslike a ping response. See MQTT section. Konektujte se na broker sa username=deviceId, password=apiKey. Subscribe nadigitalchip/{deviceId}/commands. Na poruku parsirajte JSON i izvršitecommandskao kod ping odgovora. Vidi MQTT sekciju. -
Command stream SSE (optional fallback)
SSE stream (opcioni fallback):
Open
gatewayStreamif MQTT is unavailable. Process pushedcommandsthe same way. OtvoritegatewayStreamako MQTT nije dostupan. Obradjujte pushanecommandsisto. -
Poll Loop (required for telemetry)
Petlja pinga (obavezna za telemetriju):
POST
gatewayPingwith headerX-Device-Api-Keyand JSON body (controller + sub-device status). StampslastPing/lastSeenAt(Live Tracking: online if last ping < ~3 minutes). With MQTT,30–60 secondsis enough;3 secondsremains supported. POSTgatewayPingsa headeromX-Device-Api-Keyi JSON tijelom (stanje kontrolera i boksova). UpisujelastPing/lastSeenAt(Live Tracking: online ako je zadnji ping < ~3 minuta). Uz MQTT dovoljno je30–60 sekundi;3 sekundeostaje podržano. -
Command Processing
Obrada zapovijedi:
- If `commands` is not empty (from MQTT, ping, or stream), loop through all received command items.
- Ako niz `commands` nije prazan (iz MQTT, pinga ili streama), prođite kroz sve zapovijedi u petlji.
- For each command, map the `subDeviceId` to the physical relay port and trigger the relay to generate
valuepulses (or run for `value` seconds). - Za svaku zapovijed, povežite `subDeviceId` sa odgovarajućim fizičkim portom releja i aktivirajte ga tako da generiše
valueimpulsa (ili radi `value` sekundi). - Do NOT trigger multiple overlapping activations on the same relay; queue them internally or ignore if busy. Deduplicate by command
id. - NEMOJTE pokretati više preklapajućih aktivacija na istom releju istovremeno; skladištite ih u internom redu ili ignorišite ako je relej zauzet. Deduplikujte po
idzapovijedi.
-
Completion
Završetak:
There is no callback needed to confirm execution. The server marks commands as
SENTwhen delivering them via MQTT, ping, or stream. Nema potrebe za povratnom potvrdom. Server označava zapovijedi kaoSENTkad ih isporuči preko MQTT, pinga ili streama.
Device Command Safety
Bezbjednost rada uređaja
Ensure that in case of watchdog reset or network interruption, the controller fails safely (all relays opened/off). The device should handle deduplication by ignoring commands with `id`s it has recently executed.
Osigurajte da u slučaju resetovanja kontrolera (watchdog) ili nestanka mrežne veze svi releji odu u bezbjedno isključeno stanje (open/off). Kontroler bi trebao da ignoriše zapovijedi sa ID-jevima koje je već izvršio u skorije vrijeme.
API key errors
Greške API ključa
Missing or wrong X-Device-Api-Key returns 401 Unauthorized. An unregistered deviceId returns 404. Unknown devices are never auto-created from a ping.
Nedostajući ili pogrešan X-Device-Api-Key vraća 401 Unauthorized. Neregistrovan deviceId vraća 404. Nepoznati uređaji se nikad ne kreiraju automatski iz pinga.
Mobile Service Activation
Aktivacija usluge sa mobilne aplikacije
Triggered by the mobile client app to deduct wallet balance and queue a pending command. Supports corporate B2B rules engines (time schedules, allowed days, and daily user limits).
Pokreće se iz mobilne aplikacije radi skidanja tokena sa novčanika i dodavanja zapovijedi na čekanje. Podržava B2B kontrolu limita (dozvoljeni dani, vrijeme rada, dnevni limiti po zaposlenom).
Authentication Header Required
Potrebna autorizacija
Requires a valid Firebase ID Token in the header: Authorization: Bearer <TOKEN>. Also, the calling user UID must match the userId parameter in the payload.
Zahtijeva ispravan Firebase ID Token u zaglavlju: Authorization: Bearer <TOKEN>. Takođe, UID ulogovanog korisnika mora odgovarati parametru userId.
Request Structure (Wrapped in a "data" property)
Struktura zahtjeva (Umotana u "data" objekat)
| Field | Type | Requirement | Obaveznost | Description | Opis |
|---|---|---|---|---|---|
data.userId |
String | RequiredObavezno | The UID of the user purchasing the service. | UID korisnika koji aktivira uslugu. | |
data.tenantId |
String | RequiredObavezno | Associated branch location/tenant ID. | ID lokacije/poslovnice. | |
data.deviceId |
String | RequiredObavezno | IoT Gateway controller ID. | ID mrežnog prolaza (kontrolera). | |
data.subDeviceId |
String | RequiredObavezno | Target relay module (e.g. bay_1). | ID specifičnog modula/releja koji se uključuje. | |
data.tokensToSpend |
Number | RequiredObavezno | Quantity of tokens to spend from the wallet. | Broj tokena koje korisnik troši. | |
data.qrSessionId |
String | RequiredObavezno | From createQrSession (300s / 5 min presence). |
Iz createQrSession (300s / 5 min prisustvo). |
|
data.vehicleId |
String | ConditionalUslovno | Optional. If set, must be an active vehicle in the caller’s scope (company fleet or personal). | Obavezno ako postoji ≥1 aktivno vozilo u opsegu (flota ili lično). |
{
"data": {
"userId": "UserUID_12345",
"deviceId": "dev_wash_01",
"subDeviceId": "bay_1",
"tokensToSpend": 5,
"qrSessionId": "sess_abc123",
"vehicleId": "veh_xyz"
}
}
Response Structure (Returned under a "result" property)
Struktura odgovora (Vraća se unutar "result" objekta)
| Field | Type | Description | Opis |
|---|---|---|---|
result.success |
Boolean | Indicates if the balance was successfully debited and the command queued. | Označava da li je transakcija uspješno prošla i zapovijed upisana u bazu. |
result.message |
String | Transaction output summary message. | Tekstualna poruka o statusu aktivacije. |
result.commandId |
String | The created command ID document. | ID kreirane zapovijedi u bazi podataka. |
result.value |
Number | Calculated pulse count based on tokens spent and sub-device calibration factor. | Izračunati broj impulsa na osnovu potrošenih tokena i kalibracije uređaja. |
result.remainingBalance |
Number | New wallet token balance. | Preostalo stanje tokena u novčaniku nakon skidanja. |
{
"result": {
"success": true,
"message": "Service activated successfully.",
"commandId": "cmd_act_912384x",
"value": 50,
"remainingBalance": 45
}
}
Calling HTTPS Callable functions
Pozivanje HTTPS Callable funkcija
All onCall functions require a valid Firebase Auth session. Native apps use the Firebase SDK (Android/iOS/Flutter). Raw HTTP clients must send:
Sve onCall funkcije zahtijevaju Firebase Auth sesiju. Native app koristi Firebase SDK. Raw HTTP klijenti šalju:
Authorization: Bearer <ID_TOKEN>Content-Type: application/json- Body:
{ "data": { ...params } }
import { getFunctions, httpsCallable } from "firebase/functions";
const fn = httpsCallable(getFunctions(), "getUserProfile");
const { data } = await fn({});
getUserProfile
getUserProfile
Returns the authenticated user's profile. No request fields required.
Vraća profil ulogovanog korisnika. Nema obaveznih polja u zahtjevu.
{
"success": true,
"profile": {
"userId": "abc123",
"name": "Marko Marković",
"email": "marko@alphacorp.com",
"phone": "+38761111222",
"role": "USER",
"b2bCompanyId": "comp_alpha",
"tenantId": null,
"isGuest": false,
"emailVerified": false,
"providers": ["password"]
}
}
updateUserProfile
updateUserProfile
Updates own name and phone. Cannot change role, email, or org links.
Ažurira vlastito name i phone. Ne mijenja ulogu, email ni org veze.
| Field | Type | Required | Obavezno | Notes | Napomena |
|---|---|---|---|---|---|
data.name |
String | Yes | 1–100 chars | ||
data.phone |
String | Optional | Max 40 chars (empty string allowed) | Max 40 znakova (prazan string OK) |
{
"data": {
"name": "Petar Petrović",
"phone": "+38761123456"
}
}
changeUserPassword
changeUserPassword
Verifies currentPassword via Identity Toolkit, then sets newPassword (min 6). Guests / anonymous / non-password providers are rejected.
Provjerava currentPassword preko Identity Toolkit-a, zatim postavlja newPassword (min 6). Gosti / anonimni / bez password providera se odbijaju.
| Field | Type | Required | Obavezno |
|---|---|---|---|
data.currentPassword |
String | Yes | |
data.newPassword |
String | Yes (≥ 6) |
{
"data": {
"currentPassword": "oldSecret",
"newPassword": "newSecret123"
}
}
onboardEmployee
onboardEmployee
B2B_ADMIN (own company) or SUPER_ADMIN. Creates Auth + Firestore user + wallet if needed, links to company as USER.
B2B_ADMIN (svoja kompanija) ili SUPER_ADMIN. Kreira Auth + Firestore + novčanik ako treba, veže na kompaniju kao USER.
| Field | Type | Required | Obavezno |
|---|---|---|---|
data.companyId | String | Yes | |
data.email | String | Yes | |
data.name | String | Yes | |
data.phone | String | Yes (≥ 6) | |
data.password | String | Yes (≥ 6) |
{
"data": {
"companyId": "comp_alpha",
"email": "ana@alphacorp.com",
"name": "Ana Anić",
"phone": "+38761123456",
"password": "Password123!"
}
}
{
"success": true,
"userId": "uid_...",
"created": true,
"alreadyLinked": false,
"email": "ana@alphacorp.com"
}
dispenseTokens
dispenseTokens
Moves tokens from corporate wallet to employee wallet (atomic). B2B_ADMIN of that company or SUPER_ADMIN.
Prenosi tokene s korporativnog na zaposlenički novčanik (atomski). B2B_ADMIN te kompanije ili SUPER_ADMIN.
| Field | Type | Required | Obavezno |
|---|---|---|---|
data.fromCompanyId | String | Yes | |
data.toUserId | String | Yes | |
data.amount | Number | Yes (> 0) |
{
"data": {
"fromCompanyId": "comp_alpha",
"toUserId": "employeeUid",
"amount": 50
}
}
{
"success": true,
"newBalance": 450,
"employeeBalance": 50
}
getCompanySpendingReport
getCompanySpendingReport
Lists corporate-wallet DEBIT transactions (who / when / where) plus per-user totals.
Lista DEBIT transakcija korporativnog novčanika (ko / kada / gdje) i totali po korisniku.
| Field | Type | Required | Obavezno |
|---|---|---|---|
data.companyId | String | Yes | |
data.limit | Number | Optional (default 200, max 500) |
{
"data": { "companyId": "comp_alpha", "limit": 100 }
}
{
"success": true,
"companyId": "comp_alpha",
"companyName": "Alpha Corp",
"totals": { "gross": 120, "transactionCount": 8 },
"users": [
{ "userId": "...", "name": "...", "email": "...", "gross": 40, "transactionCount": 3 }
],
"transactions": [
{
"id": "...",
"userId": "...",
"userName": "...",
"amount": 5,
"tenantId": "tenant_wash_central",
"tenantName": "...",
"deviceId": "dev_wash_01",
"subDeviceId": "bay_1",
"subDeviceName": "Bay 1",
"timestamp": "2026-07-15T12:00:00.000Z"
}
]
}
createTenantWithAdmin
createTenantWithAdmin
SUPER_ADMIN only. Creates tenant, TENANT_ADMIN Auth user, wallets (tenant balance starts at 0 — tenants sell services).
Samo SUPER_ADMIN. Kreira tenant, TENANT_ADMIN Auth nalog, novčanike (tenant balance počinje na 0 — tenant prodaje usluge).
| Field | Type | Required | Obavezno |
|---|---|---|---|
data.tenantId | String | Yes | |
data.tenantName | String | Yes | |
data.adminName | String | Yes | |
data.adminEmail | String | Yes | |
data.adminPassword | String | Yes (≥ 6) | |
data.industryType | String | Optional — CAR_WASH | LAUNDRY | EV_CHARGING | |
data.currency | String | Optional — TOKEN | BAM | EUR | RSD | USD | |
data.address | String | Optional | |
data.jib | String | Optional | |
data.platformFeePercent | Number | Optional — 0–100, default 15 |
{
"data": {
"tenantId": "tenant_eco_wash",
"tenantName": "Eco Wash Center",
"industryType": "CAR_WASH",
"adminName": "Petar Petrović",
"adminEmail": "admin@ecowash.com",
"adminPassword": "Password123!",
"currency": "BAM",
"address": "Ulica 1, Sarajevo",
"jib": "4200123450001",
"platformFeePercent": 12.5
}
}
updateTenantFeeTerms
updateTenantFeeTerms
SUPER_ADMIN only. Sets tenants/{tenantId}.platformFeePercent (0–100). Used by getEarningsReport. Clients cannot write this field (Firestore rules).
Samo SUPER_ADMIN. Postavlja tenants/{tenantId}.platformFeePercent (0–100). Koristi getEarningsReport. Klijent ne može pisati ovo polje (Firestore rules).
// Request
{ "data": { "tenantId": "tenant_eco_wash", "platformFeePercent": 10 } }
// Response
{ "success": true, "tenantId": "tenant_eco_wash", "platformFeePercent": 10 }
rotateDeviceApiKey
rotateDeviceApiKey
SUPER_ADMIN or owning TENANT_ADMIN. Issues a new apiKey for devices/{deviceId}. Old key stops working on gatewayPing.
SUPER_ADMIN ili vlasnički TENANT_ADMIN. Izdaje novi apiKey za devices/{deviceId}. Stari ključ prestaje raditi na gatewayPing.
// Request
{ "data": { "deviceId": "dev_wash_01" } }
// Response
{ "success": true, "deviceId": "dev_wash_01", "apiKey": "dc_a1b2c3..." }
getEarningsReport
getEarningsReport
Aggregates DEBIT spend by tenant → device → sub-device. Fee = each tenant’s platformFeePercent (default 15% if missing). Totals sum per-tenant fee math. SUPER_ADMIN (optional tenantId filter) or TENANT_ADMIN (own tenant only).
Agregira DEBIT potrošnju tenant → uređaj → poduređaj. Fee = platformFeePercent po tenantu (podrazumevano 15% ako nedostaje). Totals su zbir po tenantima. SUPER_ADMIN (opcionalni filter tenantId) ili TENANT_ADMIN (samo svoj tenant).
{
"data": { "tenantId": "tenant_wash_central" }
}
{
"success": true,
"platformFeePercent": 12.5,
"defaultPlatformFeePercent": 15,
"transactionCount": 42,
"totals": { "gross": 1000, "platformFee": 125, "net": 875, "transactionCount": 42 },
"tenants": [
{
"tenantId": "tenant_wash_central",
"tenantName": "Wash Central",
"platformFeePercent": 12.5,
"gross": 1000,
"platformFee": 125,
"net": 875,
"transactionCount": 42,
"devices": [
{
"deviceId": "dev_wash_01",
"gross": 1000,
"platformFee": 125,
"net": 875,
"platformFeePercent": 12.5,
"subDevices": [
{ "subDeviceId": "bay_1", "name": "Bay 1", "gross": 600, "platformFee": 75, "net": 525, "platformFeePercent": 12.5 }
]
}
]
}
]
}
Vehicles API (mobile-ready)
API vozila (za mobilnu app)
Collection /vehicles/{vehicleId}. Client writes denied — all mutations via callables. Wash ledger stores vehicleId + vehiclePlate on DEBIT txs.
Kolekcija /vehicles/{vehicleId}. Client write zabranjen — mutacije preko callable. Ledger čuva vehicleId + vehiclePlate na DEBIT tx.
listVehicles
{ "companyId": "comp_alpha" } // omit → personal vehicles
// includeInactive?: boolean
// Auth: personal owner; company members; SUPER_ADMIN
createVehicle
{ "plate": "A12-B-345", "label": "Van 1", "companyId": "comp_alpha" }
// omit companyId → personal vehicle for caller
// companyId → B2B_ADMIN of company or SUPER_ADMIN
updateVehicle / deactivateVehicle
{ "vehicleId": "...", "plate": "...", "label": "...", "active": true }
{ "vehicleId": "..." } // deactivateVehicle → active:false
getVehicleWashStats
{ "companyId": "comp_alpha" }
// B2B_ADMIN / SUPER → per-vehicle washCount, tokensSpent, lastWashAt
listWashHistory
{ "vehicleId": "optional", "limit": 50 }
// Caller DEBITs (personal wallet or company wallet where userId=caller)
// Filter by vehicleId when set
Admin / B2B / Device Mutations
Admin / B2B / Device mutacije
All platform writes used by the PWA (and native apps) go through these callables — not direct Firestore client writes.
Sve platformerske mutacije koje koristi PWA (i native appovi) idu preko ovih callable funkcija — ne direktnim Firestore write-ovima sa klijenta.
Auth
Auth
Same as other onCall: Firebase ID token. Role checks happen server-side.
Isto kao i ostali onCall: Firebase ID token. Provjera uloge je na serveru.
createB2bCompany
{
"companyId": "comp_alpha",
"name": "Alpha Corp",
"adminEmail": "admin@alphacorp.com",
"initialBalance": 500,
"dailyLimit": 50,
"currency": "BAM",
"address": "Ulica 1",
"jib": "4200123450001"
}
updateSpendingRules
{
"companyId": "comp_alpha",
"rules": {
"startTime": "08:00",
"endTime": "20:00",
"dailyLimit": 50,
"allowedDays": [1, 2, 3, 4, 5]
}
}
registerDevice
{
"deviceId": "dev_wash_02",
"tenantId": "tenant_wash_central",
"firmwareVersion": "1.0.0",
"status": "OFFLINE"
}
/* Response includes server-generated apiKey */
registerSubDevice
{
"deviceId": "dev_wash_02",
"subDeviceId": "bay_3",
"name": "Bay 3",
"qrCodePayload": "bay_3",
"pulsePerToken": 10
}
adminUpdateUser / creditUserWallet / creditOwnWallet
/* adminUpdateUser — SUPER_ADMIN */
{ "userId": "...", "role": "B2B_ADMIN", "b2bCompanyId": "comp_alpha", "tenantId": null }
/* creditUserWallet — SUPER_ADMIN */
{ "userId": "...", "amount": 100 }
/* creditOwnWallet — any signed-in user */
{ "amount": 10, "replace": false }
Other mutation IDs
Ostali mutacijski ID-jevi
deleteB2bCompany · assignCompanyAdmin · removeCompanyEmployee · deleteDevice · deleteSubDevice · linkDeviceToTenant · deleteUserAccount · deleteTenantCascade · updateTenantFeeTerms
Error Handling & Status Codes
Rukovanje greškama i statusni kodovi
HTTP gatewayPing / gatewayStream
HTTP gatewayPing / gatewayStream
200 OK— Ping OK; inspectcommands.Ping OK; pregledajcommands.400— Invalid payload.Neispravan payload.401— Missing/invalidX-Device-Api-Key.Nedostaje/neispravanX-Device-Api-Key.404— Unknown device (devices are not auto-created from ping).Nepoznat uređaj (ping ne kreira uređaj automatski).405— Method not POST.Metoda nije POST.500— Server error — retry with backoff.Serverska greška — ponovi sa backoff-om.
HTTPS Callable (onCall)
HTTPS Callable (onCall)
Errors throw Firebase HttpsError. Client SDKs expose code and message.
Greške bacaju Firebase HttpsError. Client SDK vraća code i message.
| code | Typical meaning | Značenje |
|---|---|---|
unauthenticated | Missing / expired ID token | Nedostaje / istekao ID token |
permission-denied | Wrong role or wrong company/tenant scope | Pogrešna uloga ili scope kompanije/tenanta |
invalid-argument | Missing / invalid fields | Nedostaju / neispravna polja |
not-found | User, company, device, or wallet missing | Nedostaje user, kompanija, uređaj ili novčanik |
failed-precondition | Business rule (hours, guest password, etc.) | Poslovno pravilo (radno vrijeme, gost lozinka…) |
already-exists | Tenant ID or email already taken | Tenant ID ili email već postoji |
resource-exhausted | Daily B2B limit / too many attempts | Dnevni B2B limit / previše pokušaja |
internal | Unexpected server failure | Neočekivana greška servera |