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 | deprecated for Digi controllers. Old gateway controllers may still heartbeat and pull commands here. | deprecated za Digi kontrolere. Stari gateway kontroleri i dalje mogu ovdje slati heartbeat i vući komande. |
gatewayStream |
GET/POST SSE | IoT controller | IoT kontroler | deprecated Old command push. Digi controllers do not use it. | deprecated Stari push komandi. Digi kontroleri ga ne koriste. |
MQTT + mqttAuth |
MQTT / POST | IoT + EMQX | IoT + EMQX | deprecated Old EMQX command topic. Not for mobile apps. Digi pay stays inside activateService. | deprecated Stari EMQX topic komandi. Nije za mobilne aplikacije. Digi uplata ostaje unutar activateService. |
activateService |
onCall | USER / guest | USER / gost | Spend tokens and start the machine. Digi devices are confirmed inside this call. | Potroši tokene i pokreni mašinu. Digi uređaji se potvrđuju unutar ovog poziva. |
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 deprecated
Pingovanje IoT uređaja (Gateway) deprecated
Deprecated for Digi controllers
Zastarjelo za Digi kontrolere
deprecated Digi controllers do not use gatewayPing for commands, bay status, or Live Tracking. Readiness is checked inside createQrSession and again inside activateService. Online in Live Tracking follows the last Digi status (fresh if younger than about 3 minutes). This endpoint remains only for controllers that still use the old gateway channel.
deprecated Digi kontroleri ne koriste gatewayPing za komande, status automata ni Live Tracking. Spremnost se provjerava unutar createQrSession i ponovo unutar activateService. Online u Live Trackingu prati zadnji Digi status (svjež ako je mlađi od oko 3 minute). Endpoint ostaje samo za kontrolere na starom gateway kanalu.
The physical controller on-site reports its telemetry. Controllers that are not on the Digi channel can still fetch pending relay commands here.
Fizički kontroler na lokaciji prijavljuje telemetriju. Kontroleri koji nisu na Digi kanalu i dalje mogu ovdje preuzeti komande za releje.
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) deprecated
IoT stream komandi (gatewayStream) deprecated
Deprecated
Zastarjelo
Do not use gatewayStream for new washing-machine firmware or for mobile apps. Digi payments are confirmed inside activateService. This stream remains only for controllers that still consume the old PENDING command queue.
Ne koristite gatewayStream za novi firmware praonica ni za mobilne aplikacije. Digi uplate se potvrđuju unutar activateService. Stream ostaje samo za kontrolere koji još troše stari PENDING red komandi.
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).
EMQX command MQTT deprecated
EMQX MQTT komandi deprecated
Deprecated — not the mobile API
Zastarjelo — nije mobilni API
This EMQX topic (digitalchip/{deviceId}/commands) and mqttAuth are the old command channel. Mobile apps must not connect to any broker. New Digi firmware is reached only by Cloud Functions when the app calls activateService. Broker host and port are not published here.
Ovaj EMQX topic (digitalchip/{deviceId}/commands) i mqttAuth su stari kanal komandi. Mobilne aplikacije ne smiju da se konektuju na broker. Novi Digi firmware se doseže samo iz Cloud Functions kad aplikacija pozove activateService. Host i port brokera se ovdje ne objavljuju.
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 deprecated
Tok integracije na kontroleru deprecated
Old gateway stack
Stari gateway stack
deprecated This flow (gatewayPing, gatewayStream, EMQX topic digitalchip/{deviceId}/commands) is the previous controller channel. Digi firmware does not follow it. Mobile apps never implement this section.
deprecated Ovaj tok (gatewayPing, gatewayStream, EMQX topic digitalchip/{deviceId}/commands) je prethodni kanal kontrolera. Digi firmware ga ne prati. Mobilne aplikacije ne implementiraju ovu sekciju.
Previous stack, only if the controller is still on the gateway channel: EMQX MQTT for commands plus gatewayPing every 30–60s for telemetry. gatewayStream and a 3s ping are older fallbacks, also deprecated.
Prethodni stack, samo ako je kontroler još na gateway kanalu: EMQX MQTT za komande plus gatewayPing svakih 30–60s za telemetriju. gatewayStream i ping na 3s su stariji fallback, takođe deprecated.
-
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):
deprecated for Digi. Old gateway controllers POST
gatewayPingwithX-Device-Api-Key. That stampslastPing. Live Tracking counts those devices online only when that ping is younger than about 3 minutes. Digi devices ignore this ping. deprecated za Digi. Stari gateway kontroleri šalju POSTgatewayPingsaX-Device-Api-Key. To upisujelastPing. Live Tracking te uređaje broji online samo kad je taj ping mlađi od oko 3 minute. Digi uređaji ignorišu ovaj ping. -
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.
Native external app contract
Ugovor za eksternu native aplikaciju
Android, iOS and other clients use the Firebase project yosh-digitalchip, region us-central1. They never open MQTT. Every wash goes through the callables below. Direct Firestore writes to wallets, QR sessions, vehicles and commands are denied.
Android, iOS i ostali klijenti koriste Firebase projekat yosh-digitalchip, region us-central1. Ne otvaraju MQTT. Svako pranje ide kroz callable funkcije ispod. Direktan Firestore upis u novčanike, QR sesije, vozila i komande je zabranjen.
Sign-in
Prijava
| Method | Način | How | Kako |
|---|---|---|---|
| Email and password | E-mail i lozinka | Firebase Auth SDK. Then call callables with the ID token. | Firebase Auth SDK. Zatim callable pozivi sa ID tokenom. |
| Guest | Gost | Anonymous Auth. Must buy tokens before a wash and spend the full purchased balance in one activation. | Anonimni Auth. Mora kupiti tokene prije pranja i potrošiti cijeli kupljeni saldo u jednoj aktivaciji. |
b2bCodeLogin |
b2bCodeLogin |
Callable, no auth header. Body { "data": { "company": "Acme", "code": "123456" } }. Result token is a Firebase custom token. Sign in with signInWithCustomToken, then use that session. |
Callable, bez auth zaglavlja. Tijelo { "data": { "company": "Acme", "code": "123456" } }. token u odgovoru je Firebase custom token. Prijavite se sa signInWithCustomToken, pa koristite tu sesiju. |
QR
QR
The printed code is unchanged: deviceId:subDeviceId. Parse those two ids and send them to the callables. Do not invent a second hardware id.
Odštampani kod se ne mijenja: deviceId:subDeviceId. Raspakujte ta dva id-a i pošaljite ih u callable. Ne uvodite drugi hardverski id.
Wash sequence
Redoslijed pranja
createQrSessionwithdeviceIdandsubDeviceId. On a Digi controller this call asks the machine if that bay is ready before it returnssessionId(300 seconds). Wait for it (about 5 seconds).failed-preconditionmeans the bay is notACTIVE.deadline-exceededorunavailablemeans status did not arrive; do not continue to pay.createQrSessionsadeviceIdisubDeviceId. Na Digi kontroleru ovaj poziv pita mašinu da li je taj automat spreman prije nego vratisessionId(300 sekundi). Sačekajte (oko 5 sekundi).failed-preconditionznači da automat nijeACTIVE.deadline-exceedediliunavailableznači da status nije stigao; ne nastavljajte na uplatu.activateServicewithuserIdequal to the signed-in uid,tokensToSpend,qrSessionId, optionalvehicleId, andusePersonalWallet: truewhen a B2B employee pays from a personal wallet.activateServicesauserIdjednakim uid-u prijave,tokensToSpend,qrSessionId, opcionovehicleId, iusePersonalWallet: truekad B2B zaposleni plaća iz ličnog novčanika.- On Digi controllers wait for the callable to finish (about 5 seconds).
success: truemeans the machine confirmed and tokens were deducted.remainingBalanceis the new balance. - Na Digi kontrolerima sačekajte kraj callable-a (oko 5 sekundi).
success: trueznači da je mašina potvrdila i da su tokeni skinuti.remainingBalanceje novo stanje. - On
deadline-exceededorunavailable, show the message and retry the sameqrSessionId. The wallet was not charged. - Na
deadline-exceedediliunavailableprikažite poruku i ponovite istiqrSessionId. Novčanik nije naplaćen. cancelQrSessionwith{ "data": { "sessionId" } }if the user leaves before paying.cancelQrSessionsa{ "data": { "sessionId" } }ako korisnik ode prije plaćanja.
Reads the app may do itself
Čitanja koja aplikacija smije sama
users/{uid}— profile. PrefergetUserProfile,updateUserProfile,changeUserPassword,deleteMyProfile.users/{uid}— profil. Prednost imajugetUserProfile,updateUserProfile,changeUserPassword,deleteMyProfile.wallets/{uid}fieldbalance, andwallets/{uid}/transactionsfor history. There is no separate wallet callable. Company employees readwallets/{b2bCompanyId}only when rules allow.wallets/{uid}poljebalance, iwallets/{uid}/transactionsza istoriju. Nema posebnog wallet callable-a. Zaposleni čitajuwallets/{b2bCompanyId}samo kad pravila dozvole.- Vehicles: call
listVehicles,createVehicle,updateVehicle,deactivateVehicle,listWashHistory. Clients can read/vehiclesbut cannot write it. - Vozila: zovite
listVehicles,createVehicle,updateVehicle,deactivateVehicle,listWashHistory. Klijent može čitati/vehicles, ali ne može pisati.
Not for the native wash app
Nije za native aplikaciju pranja
digiActivateService is private. creditOwnWallet is a sandbox top-up, not production checkout. stopService still queues an old STOP_SERVICE command and does nothing on Digi firmware. gatewayPing, gatewayStream and EMQX MQTT are device APIs, not app APIs.
digiActivateService je privatan. creditOwnWallet je sandbox dopuna, nije produkcijsko plaćanje. stopService i dalje stavlja staru STOP_SERVICE komandu i na Digi firmwareu ne radi ništa. gatewayPing, gatewayStream i EMQX MQTT su API-ji uređaja, ne aplikacije.
Activation messages
Poruke aktivacije
failed-precondition message is one of: QR_SESSION_REQUIRED, QR_SESSION_INVALID, QR_SESSION_CONSUMED, QR_SESSION_EXPIRED, QR_SESSION_MISMATCH, GUEST_PURCHASE_REQUIRED, GUEST_MUST_SPEND_ALL, GUEST_PERSONAL_WALLET_ONLY, PERSONAL_WALLET_DISABLED, Insufficient tokens in wallet., or the machine error text. Map the codes in the app. Show unknown text as-is.
failed-precondition poruka je jedna od: QR_SESSION_REQUIRED, QR_SESSION_INVALID, QR_SESSION_CONSUMED, QR_SESSION_EXPIRED, QR_SESSION_MISMATCH, GUEST_PURCHASE_REQUIRED, GUEST_MUST_SPEND_ALL, GUEST_PERSONAL_WALLET_ONLY, PERSONAL_WALLET_DISABLED, Insufficient tokens in wallet., ili tekst greške mašine. Mapirajte kodove u aplikaciji. Nepoznat tekst prikažite kakav jeste.
Mobile apps and machine MQTT
Mobilne aplikacije i MQTT mašine
Native and web clients do not open an MQTT connection. The broker address, port and topics live only in Cloud Functions. The app keeps calling the same HTTPS Callable endpoints. On a Digi controller, createQrSession checks that the scanned bay is ready, and activateService checks again and then waits for the machine to confirm payment.
Native i web klijenti ne otvaraju MQTT vezu. Adresa, port i topic-i brokera žive samo u Cloud Functions. Aplikacija i dalje zove iste HTTPS Callable endpoint-e. Na Digi kontroleru createQrSession provjeri da je skenirani automat spreman, a activateService provjeri ponovo i zatim sačeka da mašina potvrdi uplatu.
Do not embed the broker
Ne ugrađujte broker
Do not put a broker host, port, username, or topic in the mobile app. Those values are not part of this API. A client that needs them is calling the wrong layer.
Ne stavljajte host brokera, port, korisničko ime ni topic u mobilnu aplikaciju. To nije dio ovog API-ja. Klijent kojem to treba zove pogrešan sloj.
Endpoints the app calls
Endpoint-i koje aplikacija zove
| Callable | When | Kad | What the Cloud Function does | Šta radi Cloud Function |
|---|---|---|---|---|
createQrSession |
After the QR scan, before pay | Poslije skena QR-a, prije uplate | On a Digi controller, asks the machine whether that bay is ACTIVE, then returns sessionId (300 seconds). The app does not open a broker connection. The same check runs again inside activateService before payment. |
Na Digi kontroleru pita mašinu da li je taj automat ACTIVE, pa vrati sessionId (300 sekundi). Aplikacija ne otvara vezu ka brokeru. Ista provjera ide ponovo unutar activateService prije uplate. |
activateService |
User confirms tokens | Korisnik potvrdi tokene | On a Digi controller: sends the payment through the internal broker link and waits for confirmation (about 5 seconds). Tokens are deducted only after success. On any other controller: the existing command queue, unchanged. | Na Digi kontroleru: pošalje uplatu preko interne brokerske veze i sačeka potvrdu (oko 5 sekundi). Tokeni se skidaju tek nakon uspjeha. Na ostalim kontrolerima: postojeći red komandi, bez izmjene. |
stopService |
User stops a bay | Korisnik zaustavi boks | Still the command queue. The Digi channel has no stop message. | I dalje red komandi. Digi kanal nema poruku za stop. |
Base URL for every callable: https://us-central1-yosh-digitalchip.cloudfunctions.net/<name>. Auth and body shape are in Calling HTTPS Callable functions. activateService fields are in activateService.
Bazni URL svakog callable-a: https://us-central1-yosh-digitalchip.cloudfunctions.net/<name>. Auth i oblik tijela su u Pozivanje HTTPS Callable funkcija. Polja activateService su u activateService.
createQrSession
createQrSession
{
"data": {
"deviceId": "dev_wash_01",
"subDeviceId": "bay_1"
}
}
{
"result": {
"success": true,
"sessionId": "sess_abc123",
"deviceId": "dev_wash_01",
"subDeviceId": "bay_1",
"tenantId": "tenant_wash_central",
"expiresAt": 1710000000000,
"expiresInSec": 300
}
}
Digi result the app must handle
Digi rezultat koji aplikacija mora obraditi
The request and the success body of activateService stay the same. On a Digi controller the call blocks until the machine answers. Success means the machine accepted the payment and tokens were deducted. Failure means nothing was charged and the same qrSessionId can be sent again.
Zahtjev i uspješan odgovor activateService ostaju isti. Na Digi kontroleru poziv traje dok mašina ne odgovori. Uspjeh znači da je mašina prihvatila uplatu i da su tokeni skinuti. Neuspjeh znači da ništa nije naplaćeno i da se isti qrSessionId može poslati ponovo.
| Callable error | Greška callable-a | Meaning for the app | Značenje za aplikaciju |
|---|---|---|---|
deadline-exceeded |
deadline-exceeded |
No confirmation in time. Show the message and allow retry. Wallet unchanged. | Nema potvrde na vrijeme. Prikažite poruku i dozvolite ponovo. Novčanik nije diran. |
failed-precondition |
failed-precondition |
Machine returned an error string, or balance / QR / bay rules failed. Show message. Do not start a second pay unless the user retries. |
Mašina je vratila tekst greške, ili nisu prošla pravila balansa / QR-a / boksa. Prikažite message. Ne šaljite drugu uplatu dok korisnik ne pokuša ponovo. |
unavailable |
unavailable |
Cloud Function could not reach the machine link. Retry later. Wallet unchanged. | Cloud Function nije stigla do veze sa mašinom. Pokušajte kasnije. Novčanik nije diran. |
const functions = getFunctions();
const session = await httpsCallable(functions, "createQrSession")({
deviceId, subDeviceId
});
const started = await httpsCallable(functions, "activateService")({
userId: auth.currentUser.uid,
deviceId,
subDeviceId,
tokensToSpend,
qrSessionId: session.data.sessionId
});
// started.data.success === true, remainingBalance updated
Device status
Status uređaja
Digi controllers report bay readiness through Cloud Functions. There is no status endpoint for the mobile app and no broker credentials in this document. Super-admin sees every controller. A tenant admin sees only controllers assigned to that tenant.
Digi kontroleri javljaju spremnost automata kroz Cloud Functions. Nema status endpoint-a za mobilnu aplikaciju i nema kredencijala brokera u ovom dokumentu. Super-admin vidi svaki kontroler. Tenant admin vidi samo kontrolere dodijeljene tom tenantu.
| Where | Gdje | What you see | Šta vidite |
|---|---|---|---|
QR scan (createQrSession) |
Sken QR-a (createQrSession) |
The scanned bay must be ACTIVE. INACTIVE or ERROR returns failed-precondition and no session. A missing answer returns deadline-exceeded or unavailable. |
Skenirani automat mora biti ACTIVE. INACTIVE ili ERROR vraća failed-precondition i nema sesije. Ako odgovor ne stigne, vraća se deadline-exceeded ili unavailable. |
Payment (activateService) |
Uplata (activateService) |
The same readiness check runs again before payment. Tokens are not deducted when the bay is not ready. | Ista provjera spremnosti ide ponovo prije uplate. Tokeni se ne skidaju kad automat nije spreman. |
| Admin device list | Admin lista uređaja | Column Bay status shows each bay, for example 1: ACTIVE (50). The number in parentheses is the parameter reported with that bay (Wi-Fi strength or another device parameter). |
Kolona Status automata pokazuje svaki automat, npr. 1: ACTIVE (50). Broj u zagradi je parametar uz taj automat (jačina Wi-Fi signala ili drugi parametar uređaja). |
| Live Tracking | Live Tracking | A Digi controller is online when its last status is younger than about 3 minutes. Status is refreshed on a schedule and whenever a QR is scanned. gatewayPing / lastPing do not count for Digi devices. Old gateway controllers are still online from lastPing. |
Digi kontroler je online kad je zadnji status mlađi od oko 3 minute. Status se osvježava po rasporedu i kad se skenira QR. gatewayPing / lastPing se ne broje za Digi uređaje. Stari gateway kontroleri i dalje idu preko lastPing. |
Not a client API
Nije klijentski API
Do not call the machine broker from the app to read status. Use createQrSession. Admin lists and Live Tracking already show the stored result. gatewayPing, gatewayStream and EMQX MQTT are deprecated for this.
Ne zovite broker mašine iz aplikacije da biste pročitali status. Koristite createQrSession. Admin liste i Live Tracking već pokazuju sačuvan rezultat. gatewayPing, gatewayStream i EMQX MQTT su za ovo deprecated.
Mobile Service Activation
Aktivacija usluge sa mobilne aplikacije
Triggered by the mobile client app to deduct wallet balance and start the machine. On a Digi controller the Cloud Function confirms with the machine before charging (see Mobile MQTT via API). Other controllers still queue a command. Supports corporate B2B rules (time schedules, allowed days, and daily user limits).
Pokreće se iz mobilne aplikacije radi skidanja tokena i pokretanja mašine. Na Digi kontroleru Cloud Function prvo dobije potvrdu mašine, pa tek onda naplati (vidi Mobilni MQTT preko API-ja). Ostali kontroleri i dalje stavljaju komandu u red. 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 |
deadline-exceeded | Digi machine did not confirm in time. Safe to retry the same QR session. | Digi mašina nije potvrdila na vrijeme. Isti QR session se smije ponoviti. |
unavailable | Cloud Function could not reach the machine link. Wallet unchanged. | Cloud Function nije stigla do veze sa mašinom. Novčanik nije diran. |
internal | Unexpected server failure | Neočekivana greška servera |