RazGazRazGaz
ENעברית

Integration guide for restaurant systems

RazGazRestaurant API

The complete contract for running RazGaz deliveries from a restaurant's own system instead of the RazGaz restaurant app. Everything the app does is done here.

Version 1.2 · 2026-08-19Baseapi.raz-gaz.com
Contents

1Overview

The RazGaz restaurant app is a client, not a server: it opens orders, reads the order board and updates orders through a public interface — and a restaurant's management system can call exactly the same interface. An integration built to this document replaces the app completely. There is no action the app can take that your system cannot.

AuthenticationThe restaurant account — email and password, exchanged for a token
RazGaz callshttps://api.raz-gaz.com
Reading and updating ordershttps://firestore.googleapis.com/v1/projects/razgaz-f7295/databases/(default)/documents
TransportHTTPS only. Content-Type: application/json, UTF-8

Both hosts are one interface. Opening an order and answering a prep time are calls to RazGaz; reading the order board and acting on an existing order go straight to the database, with permission derived from that same token. A restaurant sees and updates its own orders only — the restriction is enforced server-side, not in the client.

There is no outbound channel. RazGaz does not call your system back and sends no webhook. To know what happened to an order — who picked it up, where the carrier is, whether it was delivered — you poll, §6.


2What RazGaz issues you

Three things, all from RazGaz, before the first order:

What it is
Email and passwordThe restaurant account. One account per branch — there is no single account that manages several, so a system serving two branches holds two accounts and authenticates to each separately
Web API keyThe public project identifier needed to exchange the password for a token (§3). It is not a secret and grants nothing on its own — the password is the secret
Branch nameThe branch's canonical name at RazGaz, exactly as it is recorded. It is the value your orders carry in restaurantName, and it is what you filter on in §6

No branch code and no API key like a cashier system's: on this interface the identity is the account, and the branch is derived from it. A branch marked inactive by RazGaz cannot open orders (403), but orders that already exist stay readable and keep updating.


3Authentication

3.1Getting a token

POST https://identitytoolkit.googleapis.com/v1/accounts:signInWithPassword?key=<WEB_API_KEY>

{
  "email": "branch@example.com",
  "password": "…",
  "returnSecureToken": true
}

Three fields in the response are worth keeping:

FieldWhat to do with it
idTokenThe token itself. Sent on every request, §3.3
refreshTokenReplaces an expired token, §3.2. Does not expire on its own
expiresInLifetime in seconds — 3600, one hour

3.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. Hold the token in memory and refresh it before it expires — a minute of headroom is reasonable. An expired token returns 401, and retrying with the same token returns the same thing forever.

3.3Using it

On every request, to both hosts:

Authorization: Bearer <idToken>

A request with no token, an expired token, or a token belonging to another account returns 401 or 403, and nothing is recorded — no order is opened and nothing is updated. In production such an error means the integration is silently delivering nothing; escalate it rather than retrying.


4Opening an order

POST /createRestaurantOrder

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 at RazGaz. RazGaz prices the carrier's fee, broadcasts the order to carriers in the area, and manages the carrier from assignment through delivery.

4.1Request — the required field

FieldTypeDescription
deliveryAddressstringThe delivery address as one readable line: street, house number and town. Not used for navigation — the coordinates are — but it is what the carrier reads. Entrance details — floor, apartment, entrance — do not belong in this line; they have their own fields in §4.3. With no coordinates sent, this line is what gets looked up on the map, and a floor inside it degrades that lookup. A gate code and the like go in notes

That is the only required field. An address is all this endpoint needs; everything else about the order — the item list and the coordinates included — is optional.

4.2Request — the drop-off point on the map

FieldTypeDescription
deliveryLocationobjectThe drop-off point on the map. { "latitude": number, "longitude": number }, both values numbers in JSON, not strings

Recommended, not required. If your system holds coordinates, send them and we use them as they are. That is the best outcome available: a point your system confirmed beats one we inferred.

Without them we locate the address ourselves, and fill in the branch's own town when the address named none — "Herzl 5" from a Ra'anana branch is the Herzl in Ra'anana. The address stored on the order carries the town that was added (your words are kept; the town is appended), so the carrier reads a complete address.

