RazGazRazGaz
ENעברית

Integration guide for developers

RazGazIntegration API

One document for two kinds of system: a cashier system that sends us orders (Part A), and a restaurant's own management system that runs its deliveries itself (Part B). Section 1 tells you which part is yours.

Version 2.1 · 2026-11-07Baseapi.raz-gaz.com
Contents

1Which part is yours

You are buildingReadIdentityWhat 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 branchOne 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 tokenOpens 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 keyNot 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 callshttps://api.raz-gaz.com
TransportHTTPS only. Content-Type: application/json, UTF-8
Part B onlyGoogle'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.

statusMeaning
awaiting_prep_timeHeld. RazGaz is waiting for the kitchen to say when the food will be ready. Offered to no carrier, §2.3
awaiting_locationHeld. The delivery address could not be placed on the map, and a RazGaz dispatcher will place it. Offered to no carrier
heldHeld 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
scheduledBooked for later, waiting to be released automatically, §13.3
pendingReleased. Not yet offered to carriers
broadcastingBeing offered to carriers in the area
acceptedA carrier took it and is on the way to the branch
arrived_at_pickupThe carrier is at the branch
picked_upThe food is with the carrier, on the way to the customer
arrived_at_deliveryThe carrier is at the customer
deliveredDelivered. Final
cancelledCancelled. 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 branchAn order without a prep time
Is set to give a time per orderIs 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 recordUses the average and goes out at once
Has neitherGoes 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

FieldMeaning
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
orderAmountWhat 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 isScope
API keyA secret string: rg_live_ followed by 48 hexadecimal charactersOne key per cashier installation, whatever the number of restaurants it sends for
Restaurant codeThe code that identifies one branch in your requests, sent as externalIdOne 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:

MethodWhere
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

FieldTypeDescription
orderIdstringYour 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
externalIdstringThe 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:

  • deliveryLocation as coordinates, or
  • a non-empty deliveryAddress for 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

FieldTypeDescription
deliveryLocationobjectThe drop-off point. { "latitude": number, "longitude": number }. When sent, it is used exactly as sent
deliveryAddressstringThe 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
deliveryAddressPartsobjectThe 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
pickupLocationobjectWhere the carrier collects. { "latitude": number, "longitude": number }. Usually left out (§5.1)
pickupAddressstringThe 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

FieldTypeDescription
estimatedReadyMinutesintegerMinutes 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
customerNamestringThe customer's name, as the carrier should ask for them
customerPhonestringA number the carrier can call on arrival
notesstringFree text shown to the carrier: delivery instructions, gate codes, "leave at the door"
itemsarrayThe 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
orderAmountnumber§2.5
paymentMethodstring§2.5
placedAtnumber | stringWhen 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

HTTPMeaning
201The order was created
200An 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>" }.

HTTPMessageCauseWhat to do
400orderId and (externalId or restaurantName) are required§5.1Fix the payload. Retrying it unchanged cannot succeed. RazGaz is alerted internally on every 400
400deliveryLocation or deliveryAddress is requiredNo delivery point of either kindSame
400pickupLocation and deliveryLocation must each have numeric latitude and longitudeA coordinate sent as a string, or missing halfSame
400pickupLocation is required: the restaurant has no stored positionThe branch has no stored positionSend pickupLocation, and tell us
400A Hebrew sentence about the prep timeestimatedReadyMinutes is not a positive number, or is more than 240Fix the value
401Invalid API keyNo key, an unknown key, or a revoked keyStop and escalate. No internal alert is raised, so RazGaz will not notice for you
405Method not allowedThe request was not a POSTFix the method
409orderId belongs to an order another sender createdThis orderId belongs to an order a delivery partner created. Nothing was changedDo not retry. Contact RazGaz
500Internal error ingesting orderA fault on the RazGaz sideRetry. 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_location is 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;
  • placedAt left 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 orderId and 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

  1. RazGaz has issued the API key and one restaurant code per branch.
  2. Your orderIds cannot collide with another system's (§5.1).
  3. 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.
  4. 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.
  5. estimatedReadyMinutes is sent whenever the cashier system can produce it.
  6. Retries on 500 and on timeout are in place. 400 and 401 are 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 passwordThe 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 keyThe 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 idThe 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:

FieldWhat to do with it
idTokenThe token itself. Sent on every request, §11.3
refreshTokenReplaces an expired token, §11.2. Does not expire on its own
expiresInLifetime 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

FieldTypeDescription
deliveryAddressstringThe 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

FieldTypeDescription
deliveryLocationobject{ "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

FieldTypeDescription
itemsarrayThe 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
estimatedReadyMinutesintegerMinutes 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
scheduledFornumber | stringA 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
customerNamestringThe customer's name, as the carrier should ask for them
customerPhonestringA 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
floorstringThe floor, shown to the carrier at the building
apartmentstringThe apartment
entrancestringThe entrance
notesstringFree text shown to the carrier: delivery instructions, "leave at the door"
orderAmountnumber§2.5
paymentMethodstring§2.5
bigOrderbooleantrue 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
orderSizenumber2 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>" }.

HTTPWhen
400deliveryAddress 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)
400Restaurant is missing a pickup location — a setup fault; contact us
401The token is missing, expired or invalid (§11)
403No restaurant is linked to this account · Restaurant is not active
405Not 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 orderIts first status
Was sent with scheduledForscheduled, 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 orderpending, and offered to carriers at once
Has no prep time and the branch is set to give a time per orderawaiting_prep_time — held until it is answered per §13.2 or §13.3
Any of the above, when RazGaz holds the order for a dispatcherheld (§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 stateWhat happens
awaiting_prep_timeReleases 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 stateUpdates 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 / cancelled409 — 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.

HTTPWhen
400orderId is required; minutes out of range or below the area's minimum; an invalid payment answer
403Not your order · No restaurant is linked to this account
404Order not found
409Order 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.

FieldTypeDescription
orderIdstringThe order. It must be at awaiting_prep_time
scheduledFornumber | stringWhen 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
minutesintegerThe prep time: 1–240, and not below the area's minimum. Required
paymentMethod, orderAmountOptional, 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.

HTTPWhen
400A 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
403Not your order
404Order not found
409The 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 rateOne request per minute per branch, at most
When no order is activeDo not poll at all
Number of pollersOne 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:

FieldWhat it is
statusThe order's state, §2.2
platformOrderRefThe human-facing order number — what you say on the phone
estimatedReadyMinutesThe prep time on record, in minutes
readyAtWhen the food is expected to be ready, according to the prep time
markedReadyAtWhen someone said the food was ready. Empty until then, §15.1
carrierIdThe carrier's identifier, or null until a carrier is assigned. The key for §14.5
etaA 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
scheduledForOn a booked order, the time the carrier arrives at the restaurant, §13.3
cancelledAt, cancellationReasonThe cancellation: when, and what was written about it
deliveredAtThe moment of delivery to the customer
deliveryAddress, customerName, customerPhone, items, orderAmount, paymentMethod, notesAs 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.

FieldWhat it is
displayNameThe carrier's name
phoneTheir phone number, for anyone who wants a call button
currentLocationTheir position on the map, a geoPointValue, updated while they are on shift
currentLocationUpdatedAtWhen that position was recorded
vehicleTypeThe 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 RazGazNone. No webhook and no push to your system. You read changes, §14
An order identifier of your ownNone. The identifier is RazGaz's, and there is no lookup by yours — §12.5, §12.7
Changing the address or customer details after openingNone. An order opened with a wrong address is cancelled and opened again
Choosing a carrier, or changing the region or priceNone. Carrier assignment, the carrier's fee and the broadcast radius belong to RazGaz
Booking an order already releasedNone. Booking is possible only from awaiting_prep_time — §13.3
One account for several branchesNone. One account per branch, §10

17Before going live

In this order, against one real branch:

  1. 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.
  2. A test order opens and returns 201 and an orderId, and your system stores the orderId with the order.
  3. The §14.1 query returns exactly that order. An error here means the query does not filter on your restaurantId exactly as shown.
  4. The status moves. It goes from pending to broadcasting, then to accepted when a carrier takes the order. At that point carrierId fills in and §14.5 returns the carrier's position.
  5. "The food is ready" (§15.1) writes. markedReadyAt fills in on the next read.
  6. Cancelling (§15.2) works on the test order and closes it, with cancelledBy in the write. Finish with this step, so a test delivery is not left out with carriers.
  7. estimatedReadyMinutes is 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.