Sales API
If your POS can export a sales report, and you have a tool that can send a web request, you can push sales into Rinvy without uploading a file. Zapier, Make, IFTTT and a Google Sheets script all work.
Sales sent this way land in the same place as a CSV upload. Lines that match a menu item are recorded right away. Lines that don't match wait for you in Pending Review on the Sales Data page.
Create an API key
Go to Kitchen Settings and open the Integrations section.
Under API access, type a name for the key. Name it after the tool that will use it, for example "Zapier".
- Press Create key.
- Press Copy key and paste it into your tool. Then press Done.
A kitchen can have up to 5 active keys. Only owners can see, create or revoke them. The list shows when each key was created and when it was last used.
To stop a key, press Revoke next to it. Anything that uses the key stops working right away. Revoked keys stay in the list so you can see what they were.
Send sales
Send a POST request to:
https://api.rinvy.app/api/v1/sales
Put the key in the Authorization header as Bearer <your key>, and send the body as JSON with Content-Type: application/json. Rinvy refuses a key in the address (?api_key=), because addresses end up in logs.
Where to put the header in common tools:
- Zapier (Webhooks by Zapier, POST): add
Authorizationwith the valueBearer <your key>under Headers. - Make (HTTP, Make a request): add a header named
Authorizationwith the valueBearer <your key>. - IFTTT (Webhooks, Make a web request, which needs IFTTT Pro): in Additional Headers, type
Authorization: Bearer <your key>. Set Content Type toapplication/json. - Google Apps Script: see the example below.
The request body
{
"salesDateStart": "2026-09-26",
"salesDateEnd": "2026-09-26",
"items": [
{ "posItemName": "Margherita Pizza", "quantitySold": 12, "netSales": 168.0 },
{ "posItemName": "Caesar Salad", "quantitySold": 7, "netSales": 77.0 }
]
}
salesDateStartandsalesDateEndare the business days the sales cover, asYYYY-MM-DD. They can't be in the future in your kitchen's timezone.- Each item needs
posItemName(the name your POS uses) andquantitySold(more than 0).netSalesandgrossSalesare optional. - You can send up to 2,000 items in one request.
- To send more than one day in one request, give every item a
dailyBreakdownthat splits it by day. Every day in it must be inside the date range, and the days must add up to the item'squantitySoldandnetSales:
{
"posItemName": "Margherita Pizza",
"quantitySold": 20,
"netSales": 280.0,
"dailyBreakdown": [
{ "businessDate": "2026-09-25", "qty": 8, "netSales": 112.0 },
{ "businessDate": "2026-09-26", "qty": 12, "netSales": 168.0 }
]
}
Sending the same days again
Each request is the whole story for its days. If Rinvy already has sales for any of those days, the request is refused with a 409 and nothing changes. That makes a retry safe.
To replace days you already sent, for example after a correction in the POS, add "replace": true to the body. Rinvy then swaps the old sales for those days with the new ones.
The response
A successful request returns:
{
"ok": true,
"uploadId": "…",
"status": "COMPLETED",
"matchedCount": 2,
"unmatchedCount": 0
}
status is COMPLETED when every line matched a menu item, or PENDING_REVIEW when some lines wait for you on the Sales Data page. The upload shows there as via API key: Zapier (the name of your key).
A refused request returns "ok": false and an error that says what to fix:
| Status | Meaning |
|---|---|
| 400 | The body is wrong, for example a missing field or a future date. |
| 401 | The key is missing, wrong, revoked, or sent in the address instead of the header. |
| 403 | Your plan doesn't include sales data. |
| 409 | Rinvy already has sales for these days. Send "replace": true to replace them. |
| 429 | Too many requests. A key can send 60 requests an hour. Wait, then try again. |
Try it with curl
curl -X POST https://api.rinvy.app/api/v1/sales \
-H "Authorization: Bearer rk_live_YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{
"salesDateStart": "2026-09-26",
"salesDateEnd": "2026-09-26",
"items": [{ "posItemName": "Margherita Pizza", "quantitySold": 12, "netSales": 168.0 }]
}'
Google Sheets example
This script reads yesterday's rows from a sheet and sends them as one day. It expects a tab named Sales with the columns Date, Item, Qty and Net sales (the date as a real date, not text).
- In your Google Sheet, open Extensions → Apps Script.
- Paste the script below and put your key in
RINVY_API_KEY. - Run
sendYesterdayonce and approve the permissions it asks for. Open Triggers (the clock icon), add a trigger for
sendYesterday, and pick a daily time-driven schedule, for example between 5 and 6 AM.
const RINVY_API_KEY = 'rk_live_YOUR_KEY';
const RINVY_URL = 'https://api.rinvy.app/api/v1/sales';
function sendYesterday() {
const tz = Session.getScriptTimeZone();
const yesterday = new Date(Date.now() - 24 * 60 * 60 * 1000);
const day = Utilities.formatDate(yesterday, tz, 'yyyy-MM-dd');
const rows = SpreadsheetApp.getActive().getSheetByName('Sales').getDataRange().getValues();
const totals = {};
rows.slice(1).forEach(([date, item, qty, net]) => {
if (!item || Utilities.formatDate(new Date(date), tz, 'yyyy-MM-dd') !== day) return;
totals[item] = totals[item] || { posItemName: String(item), quantitySold: 0, netSales: 0 };
totals[item].quantitySold += Number(qty) || 0;
totals[item].netSales += Number(net) || 0;
});
const items = Object.values(totals).filter((i) => i.quantitySold > 0);
if (items.length === 0) return;
const response = UrlFetchApp.fetch(RINVY_URL, {
method: 'post',
contentType: 'application/json',
headers: { Authorization: 'Bearer ' + RINVY_API_KEY },
payload: JSON.stringify({ salesDateStart: day, salesDateEnd: day, replace: true, items }),
muteHttpExceptions: true,
});
Logger.log(response.getResponseCode() + ' ' + response.getContentText());
}
The script sends replace: true, so running it twice for the same day is safe: the second run replaces the first.