Two things worth designing around:

  • An address that cannot be located at all is a 400 with a message in Hebrew.
  • An address that resolves only to town level gets a point at the centre of that town rather than at the building, and the order opens normally. Nothing in the response distinguishes the two — if that precision matters to you, send coordinates.

4.3Request — optional fields

FieldTypeDescription
itemsarrayThe order's items, { "name": string, "qty": number } each. Send them when you have them — the carrier sees the list and it is what a collection at the counter is checked against. Not required: an order sent without them, or with an empty array, is opened normally and shows as having no itemised list, which is how orders from till systems that do not send one already arrive. A value that is not an array is a 400
estimatedReadyMinutesintegerMinutes from now until the food is ready. Greater than 0 and no more than 240. Send it whenever your system knows the number — see §5. On a booking (scheduledFor below) send it always: the order is released straight into dispatch and nothing asks you again, so a booking without it goes out saying the food is already up. It does not change when the order is dispatched — that is a fixed 20 minutes before the pickup time
scheduledFornumber | stringA booking. When the carrier should arrive at the restaurant to collect the order — epoch milliseconds, or ISO-8601. The order is held and released in time for the food to be ready as the carrier reaches the counter; it is offered to no carrier before then. 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 needs to ask for it
customerPhonestringA number that can be called. The carrier calls on arrival
floorstringThe floor. Shown to the carrier at the door, separately from the address line
apartmentstringThe apartment
entrancestringThe entrance
notesstringFree text shown to the carrier. Delivery instructions, "leave at the door"
orderAmountnumberWhat the customer pays for the order, in shekels
paymentMethodstring"cash" when the carrier must collect payment from the customer. Any other value, and the field's absence, are recorded as "card" — the carrier collects nothing

items is stored as sent, and every screen — the carrier's, the dispatcher's — reads exactly the two keys name and qty. An item that calls its quantity something else shows as 1.

The three entrance fields (floor, apartment, entrance) are stored apart from the address line and shown to the carrier as they reach the building. A value of "0" is read as "not given" and is not shown — till systems send "0" for a private house and the two cannot be told apart. A ground floor that must be shown should be written in words.

4.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 on the RazGaz side and are not taken from the request.

4.5Response

HTTPBodyMeaning
201{ "success": true, "orderId": "…" }The order was opened

orderId is the order's identity at RazGaz, and it is the value the rest of this document takes: §5, §6.2 and §8 all address an order by it. Store it alongside the order in your own system — there is no other way to point at an order, and there is no lookup by an identifier of yours.

4.6Errors

HTTPWhen
400deliveryAddress missing, items sent as something other than an array, or estimatedReadyMinutes out of range
400 (Hebrew)An address was sent with no coordinates and we could not place it on the map at all (§4.2)
400The branch has no pickup location stored at RazGaz — a configuration fault; contact us
401Token missing, expired or invalid (§3)
403No branch is linked to the account, or the branch is marked inactive
405Not a POST

4.7A repeated call opens a second order

There is no idempotency on this call. The order id is generated on the RazGaz side on every call, and there is no field in which to send an identifier of yours, so RazGaz has no way to tell that two requests describe the same order. Retrying after a timeout where the first request did land opens a second delivery, and two carriers arrive at the same branch.

So: an automatic retry on this call is your system's responsibility, and its precondition is knowing the first one was not written. The way to check is §6.1 — poll the active orders and look for one matching the address, the phone and the time. An order opened twice by mistake is cancelled per §8.3.


5Prep time

Prep time is what lets RazGaz time the carrier to arrive at food that is ready rather than before it. Every branch has either an average prep time recorded with us, or a flag saying it gives a time per order. So a new order is in one of two situations:

  • estimatedReadyMinutes was sent in §4.1, or the branch has a recorded average — the order goes out to broadcast at once. This is the intended path.
  • It was not sent, and the branch is flagged as giving a time per order — the order is held at status awaiting_prep_time and is broadcast to no carrier until it is answered per §5.1 or §5.2. An unanswered order stays held: there is no automatic release, because broadcasting on a guess is worse than waiting. RazGaz raises an internal alert about an order nobody is answering, but that alert does not release it.

