REST API
Auth
Generate keys in Settings → API Keys. Each key looks like ck_live_xxxxxxxx_… and is shown once at creation. Send it as the standard Authorization header on every request:
Authorization: Bearer ck_live_xxxxxxxx_…
Rate limit: 120 requests per minute, per key. Every response carries RateLimit-Limit / RateLimit-Remaining / RateLimit-Reset headers (reset in seconds), and a 429 adds Retry-After — throttle on those rather than counting requests yourself. Optionally restrict by IP allowlist in the key settings.
Base URL
https://rebaar.in/api/v1
Machine-readable spec: https://rebaar.in/openapi.json (OpenAPI 3.0 — typed schemas and operation IDs for codegen and AI function calling).
Endpoints
Projects
| Method | Path | Description |
|---|---|---|
| GET | /projects | List projects (cursor-paged) |
| GET | /projects/{id} | Get one project + linked client |
| POST | /projects/create | Create a project (subject to plan limit). Optional pincode, stateCode (2-digit GST code, e.g. 29) and district: the PIN fills the state and district when they are not sent, and a state the PIN rules out is 400 with field: "pincode" — for the site, whose state an invoice suggests as place of supply |
Invoices
| Method | Path | Description |
|---|---|---|
| GET | /invoices | List invoices, filterable by ?status= |
| GET | /invoices/{id} | Get one invoice + linked client + project |
| POST | /invoices/create | Create an invoice as Draft (default) or Sent. Amounts in paise (₹ × 100) |
| PATCH | /invoices/{id} | Update status / amountPaid / paidAt / dueDate, under the same rules as the app: an issued invoice never goes back to Draft; Cancelled is final; a Paid invoice’s status follows its payments; a Draft moves only to Sent (not while approvals are pending) or Cancelled; cancelling needs the IRN cancelled and nothing paid. Raising amountPaid — or sending status: "Paid", meaning paid in full — records a payment for the difference, which shows (and can be voided) in Rebaar; the same call twice records it once. amountPaid can only go up (409 amount_paid_decrease), cannot exceed the total (422), and sets the status (Partial / Paid) unless you pass a status that agrees. Partial is never set on its own — it follows the payments. Refusals are 409 with { error, message } |
| DELETE | /invoices/{id} | Delete — only a Draft with nothing paid, no IRN and no payment awaiting verification (else 409). An issued invoice is cancelled with PATCH, never deleted |
Vendors
| Method | Path | Description |
|---|---|---|
| GET | /vendors | List vendors |
| GET | /vendors/{id} | Get one vendor |
| POST | /vendors/create | Create a vendor. Optional pincode, stateCode (2-digit GST code, e.g. 29) and district: the PIN fills the state and district when they are not sent, and a state the PIN rules out is 400 with field: "pincode" |
| PATCH | /vendors/{id} | Update contact / classification / location fields. A new pincode decides its own state and district |
| DELETE | /vendors/{id} | Delete vendor |
Clients
| Method | Path | Description |
|---|---|---|
| GET | /clients | List clients |
| GET | /clients/{id} | Get one client |
| POST | /clients/create | Create a client. Optional pincode, stateCode (2-digit GST code, e.g. 29) and district: the PIN fills the state and district when they are not sent, and a state the PIN rules out is 400 with field: "pincode" |
| PATCH | /clients/{id} | Update name / contact / GSTIN / location. A new pincode decides its own state and district |
| DELETE | /clients/{id} | Delete — refuses with 409 if the client still has invoices, projects, or quotations |
Need an endpoint that isn't listed? Subscribe to the outbound webhooks for event notifications, or email [email protected].
Example
curl https://rebaar.in/api/v1/projects \ -H "Authorization: Bearer ck_live_xxxxxxxx_…"
Response (shortened):
{
"object": "list",
"data": [
{
"id": "casa-verde",
"name": "Casa Verde",
"type": "Residential",
"status": "On Track",
"value": 8750000, // value in paise (₹87,500)
"progress": 72,
"client": { "id": "marigold-estates", "name": "Marigold Estates" }
}
],
"hasMore": false
}Webhooks
Outbound webhooks fire on workspace events: invoice.created, invoice.paid, invoice.overdue, project.created, project.status_changed, lead.created, lead.won, quotation.approved, quotation.rejected.
Configure your endpoint URL in Settings → Webhooks. Every request is signed following the Standard Webhooks specification:
webhook-id: <unique per message; REUSED across retries — use as your idempotency key> webhook-timestamp: <unix seconds, when THIS attempt was sent> webhook-signature: v1,<base64 HMAC-SHA256> x-rebaar-event: invoice.paid (convenience only — NOT signed; never branch on it before verifying)
The signed content is {webhook-id}.{webhook-timestamp}.{raw body}, joined by full stops. Use the raw request body — parsing and re-serialising changes the bytes and the signature will not match. Reject anything whose timestamp is more than 5 minutes from your clock, in either direction; that is what stops a captured request being replayed later.
const crypto = require('crypto')
function verify(req, rawBody, secret) {
const id = req.headers['webhook-id']
const ts = Number(req.headers['webhook-timestamp'])
if (!Number.isFinite(ts)) return false
if (Math.abs(Math.floor(Date.now() / 1000) - ts) > 300) return false // replay window
const expected = 'v1,' + crypto
.createHmac('sha256', secret)
.update(id + '.' + ts + '.' + rawBody)
.digest('base64')
// Any entry may match — the header is a space-delimited list so we can rotate secrets.
return String(req.headers['webhook-signature'])
.split(' ')
.some(sig => sig.length === expected.length &&
crypto.timingSafeEqual(Buffer.from(sig), Buffer.from(expected)))
}We retry failed deliveries with exponential backoff up to 5 attempts, then move them to the dead-letter queue. A retry keeps the same webhook-id but carries a fresh webhook-timestamp and signature, so verification succeeds however late it arrives — deduplicate on webhook-id.
Errors
401 Authorization header missing or invalid 402 Plan doesn't include this — currentPlan / requiredPlan in body 402 Plan limit reached — used / limit in body 402 Workspace access expired — trial / past_due / cancelled / suspended 404 Resource not found 429 Rate limited (120 req/min) — Retry-After header set 5xx Internal — retry with exponential backoff