1Which part is yours
| You are building | Read | Identity | What it does |
|---|---|---|---|
| A cashier (POS) system that sends a restaurant's delivery orders to RazGaz | §2 and Part A (§3–§9) | One API key per cashier installation, plus one code per branch | One call per order, POST /ingestOrder. Nothing comes back: no status and no carrier details |
| A restaurant's own management system that runs its deliveries itself instead of the RazGaz web portal | §2 and Part B (§10–§17) | The branch's restaurant account: email and password, exchanged for a token | Opens orders, answers the prep time, books for later, reads the board and the carrier, marks food ready, cancels |
| A delivery or ordering platform that needs status callbacks, bookings or cancellation through a key | Not this document | — | These are partner contracts with their own terms. Ask us for yours |
The two parts are not interchangeable:
- Part A pushes orders in and gets nothing back. One key serves any number of branches. You choose the order's id.
- Part B controls the whole life of an order and sees everything about it. It works for one branch per account. RazGaz chooses the order's id.
Orders a cashier sends through Part A appear on the branch's board in Part B too. The restaurant reads them like its own.
2Common to both parts
2.1Hosts and transport
| RazGaz calls | https://api.raz-gaz.com |
| Transport | HTTPS only. Content-Type: application/json, UTF-8 |
| Part B only | Google's sign-in hosts and the database host, §11 and §14 |
2.2Order statuses
Part A cannot read an order's status. The table is still the vocabulary a RazGaz dispatcher uses when you speak to them. Part B reads it on every order, §14.
status | Meaning |
|---|---|
awaiting_prep_time | Held. RazGaz is waiting for the kitchen to say when the food will be ready. Offered to no carrier, §2.3 |
awaiting_location | Held. The delivery address could not be placed on the map, and a RazGaz dispatcher will place it. Offered to no carrier |
held | Held for a RazGaz dispatcher, who releases it by hand. This happens when the branch is set to manual publishing, when the address matched no delivery price, or when an order is flagged for review. Offered to no carrier. The kitchen has nothing to do — cook as usual |
scheduled | Booked for later, waiting to be released automatically, §13.3 |
pending | Released. Not yet offered to carriers |
broadcasting | Being offered to carriers in the area |
accepted | A carrier took it and is on the way to the branch |
arrived_at_pickup | The carrier is at the branch |
picked_up | The food is with the carrier, on the way to the customer |
arrived_at_delivery | The carrier is at the customer |
delivered | Delivered. Final |
cancelled | Cancelled. Final |
An order moves in that order, but not every state is seen by a reader. Two states can pass between two reads. Treat the status as the current state, not as an event, and build nothing that needs to see every state.
2.3Prep time
The prep time is the minutes from now until the food is ready. It lets RazGaz time
the carrier to arrive at the counter as the food comes out, instead of having
them wait there. Both parts send it as estimatedReadyMinutes: a whole number,
greater than 0 and no more than 240.
When you do not send it, RazGaz uses what it knows about the branch. A branch is in one of three situations:
| The branch | An order without a prep time |
|---|---|
| Is set to give a time per order | Is held at awaiting_prep_time and offered to no carrier until someone answers. Nothing releases it automatically. This applies even when the branch also has an average on record |
| Has an average prep time on record | Uses the average and goes out at once |
| Has neither | Goes out at once with no ready time at all |
The first situation is why a system that knows the prep time should always send it. Doing so removes a manual step that otherwise delays every order.
The area's minimum. When an area is busy, RazGaz sets a minimum prep time for it (the Busy Lag). A time below the minimum is handled differently by each part:
- Part A raises it to the minimum without telling you.
- Part B refuses it with a
400(§12.6). The current minimum is readable, §14.4.
A prep time you did not send, taken from the branch's average, is raised to the minimum in both parts.
2.4Addresses
The address line is street, house number and town, and nothing else.
- Floor, apartment and entrance have their own fields. In Part A they are
inside
deliveryAddressParts. In Part B they are top-level fields. They are shown to the carrier at the building. - Do not put the floor in the address line. When no coordinates are sent, the line is what gets looked up on the map, and a floor inside it degrades the lookup.
- A gate code and the like go in
notes. - A floor, apartment or entrance of
"0"is read as "not given" and is not shown — till systems send"0"for a private house. Write a real ground floor in words.
Send coordinates whenever you have them. A point your system confirmed is better than one we look up. Without coordinates, RazGaz looks the address up itself.
2.5Money
| Field | Meaning |
|---|---|
paymentMethod | "cash" means the carrier collects payment from the customer. Any other value, and a missing field, are recorded as "card", and the carrier collects nothing |
orderAmount | What the customer pays for the order, in shekels, as a JSON number. A value that is not a number is recorded as 0 |
Part A — Cashier systems: sending orders in
3What RazGaz issues you
You receive two things from RazGaz before the first order:
| What it is | Scope | |
|---|---|---|
| API key | A secret string: rg_live_ followed by 48 hexadecimal characters | One key per cashier installation, whatever the number of restaurants it sends for |
| Restaurant code | The code that identifies one branch in your requests, sent as externalId | One per branch. It is your own identifier for the branch. RazGaz records it against the branch, so you do not have to adopt ours |
The key is shown once, when it is created, and cannot be recovered afterwards.
Store it as a secret. RazGaz can revoke any key at any time. After that, every
request that presents it is answered 401, within a minute of the revocation.
4Authentication
Present the key on every request, in either of two ways:
| Method | Where |
|---|---|
| Header (preferred) | x-api-key: <key> |
| Query parameter | ?apiKey=<key> |
If both are present, the header is used.
A request with no key, an unknown key or a revoked key is answered 401, and
nothing is recorded. No order is created, and RazGaz receives no internal
alert. In production, a 401 means the integration is delivering nothing without
anyone noticing. Treat it as a hard failure and escalate it rather than retrying
it forever.
5Call: Add Order
POST https://api.raz-gaz.com/ingestOrder
5.1Request — what must be present
| Field | Type | Description |
|---|---|---|
orderId | string | Your identifier for this order. It becomes the order's identity at RazGaz and is the idempotency key, §7. Keep it stable across retries of the same order. Make it impossible for another system to send the same id: prefix it with your system's name and the branch, for example "acme-102-58817". The id space is shared with other cashier systems, and the same id from two systems is treated as one order |
externalId | string | The restaurant code RazGaz issued you (§3). It identifies which branch the order is collected from |
Location. Each order also needs a delivery point, given in one of two ways:
deliveryLocationas coordinates, or- a non-empty
deliveryAddressfor RazGaz to look up.
pickupLocation may be left out. The branch's stored position is then used. It
is required only for a branch that has no stored position, and that is a setup
fault to raise with us.
Every coordinate you send must be a JSON number, not a string. A request that
fails any of these rules is answered 400 and no order is created.
5.2Request — address and location
| Field | Type | Description |
|---|---|---|
deliveryLocation | object | The drop-off point. { "latitude": number, "longitude": number }. When sent, it is used exactly as sent |
deliveryAddress | string | The delivery address as one readable line: street, house number, town (§2.4). The carrier reads it. Without deliveryLocation, this line is what gets looked up on the map, and it also decides the price of the delivery, so keep the floor and apartment out of it |
deliveryAddressParts | object | The address in parts, all optional: { "city", "street", "homeNumber", "floor", "apartment", "entrance" }, each a string. floor, apartment and entrance are shown to the carrier at the building. city is the town the delivery is priced by — send it whenever you have it. city and street also help locate an address sent without coordinates. Send it alongside deliveryAddress: the line is never built from the parts |
pickupLocation | object | Where the carrier collects. { "latitude": number, "longitude": number }. Usually left out (§5.1) |
pickupAddress | string | The branch address as one readable line |
An address that cannot be placed on the map is still accepted (201). The
order then waits at awaiting_location until a RazGaz dispatcher places it.
Nothing in the response says this happened.
5.3Request — optional fields
| Field | Type | Description |
|---|---|---|
estimatedReadyMinutes | integer | Minutes from now until the food is ready, greater than 0 and no more than 240, §2.3. Send it whenever the cashier system knows it, §8 |
customerName | string | The customer's name, as the carrier should ask for them |
customerPhone | string | A number the carrier can call on arrival |
notes | string | Free text shown to the carrier: delivery instructions, gate codes, "leave at the door" |
items | array | The order's line items, each { "name": string, "qty": number }. RazGaz stores the array as sent, and every screen reads exactly those two keys. A quantity sent under any other key does not show. It may be left out entirely — carriers do not need line items to deliver |
orderAmount | number | §2.5 |
paymentMethod | string | §2.5 |
placedAt | number | string | When the customer placed the order: epoch milliseconds, or an ISO-8601 timestamp. Left out or unreadable, the time of the request is used |
5.4Reserved fields — do not send
The endpoint accepts several more fields that belong to RazGaz's own operations. Sending them from a cashier system is not supported. Some change what the carrier is paid and how far the order is offered, and RazGaz does not check them against the delivery:
estimatedPayout, broadcastRadius, platformOrderRef, regionId,
restaurantId.
scheduledFor is not part of this integration. It is stored and has no
effect: an order sent with a future time is dispatched immediately. Do not send an
order for later until it is due.
restaurantName is accepted as a fallback for identifying the branch when no
externalId is sent or the code matches nothing. It must then match RazGaz's
spelling of the branch name (current or former) character for character. Do not
rely on it — send externalId.
5.5Response
| HTTP | Meaning |
|---|---|
201 | The order was created |
200 | An order with this orderId already existed and was updated, §7 |
Both come with the same body:
{ "success": true, "orderId": "acme-102-58817", "created": true }
orderId is the id you sent, echoed back. created is true on 201 and
false on 200.
Treat any 2xx as accepted. created: false is a normal, successful answer, not
a duplicate error.
5.6Errors
Every error body is { "error": "<message>" }.
| HTTP | Message | Cause | What to do |
|---|---|---|---|
400 | orderId and (externalId or restaurantName) are required | §5.1 | Fix the payload. Retrying it unchanged cannot succeed. RazGaz is alerted internally on every 400 |
400 | deliveryLocation or deliveryAddress is required | No delivery point of either kind | Same |
400 | pickupLocation and deliveryLocation must each have numeric latitude and longitude | A coordinate sent as a string, or missing half | Same |
400 | pickupLocation is required: the restaurant has no stored position | The branch has no stored position | Send pickupLocation, and tell us |
400 | A Hebrew sentence about the prep time | estimatedReadyMinutes is not a positive number, or is more than 240 | Fix the value |
401 | Invalid API key | No key, an unknown key, or a revoked key | Stop and escalate. No internal alert is raised, so RazGaz will not notice for you |
405 | Method not allowed | The request was not a POST | Fix the method |
409 | orderId belongs to an order another sender created | This orderId belongs to an order a delivery partner created. Nothing was changed | Do not retry. Contact RazGaz |
500 | Internal error ingesting order | A fault on the RazGaz side | Retry. RazGaz is alerted internally on every 500 |
A network timeout with no response is indistinguishable from a 500. Handle it
the same way: retry. Retrying is always safe, §7.
6Identifying the restaurant
Send externalId on every order. RazGaz holds your code for each branch against
that branch and keyed to your API key, so:
- your codes need no relation to RazGaz's identifiers or naming;
- two cashier systems may use different codes for the same branch without interfering.
A branch you have not been issued a code for is not integrated yet. An order
with an unknown code is still accepted with 201. It arrives attached to no
restaurant, and RazGaz raises no alert. This is the failure to watch for on the
first order from each new branch: the request looks entirely successful.
If your API key is replaced, the branch codes must be registered against the new key before it is used. Coordinate a key change with RazGaz; do not switch keys on your own.
7Idempotency, retries and corrections
The orderId you send is the order's identity at RazGaz. A second request with
an orderId that already exists is an update, never a second delivery.
What an update does:
- It rewrites the order details you own: both addresses and their parts, both coordinate pairs, the customer's name and phone, the items, the amount, the payment method, the notes, the prep time and the time the order was placed.
- It re-prices the delivery from the new details: the carrier's fee and the restaurant's charge. A changed address therefore changes what the delivery costs, even if a carrier is already on the way.
- It never touches the assignment to a carrier, the delivery's status, or the
timestamps of its progress. The one exception: an order waiting at
awaiting_locationis released once a re-sent address can be placed on the map.
Send the whole order every time. Partial updates are not supported. A field left out of the retry is written as empty, not left as it was, with these exceptions:
- a prep time the kitchen already answered itself is kept;
- a price set by a RazGaz manager is kept;
- if the re-sent address cannot be placed on the map, the previous point and price are kept;
placedAtleft out is set to the time of the retry.
What this means for the cashier system:
- Retrying is always safe, at any interval, for as long as needed. No retry can create a second delivery or move a delivery in progress back.
- A correction is sent the same way: send the whole order again with the same
orderIdand the corrected values. Once a delivery is under way, a person is driving it, so phone RazGaz as well rather than assuming the carrier has seen the change. - Cancellation is not part of this integration. To cancel an order you have already sent, contact RazGaz.
8Prep time
estimatedReadyMinutes is the most useful optional field. It is what lets RazGaz
time the carrier's arrival to the kitchen rather than send them to stand at a
counter.
Left out, the branch's settings decide (§2.3). The case that hurts is a branch set to give a time per order. Its orders are held, undispatched, until someone in that kitchen answers in the RazGaz portal, and nothing releases them automatically. A cashier system that knows its own kitchen should always send the field.
Other ways an order can wait. A 201 means the order was recorded. It does
not mean the order went out to carriers. Three other states keep an order from
carriers, and the response mentions none of them (§2.2):
awaiting_location— the address could not be placed on the map;held— the branch publishes by hand, the address matched no delivery price, or the order was flagged;awaiting_prep_time— the branch gives a time per order and none was sent.
A RazGaz dispatcher sees all three.
9Before going live
- RazGaz has issued the API key and one restaurant code per branch.
- Your
orderIds cannot collide with another system's (§5.1). - Every order carries a delivery point. Coordinates are sent as numbers, or the
address line is street, house number and town, with the floor and apartment in
deliveryAddressParts. - One real order per branch has been sent, and you have confirmed with RazGaz that it arrived attached to the right restaurant. §6 explains why the HTTP response alone does not confirm this.
estimatedReadyMinutesis sent whenever the cashier system can produce it.- Retries on
500and on timeout are in place.400and401are not retried blindly but are shown to whoever can act on them.
Part B — Restaurant systems: running deliveries yourself
10What RazGaz issues you
The RazGaz web portal is a client of a public interface: it opens orders, reads the board and updates orders through it. A restaurant's own management system can call the same interface for everything in this part.
You receive three things from RazGaz before the first order:
| What it is | |
|---|---|
| Email and password | The branch's restaurant account. One account per branch. No single account manages several branches, so a system serving two branches holds two accounts and signs in to each separately |
| Web API key | The public project identifier needed to exchange the password for a token (§11). It is not a secret and grants nothing on its own — the password is the secret |
| Restaurant id | The branch's identifier at RazGaz. It is what you filter on when reading orders (§14). The same value is inside your token, as the restaurantId claim |
There is no branch code and no API key like a cashier system's. On this
interface the account is the identity, and the branch follows from it. A branch
that RazGaz marks inactive cannot open orders (403). Its existing orders stay
readable and keep updating.
11Authentication
11.1Getting a token
POST https://identitytoolkit.googleapis.com/v1/accounts:signInWithPassword?key=<WEB_API_KEY>
{
"email": "branch@example.com",
"password": "…",
"returnSecureToken": true
}
Keep three fields from the response:
| Field | What to do with it |
|---|---|
idToken | The token itself. Sent on every request, §11.3 |
refreshToken | Replaces an expired token, §11.2. Does not expire on its own |
expiresIn | Lifetime in seconds — 3600, one hour |
11.2Refreshing
POST https://securetoken.googleapis.com/v1/token?key=<WEB_API_KEY>, with
grant_type=refresh_token&refresh_token=<refreshToken> encoded as
application/x-www-form-urlencoded. The response carries a new id_token.
Do not sign in with the password on every request. Keep the token in memory
and refresh it before it expires; a minute of headroom is enough. An expired token
is answered 401, and retrying with the same token gets the same answer forever.
11.3Using it
On every request, to every host:
Authorization: Bearer <idToken>
A request with no token, an expired token, or another account's token is answered
401 or 403, and nothing is recorded. No order is opened and nothing is
updated. In production, such an error means the integration is delivering
nothing without anyone noticing. Escalate it rather than retrying.
12Opening an order
POST https://api.raz-gaz.com/createRestaurantOrder
This opens a delivery order for the branch the account is linked to. The pickup point is neither sent nor controllable: it is the branch's stored location. RazGaz prices the carrier's fee, offers the order to carriers in the area, and manages the carrier from assignment to delivery.
12.1Request — the required field
| Field | Type | Description |
|---|---|---|
deliveryAddress | string | The delivery address as one readable line: street, house number and town (§2.4). The carrier reads it. Without coordinates, it is what gets looked up on the map |
That is the only required field. Everything else — the item list and the coordinates included — is optional.
12.2Request — the drop-off point on the map
| Field | Type | Description |
|---|---|---|
deliveryLocation | object | { "latitude": number, "longitude": number }, both values JSON numbers |
Recommended, not required. If your system has coordinates, send them.
Without them, we locate the address ourselves. If the address names no town, we add the branch's own town: "Herzl 5" from a Ra'anana branch is the Herzl in Ra'anana. The stored address keeps your words with the town added, so the carrier reads a complete address.
Three cases to design around:
- An address that cannot be located at all is a
400, with a message in Hebrew. - An address that resolves only to a town gets a point at the centre of that town rather than at the building, and the order opens normally. Nothing in the response tells the two apart. If that precision matters to you, send coordinates.
- A point in a different town from the one the address names is discarded. The address is then looked up from its words. A point is trusted only where it agrees with the address.
12.3Request — optional fields
| Field | Type | Description |
|---|---|---|
items | array | The order's items, each { "name": string, "qty": number }. The carrier sees the list, and it is what a pickup at the counter is checked against. An order without items, or with an empty array, opens normally. A value that is not an array is a 400. A quantity that is missing, 0, or not a number shows as 1 |
estimatedReadyMinutes | integer | Minutes from now until the food is ready, greater than 0 and no more than 240, and not below the area's minimum (§2.3). Send it whenever your system knows the number. On a booking, always send it: nothing asks for it again once the order is booked |
scheduledFor | number | string | A booking: when the carrier should arrive at the restaurant to collect the order, as epoch milliseconds or ISO-8601. The order is held at scheduled, offered to no carrier, and released 20 minutes before that time. It must be in the future and no more than 7 days ahead, or the call is a 400 |
customerName | string | The customer's name, as the carrier should ask for them |
customerPhone | string | A number the carrier can call on arrival. Checked: an Israeli mobile of 10 digits (05…), a landline of 9 (03…), a +972 number, or another country's number starting +. Anything else is a 400. It is stored in a normalised form |
floor | string | The floor, shown to the carrier at the building |
apartment | string | The apartment |
entrance | string | The entrance |
notes | string | Free text shown to the carrier: delivery instructions, "leave at the door" |
orderAmount | number | §2.5 |
paymentMethod | string | §2.5 |
bigOrder | boolean | true for an order too large for one scooter. It is offered to cars first, may be split between two carriers, and is billed as two deliveries. It cannot be changed after the order is opened |
orderSize | number | 2 is the same as bigOrder: true. Any other value is a normal order |
12.4Reserved fields — do not send
The pickup point, the carrier's fee, the broadcast radius, the pricing region and the human-facing order number are all decided by RazGaz. None of them is taken from the request.
12.5Response
201 with:
{ "success": true, "orderId": "…" }
orderId is the order's identity at RazGaz, and the rest of this part uses it.
Store it with the order in your own system. There is no other way to refer to
an order, and no lookup by an identifier of yours.
12.6Errors
Every error body is { "error": "<message>" }.
| HTTP | When |
|---|---|
400 | deliveryAddress is required · items must be an array |
400 (Hebrew) | The prep time is not a positive number or is more than 240; a scheduledFor that is malformed, in the past, or more than 7 days out; a phone number that is not valid |
400 (Hebrew) | The area's minimum prep time. estimatedReadyMinutes is below it. The message names the minimum: send at least that number |
400 (Hebrew) | An address sent without coordinates that could not be placed on the map at all (§12.2) |
400 | Restaurant is missing a pickup location — a setup fault; contact us |
401 | The token is missing, expired or invalid (§11) |
403 | No restaurant is linked to this account · Restaurant is not active |
405 | Not a POST |
12.7A repeated call opens a second order
There is no idempotency on this call. RazGaz generates a new order id on every call, and there is no field for an identifier of yours. RazGaz therefore cannot tell that two requests describe the same order. If you retry after a timeout and the first request did land, a second delivery opens, and two carriers arrive at the same branch.
An automatic retry on this call is your system's responsibility, and it is safe only once you know the first request was not written. To check, read the active orders (§14.1) and look for one with the same address, phone and time. Cancel an order opened twice by mistake per §15.2.
13Prep time and booking
13.1How a new order starts
| The order | Its first status |
|---|---|
Was sent with scheduledFor | scheduled, released 20 minutes before the pickup time |
Was sent with estimatedReadyMinutes, or the branch has an average and is not set to give a time per order | pending, and offered to carriers at once |
| Has no prep time and the branch is set to give a time per order | awaiting_prep_time — held until it is answered per §13.2 or §13.3 |
| Any of the above, when RazGaz holds the order for a dispatcher | held (§2.2). There is nothing for you to do |
There is no automatic release from awaiting_prep_time. Offering an order to
carriers on a guess is worse than waiting. The simplest way to avoid the hold is
to always send estimatedReadyMinutes.
13.2"Ready in N minutes"
POST https://api.raz-gaz.com/submitPrepTime
{ "orderId": "…", "minutes": 20 }
minutes is a number of minutes from now: greater than 0, no more than 240, and
not below the area's minimum (§2.3).
The call does one of two things, depending on the order's state. You do not have to choose between them:
| Order state | What happens |
|---|---|
awaiting_prep_time | Releases the order. It moves to pending and the prep clock starts now. If RazGaz holds the branch's orders for a dispatcher, it moves to held instead — nothing for you to do |
| Any other active state | Updates the prep time. The status does not change. This is the "the food will be ten minutes late" path, and a carrier already assigned is notified |
delivered / cancelled | 409 — the order is finished |
On a release you may also send paymentMethod ("cash" or "card") and
orderAmount (required for cash, greater than 0) to state the payment at the same
time.
Response:
{ "success": true, "orderId": "…", "minutes": 20, "released": true,
"readyAt": 1762520400000, "paymentMethod": "card" }
released says whether the call released the order or updated it. readyAt is
in epoch milliseconds on a release, and null on an update.
| HTTP | When |
|---|---|
400 | orderId is required; minutes out of range or below the area's minimum; an invalid payment answer |
403 | Not your order · No restaurant is linked to this account |
404 | Order not found |
409 | Order is already finished |
13.3Booking an order already open
If you know the time when you open the order, send scheduledFor on
createRestaurantOrder (§12.3) and stop there. This call is for an order that
is already open and held at awaiting_prep_time.
POST https://api.raz-gaz.com/scheduleRestaurantOrder
{ "orderId": "…", "scheduledFor": "2026-11-08T20:30:00+02:00", "minutes": 25 }
This answers the same question in a different way. Instead of how long the food
takes from now, you say when the carrier should come for it. RazGaz holds the
order at scheduled and releases it to carriers 20 minutes before that time.
| Field | Type | Description |
|---|---|---|
orderId | string | The order. It must be at awaiting_prep_time |
scheduledFor | number | string | When the carrier should arrive at the restaurant, as epoch milliseconds or ISO-8601. It must be in the future and no more than 7 days ahead |
minutes | integer | The prep time: 1–240, and not below the area's minimum. Required |
paymentMethod, orderAmount | Optional, as in §13.2 |
Response:
{ "success": true, "orderId": "…", "scheduledFor": 1762626600000,
"releaseAt": 1762625400000, "minutes": 25, "paymentMethod": null }
Both times are in epoch milliseconds. releaseAt is the moment the order will be
offered to carriers.
| HTTP | When |
|---|---|
400 | A malformed time, a time in the past, or more than 7 days out; minutes out of range or below the area's minimum; an invalid payment answer |
403 | Not your order |
404 | Order not found |
409 | The order is not at awaiting_prep_time (the body carries its status), or it has no point on the map yet |
An order that has already gone out to carriers cannot be booked from this interface. If the customer moves the time after release, cancel (§15.2) and open a new order for the right time.
14Reading your orders
Everything the portal displays is read here: the status, the carrier, their position on the map and the arrival times. These calls go to Google's database host, with the same token (§11.3). Every call in this section starts from:
https://firestore.googleapis.com/v1/projects/razgaz-f7295/databases/(default)/documents
The polling limits are part of the contract. Polling is the only way to learn what happened to an order unless you use a listener (§14.6), so its rate is not a matter of politeness:
| Permitted rate | One request per minute per branch, at most |
| When no order is active | Do not poll at all |
| Number of pollers | One per branch — not one per till or screen |
Every one of these limits has already been broken on another integration. One system polled us every 0.37 seconds, around the clock, from several terminals, on behalf of one restaurant.
Nothing refuses an individual request for exceeding the limits. The read path is Google's own service, with no middleware of ours in front of it. We watch the total read volume. If a branch keeps exceeding the limits, we contact you. If it continues, the branch account is disabled. That is the only lever we have here, and it stops order creation too. A listener (§14.6) avoids the question entirely.
14.1The active orders
POST …/documents:runQuery
{
"structuredQuery": {
"from": [{ "collectionId": "orders" }],
"where": { "compositeFilter": { "op": "AND", "filters": [
{ "fieldFilter": {
"field": { "fieldPath": "restaurantId" },
"op": "EQUAL",
"value": { "stringValue": "<restaurant id>" } } },
{ "fieldFilter": {
"field": { "fieldPath": "status" },
"op": "IN",
"value": { "arrayValue": { "values": [
{ "stringValue": "scheduled" },
{ "stringValue": "awaiting_prep_time" },
{ "stringValue": "awaiting_location" },
{ "stringValue": "held" },
{ "stringValue": "pending" },
{ "stringValue": "broadcasting" },
{ "stringValue": "accepted" },
{ "stringValue": "arrived_at_pickup" },
{ "stringValue": "picked_up" },
{ "stringValue": "arrived_at_delivery" }
] } } } }
] } }
}
}
<restaurant id> is the value from §10. Always filter on restaurantId. A
query that cannot be proven to touch only your orders is refused whole, with a
permission error rather than a filtered result. The query by branch name that
earlier versions of this document gave is refused as well, for lack of an index:
move it to restaurantId.
14.2A single order
GET …/documents/orders/<orderId>, with the token in the header.
Use this to follow one order without reading the whole board, and in particular to confirm that a call from §12 or §13 that got no answer did in fact land. Another branch's order, or an id that does not exist, is answered with a permission error.
14.3History
Orders are never deleted. A delivered or cancelled order stays readable. For history, read the branch's orders newest first and stop where you need to:
{
"structuredQuery": {
"from": [{ "collectionId": "orders" }],
"where": { "fieldFilter": {
"field": { "fieldPath": "restaurantId" },
"op": "EQUAL",
"value": { "stringValue": "<restaurant id>" } } },
"orderBy": [{ "field": { "fieldPath": "createdAt" }, "direction": "DESCENDING" }],
"limit": 50
}
}
14.4The fields that come back
The response is a Firestore document, so every value is wrapped in its type
(stringValue, integerValue, doubleValue, timestampValue, nullValue,
geoPointValue, mapValue, arrayValue). These are the fields of interest to a
restaurant system. An order carries others, all internal to RazGaz:
| Field | What it is |
|---|---|
status | The order's state, §2.2 |
platformOrderRef | The human-facing order number — what you say on the phone |
estimatedReadyMinutes | The prep time on record, in minutes |
readyAt | When the food is expected to be ready, according to the prep time |
markedReadyAt | When someone said the food was ready. Empty until then, §15.1 |
carrierId | The carrier's identifier, or null until a carrier is assigned. The key for §14.5 |
eta | A map, present once a carrier is assigned. eta.pickupArrivalAt is the carrier's estimated arrival at the branch, and eta.deliveryArrivalAt their estimated arrival at the customer, both timestamps. They are recalculated every two minutes while the carrier is on the order |
scheduledFor | On a booked order, the time the carrier arrives at the restaurant, §13.3 |
cancelledAt, cancellationReason | The cancellation: when, and what was written about it |
deliveredAt | The moment of delivery to the customer |
deliveryAddress, customerName, customerPhone, items, orderAmount, paymentMethod, notes | As sent in §12. The phone is in its normalised form, and the address may have the town added |
The area's minimum prep time (§2.3) is readable at
GET …/documents/regions/<regionId>, in the field busyLagMinutes. The branch's
regionId is in its own document, GET …/documents/restaurants/<restaurant id>.
A missing field or 0 means there is no minimum.
14.5Tracking the carrier
GET …/documents/carrierPublic/<carrierId>, once the order has a carrierId.
| Field | What it is |
|---|---|
displayName | The carrier's name |
phone | Their phone number, for anyone who wants a call button |
currentLocation | Their position on the map, a geoPointValue, updated while they are on shift |
currentLocationUpdatedAt | When that position was recorded |
vehicleType | The vehicle |
That is everything a restaurant can read about a carrier. The carrier's own record
(carriers/<carrierId>) is private and answers with a permission error.
14.6Not polling at all — the recommended path
Firestore has a listen channel. You open one connection and receive every change the moment it happens. This is how the RazGaz portal itself works, and it is the recommended path here. Changes arrive instantly rather than within a minute, and the channel costs nothing while nothing is happening, so the limits above do not apply to it.
The listen channel uses gRPC and does not exist over REST, so it needs the Firebase SDK. In Node:
const { initializeApp } = require('firebase/app');
const { getAuth, signInWithEmailAndPassword } = require('firebase/auth');
const { getFirestore, collection, query, where, onSnapshot } = require('firebase/firestore');
const app = initializeApp({ apiKey: '<WEB_API_KEY>', projectId: 'razgaz-f7295' });
const { user } = await signInWithEmailAndPassword(getAuth(app), 'branch@example.com', '…');
const { restaurantId } = (await user.getIdTokenResult()).claims;
onSnapshot(
query(
collection(getFirestore(app), 'orders'),
where('restaurantId', '==', restaurantId),
where('status', 'in', ['scheduled', 'awaiting_prep_time', 'awaiting_location', 'held',
'pending', 'broadcasting', 'accepted', 'arrived_at_pickup', 'picked_up',
'arrived_at_delivery']),
),
(snap) => {
for (const change of snap.docChanges()) {
// 'added' — an order joined the board · 'modified' — status, carrier or ETA changed
console.log(change.type, change.doc.id, change.doc.data().status);
}
},
);
The query and its permissions are the same as §14.1's. The SDK keeps and refreshes the token itself, so §11.2 is not needed on this path. The fields are those of §14.4, delivered as plain values rather than wrapped in Firestore types.
Every change to the document arrives, not only status changes. The ETA, for example, is rewritten every two minutes while an order is on its way. That is what lets you show an ETA that moves. It also sets the cost: a few dozen changes per order, and nothing between orders.
When to poll anyway. Use §14.1's polling when there is no SDK, for example a PHP or older .NET stack that cannot hold a connection open. The limits at the top of §14 then apply.
15Acting on an existing order
Both actions are a POST …/documents:commit to the database host from §14, with
the same token, in exactly the shape shown. Only these fields may be written to
an order from this interface, and only on your branch's orders. A field not on
the list, or another branch's order, gets a permission error rather than a partial
write.
In every request, <orderId> is the identifier from §12.5.
15.1The food is ready
{ "writes": [ {
"update": {
"name": "projects/razgaz-f7295/databases/(default)/documents/orders/<orderId>",
"fields": { "readySource": { "nullValue": null } }
},
"updateMask": { "fieldPaths": ["markedReadyAt", "readySource"] },
"updateTransforms": [
{ "fieldPath": "markedReadyAt", "setToServerValue": "REQUEST_TIME" }
]
} ] }
This tells the carrier there is nothing left to wait for. markedReadyAt is
written as server time, not your system's clock, because everything on the other
side reads it as RazGaz time. A clock a few minutes off would move the message.
Nothing else ever writes it. A prep time is an estimate, and an estimate that runs out does not mark the food ready. If this call is never made, the carrier is never told. The order is still dispatched, collected and delivered normally. Send it when the bag is on the counter, not before. Call it once; a second call only writes a new time over the first.
To change the prep time, use §13.2, not a direct write. submitPrepTime also
notifies a carrier already assigned; a direct write notifies nobody.
15.2Cancelling
{ "writes": [ {
"update": {
"name": "projects/razgaz-f7295/databases/(default)/documents/orders/<orderId>",
"fields": {
"status": { "stringValue": "cancelled" },
"cancellationReason": { "stringValue": "Customer cancelled" },
"cancelledBy": { "stringValue": "restaurant" }
}
},
"updateMask": { "fieldPaths": ["status", "cancelledAt", "cancellationReason", "cancelledBy"] },
"updateTransforms": [
{ "fieldPath": "cancelledAt", "setToServerValue": "REQUEST_TIME" }
]
} ] }
"cancelled" is the only status value this interface may write. RazGaz writes
the other statuses, because they describe what is happening with the carrier. An
order marked from outside into a state that does not match reality drifts apart
from what the dispatcher and the carrier see.
cancellationReason is read by a person, so make it concrete — "customer
cancelled", "branch closed" — not empty.
A cancellation is final. A cancelled order is not offered to carriers again and cannot be revived. If needed, open a new order (§12).
cancelledBy: "restaurant" is what tells the carrier. With it, RazGaz
pushes the cancellation to the carrier on the order — or, while it is still being
offered, to every carrier it was offered to. It is the only value accepted in
that field, and only on the write that cancels. A cancellation without it is
still accepted but tells no carrier, so always send it.
Cancelling an order the carrier has already collected does not bring the food
back. From picked_up onward, call the RazGaz dispatch desk as well.
16What this interface does not have
Worth knowing before you design, so that nothing depends on it:
| A callback from RazGaz | None. No webhook and no push to your system. You read changes, §14 |
| An order identifier of your own | None. The identifier is RazGaz's, and there is no lookup by yours — §12.5, §12.7 |
| Changing the address or customer details after opening | None. An order opened with a wrong address is cancelled and opened again |
| Choosing a carrier, or changing the region or price | None. Carrier assignment, the carrier's fee and the broadcast radius belong to RazGaz |
| Booking an order already released | None. Booking is possible only from awaiting_prep_time — §13.3 |
| One account for several branches | None. One account per branch, §10 |
17Before going live
In this order, against one real branch:
- Signing in returns a token, and refreshing works. Test the path after an hour too, not only the sign-in: in production that is the path you hit constantly.
- A test order opens and returns
201and anorderId, and your system stores theorderIdwith the order. - The §14.1 query returns exactly that order. An error here means the query
does not filter on your
restaurantIdexactly as shown. - The status moves. It goes from
pendingtobroadcasting, then toacceptedwhen a carrier takes the order. At that pointcarrierIdfills in and §14.5 returns the carrier's position. - "The food is ready" (§15.1) writes.
markedReadyAtfills in on the next read. - Cancelling (§15.2) works on the test order and closes it, with
cancelledByin the write. Finish with this step, so a test delivery is not left out with carriers. estimatedReadyMinutesis sent on every order, at or above the area's minimum. Otherwise, confirm with RazGaz that the branch is not set to give a time per order; if it is, the first order in production will sit held (§13.1).
For questions and integration faults, contact whoever gave you this document. An
error that repeats in production — especially a 401, a 403, or a read that
comes back empty — means the integration is delivering nothing without anyone
noticing. Escalate it immediately rather than waiting for the next order.