The simple way to avoid the second situation is to always send estimatedReadyMinutes.

5.1"Ready in N minutes"

POST /submitPrepTime

{ "orderId": "…", "minutes": 20 }

minutes — a number of minutes from now, greater than 0 and no more than 240.

The call does one of two things depending on the order's state, and you do not have to choose between them:

Order stateWhat happens
awaiting_prep_timeReleases the order: status moves to pending and the prep clock starts now. If a RazGaz manager has put the branch on hold, it moves to held instead and a dispatcher publishes it — nothing for you to do
Any other active stateUpdates the prep time. The status is untouched; this is the "the food will be ten minutes late" path, and a carrier already assigned is notified
delivered / cancelled409 — the order is finished

Response: { "success": true, "orderId": "…", "minutes": 20, "released": true, "readyAt": 1755513600000 }. released says whether this was the release or the update; readyAt — epoch milliseconds — is only present on a release.

5.2Booking an order for later

If you know the time when you open the order, send scheduledFor on createRestaurantOrder (§4.3) and stop there — the order is booked from the outset and this call is not needed. What follows is for an order already open: one your system sent without a time, or one held waiting for a prep time.

POST /scheduleRestaurantOrder

{ "orderId": "…", "scheduledFor": "2026-08-18T20:30:00+03:00", "minutes": 25 }

The other answer to the same question: not how long the food takes from now but when the carrier should come for it. RazGaz holds the order and releases it to broadcast itself, at a time computed backwards from the requested moment through the cooking — so the food is ready as the carrier reaches the counter. That is why minutes is required here too: the prep time is part of the arithmetic, and without it the release time cannot be worked out.

FieldTypeDescription
orderIdstringThe order. Must be at awaiting_prep_time
scheduledFornumber | stringThe requested pickup time — when the carrier should arrive at the restaurant: epoch milliseconds, or ISO-8601. Must be in the future and no more than 7 days ahead
minutesintegerThe prep time, 1–240

Response: { "success": true, "orderId": "…", "scheduledFor": …, "releaseAt": …, "minutes": 25 } — both stamps in milliseconds. releaseAt is the moment the order will go out to broadcast; until then it sits at status scheduled and is offered to no carrier.

HTTPWhen
400A time in the past, a time more than 7 days out, or minutes out of range
404Unknown order
403The order does not belong to this branch
409The order is not at awaiting_prep_time — it has already been released, and booking it backwards is not possible from here

An order already out to broadcast cannot be booked from this interface. If a customer moves the time after the order was released — cancel (§8.3) and open a new one at its proper time.


6Reading your orders

Everything the app displays is read here: the status, the carrier, their position on the map, the travel times. These calls go to the second host, firestore.googleapis.com, with the same token from §3.3. The base for every call in this section:

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, so its rate is not a matter of politeness:

Permitted rateOne request every 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

All three are spelled out because every one of them has already been broken against us in production, on another integration: one POS polls a different endpoint of ours once every 0.37 seconds — 54× the rate it was given — around the clock, including hours when the kitchen is shut, from several terminals each keeping its own timer. It did that on behalf of a single restaurant that had sent eight orders.

Nothing enforces this per request. No call of yours will be refused for exceeding it — the read path is Google's own service and there is no middleware of ours in front of it. What we see day to day is the total read volume across all branches; attributing a spike to one branch is something we turn on when a spike is what we are looking at, and it names you. Sustained excess means we contact you, and if it continues, the branch account is disabled: that is the only lever we have here, and it stops order creation too. Better to avoid that conversation — and better still not to need polling at all, which is exactly what a listener (§6.6) is for.

6.1The active orders

POST …/documents:runQuery

