Keep your catalogue, stock and prices on Garagy in step with your own ERP or POS, and handle your Parts Hub orders from it. Garagy can also post events to your system (webhooks), or read a file from your system every night (scheduled pull). The shop owner chooses this in Garagy: Selling → Your own system.
Start
- The shop owner chooses API push and creates a key. It is shown once: store it as a secret.
- Send it on every call:
Authorization: Bearer gk_…. A key reaches its own shop only. - Base address:
https://nboenaiqwjwpbbaraeyt.supabase.co/functions/v1/supplier-api - Try things against a test project first: ask Garagy for a key on the staging project, or use your local copy.
Limits: 120 calls a minute per key, 1,000 items and 2 MB per call. Money is in fils (1 BHD = 1000 fils); prices exclude VAT. Every call is logged and visible to the shop owner. OpenAPI description
Send stock and prices
Create or update items by SKU (your part number). Sending the same rows again changes nothing; each row is saved or reported on its own.
curl -X POST "https://nboenaiqwjwpbbaraeyt.supabase.co/functions/v1/supplier-api/v1/items" \
-H "Authorization: Bearer $GARAGY_KEY" \
-H "Content-Type: application/json" \
-d '{"items":[{"sku":"OF-1001","name":"Oil filter","brand":"Denso","oem_number":"04152-YZZA1","price_fils":2500,"wholesale_price_fils":2000,"quantity":12,"branch":"Main","list_on_marketplace":true,"fit":[{"make":"Toyota","model":"Camry","year_from":2015,"year_to":2020}]}]}'{"received":1,"saved":1,"created":1,"updated":0,"errors":[]}Fields: sku (required), name (required for a new item), name_ar, oem_number, brand, category, condition (new_oem, new_aftermarket, used, refurbished), price_fils, wholesale_price_fils, online_price_fils, list_on_marketplace, active, quantity (on hand at the branch, not a change), branch (name; main branch when left out), fit (replaces the list). Leave a field out to keep what Garagy has. Stock cannot go below what open orders hold.
Read back what Garagy holds, 500 at a time:
curl "https://nboenaiqwjwpbbaraeyt.supabase.co/functions/v1/supplier-api/v1/items?offset=0&limit=500" \ -H "Authorization: Bearer $GARAGY_KEY"
Orders
curl "https://nboenaiqwjwpbbaraeyt.supabase.co/functions/v1/supplier-api/v1/orders?status=placed" \ -H "Authorization: Bearer $GARAGY_KEY"
Move an order as in the portal: confirm_fitment, accept, reject (with a reason the buyer sees), ready, dispatched, delivered. A new order not accepted within 30 minutes is cancelled and refunded.
curl -X POST "https://nboenaiqwjwpbbaraeyt.supabase.co/functions/v1/supplier-api/v1/orders/ORD-2026-000123/reject" \
-H "Authorization: Bearer $GARAGY_KEY" \
-H "Content-Type: application/json" \
-d '{"reason":"Out of stock at the branch"}'Webhooks
The owner adds your address in Selling → Webhooks. Garagy posts JSON for the events chosen: order.placed, order.status, rfq.new, stock.changed (stock moved in Garagy: a sale, a job, a return, a count; changes your own system sent are not echoed back). A non-2xx answer is tried again after 1, 2, 4, 8 and 16 minutes.
Headers: x-garagy-event, x-garagy-delivery (same on a retry), x-garagy-signature: sha256=<hex HMAC-SHA256 of the body with your webhook secret>. Check it before trusting the body:
import { createHmac, timingSafeEqual } from "node:crypto";
const expected = "sha256=" + createHmac("sha256", process.env.GARAGY_WEBHOOK_SECRET).update(rawBody).digest("hex");
const ok = timingSafeEqual(Buffer.from(expected), Buffer.from(req.headers["x-garagy-signature"] ?? ""));Answers
200 done (an upload lists rejected rows in errors), 400 bad request, 401 invalid or revoked key, 404 not found or not yours, 413 too large, 422 refused (the message says why, e.g. the shop is read-only or the order cannot move from its status), 429 too many calls.
Garagy