{
  "structuredQuery": {
    "from": [{ "collectionId": "orders" }],
    "where": { "compositeFilter": { "op": "AND", "filters": [
      { "fieldFilter": {
        "field": { "fieldPath": "restaurantName" },
        "op": "EQUAL",
        "value": { "stringValue": "Branch name" } } },
      { "fieldFilter": {
        "field": { "fieldPath": "status" },
        "op": "IN",
        "value": { "arrayValue": { "values": [
          { "stringValue": "scheduled" },
          { "stringValue": "awaiting_prep_time" },
          { "stringValue": "held" },
          { "stringValue": "pending" },
          { "stringValue": "broadcasting" },
          { "stringValue": "accepted" },
          { "stringValue": "arrived_at_pickup" },
          { "stringValue": "picked_up" },
          { "stringValue": "arrived_at_delivery" }
        ] } } } }
    ] } }
  }
}

Branch name is the canonical name from §2, matched character for character. A name that is not identical returns an empty set rather than an error — the one silent failure in this section, which is why it is worth confirming the name against one real order before going live.

6.2A single order

GET …/documents/orders/<orderId> — with the token in the header. 404 for an unknown order, 403 for another branch's order.

This is how to follow one order without polling the whole board, and in particular how to confirm that a call from §4 or §5 that you got no answer to did in fact land.

6.3History

The same query as §6.1 with "stringValue": "delivered" and "cancelled" in the status list. Orders are never deleted: a delivered or cancelled order stays readable.

6.4The fields that come back

The response is a Firestore document, meaning 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 RazGaz-internal:

FieldWhat it is
statusThe order's state, §7
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, per the prep time
markedReadyAtThe moment someone said the food was up. Empty until then — §8.1
carrierIdThe carrier's identifier, or null until one is assigned. The key for §6.5
estimatedArrivalRazGaz's estimate of the carrier's arrival
scheduledForThe pickup time on a booked order — when the carrier arrives at the restaurant, §5.2
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 §4

6.5Tracking the carrier

GET …/documents/carriers/<carrierId> — once the order carries 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
vehicleTypeThe vehicle

This is the information the app draws on its map. The carrier document is read whole, and anything not in this table is RazGaz-internal operations, not part of this contract — do not rely on it.

6.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 restaurant app itself works, and it is the recommended path here — it arrives instantly rather than within a minute, and it costs nothing while nothing is happening, so the limits above simply do not apply to it.

It needs the Firebase SDK, because the listen channel is gRPC and does not exist over REST. 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' });
await signInWithEmailAndPassword(getAuth(app), 'branch@example.com', '…');

onSnapshot(
  query(
    collection(getFirestore(app), 'orders'),
    where('restaurantName', '==', 'Branch name'),
    where('status', 'in', ['scheduled', 'awaiting_prep_time', '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 is identical to §6.1's, and so are the permissions. signInWithEmailAndPassword holds and refreshes the token itself, so §3.2 is not needed on this path. The fields are the same ones as §6.4, except they arrive as plain values rather than wrapped in Firestore types.

What arrives is every change to the document, not only a status change — the carrier's ETA updates too, which are written every two minutes while an order is on its way. That is what lets you show an ETA that moves, and it is also what sets the cost: a few dozen changes per order, and nothing between orders.

When to poll anyway. When there is no SDK — a PHP or older .NET stack that cannot hold a connection — §6.1's polling is the path, subject to the limits at the top of §6.


7Statuses

statusMeaning
awaiting_prep_timeHeld: RazGaz is waiting for a prep time. Broadcast to no carrier — §5
heldHeld by a RazGaz dispatcher: the branch is set to manual publishing, and a dispatcher releases each order. Broadcast to no carrier; nothing for the branch to do — cook as usual
scheduledBooked for later, awaiting automatic release — §5.2
pendingReleased, broadcast not yet started
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

The status advances in that order, but not every state necessarily appears in your polling: two adjacent states can both pass between two samples. Treat the status as a current state rather than an event, and do not build logic that requires seeing every one.


8Acting on an existing order

The three actions the app offers on an order card. All of them are POST …/documents:commit against the host from §6, with the same token, and in exactly this shape: the shapes below are what is permitted, and any other write to an order is rejected server-side — a field not on the list, or another branch's order, comes back as a permission error rather than a partial write.

In every request <orderId> is the identifier from §4.5, and name is the full path projects/razgaz-f7295/databases/(default)/documents/orders/<orderId>.

8.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 is the message to the carrier that 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, and a clock a few minutes off moves the message. readySource: null is sent in the same write; the field records who said the food was up, and since 19 August 2026 the answer is always a person.

Nothing else ever writes it. A prep time is an estimate, and letting one expire into "ready" told a carrier the food was up on nobody's word. So an estimate that runs out changes nothing about the order: if this call is never made, the carrier is simply never told, for the whole life of the order. It is still dispatched, claimed and delivered normally. Send it when the bag is on the counter and not before.

Call it once. A second call writes a fresh stamp over the first and adds nothing.

8.2Updating the prep time

Not here — §5.1. submitPrepTime is correct in both situations, writes the same field, and also notifies a carrier already assigned. Writing estimatedReadyMinutes directly is possible and notifies nobody; there is no reason to prefer it.

8.3Cancelling

{ "writes": [ {
  "update": {
    "name": "projects/razgaz-f7295/databases/(default)/documents/orders/<orderId>",
    "fields": {
      "status": { "stringValue": "cancelled" },
      "cancellationReason": { "stringValue": "Cancelled by the restaurant" }
    }
  },
  "updateMask": { "fieldPaths": ["status", "cancelledAt", "cancellationReason"] },
  "updateTransforms": [
    { "fieldPath": "cancelledAt", "setToServerValue": "REQUEST_TIME" }
  ]
} ] }

"cancelled" is the only value for status supported from this interface. The other statuses describe what is happening to the carrier and RazGaz is what writes them; an order marked from outside into a state that does not match what is actually happening comes apart from what the dispatcher and the carrier see.

cancellationReason is read by a person, so make it concrete — "customer cancelled", "branch closed" — rather than empty.

A cancellation is final and does not undo itself. A cancelled order does not return to broadcast and cannot be revived; what follows it is a new order per §4. Cancelling an order the carrier has already collected does not bring the food back — at picked_up and beyond, call the dispatch desk rather than relying on this call alone.


9What this interface does not have

Worth knowing before you design, so that nothing is designed around it:

A callback from RazGazNone. No webhook and no push to your system; information arrives by polling, §6
Address-to-coordinate lookupSince 2026-08-25 there is one: send the address without coordinates and we locate it, filling in the branch's own town when the address named none (§4.2). Sending coordinates is still the more precise input, and the only one that guarantees a building rather than a town centre
An order identifier of your ownNone. The identifier is RazGaz's, and there is no lookup by yours — §4.5, §4.7
Changing the address or customer details after openingNone. An order opened with a wrong address is cancelled and reopened
Choosing a carrier, or changing region and pricingNone. Carrier assignment, their fee and the broadcast radius are RazGaz's
Booking an order that has already been releasedNone. Booking is only possible from awaiting_prep_time — §5.2
One account for several branchesNone. One account per branch, §2

10Before going live

In this order, against one real branch:

  1. Authentication returns a token, and refresh works. Not just the sign-in — the path after an hour too, because in production that is the one you meet constantly.
  2. A test order opens and returns 201 and an orderId, and the orderId is stored on your side alongside the order.
  3. The §6.1 poll returns exactly that order. If it returns an empty set, the branch name is not identical to the canonical one. This is the common fault.
  4. The status moves. From pending to broadcasting and on to accepted when a carrier takes the order, at which point carrierId fills in and §6.5 returns their position.
  5. "The food is ready" (§8.1) writesmarkedReadyAt fills in on the next poll.
  6. Cancelling (§8.3) works on the order you opened for the test, and closes it. Finish on that, so a test delivery is not left open in broadcast.
  7. estimatedReadyMinutes is sent on every order — or it has been confirmed with RazGaz that the branch has an average prep time on record. Otherwise the first order in production will sit held, §5.

For questions and integration faults, the address is whoever gave you this document. An error that repeats in production — particularly a 401, a 403, or a poll that comes back empty — is an integration silently delivering nothing; escalate it immediately rather than waiting for the next order.