Quick start
- In Rentsi, open Settings → 🧑💻 Developers and create an API key. Give it only the access it needs. Copy the key: it is shown once.
- Call the API with the key in the
Authorizationheader.
curl https://YOUR-RENTBOOK/api/v1/properties \
-H "Authorization: Bearer rbk_…"
Every response is JSON. Lists look like {"data":[…],"count":42,"limit":50,"offset":0,"has_more":false,"next":null}; single records like {"data":{…}}.
The base address is your own Rentsi address followed by /api/v1.
Keys & scopes
Send Authorization: Bearer rbk_… on every call. Keys belong to one company: a key only ever sees the company that created it. Keys are stored only as a fingerprint (SHA-256 hash), so Rentsi can't show a key again after it's created. Revoke a key any time in Settings → Developers; it stops working at once.
Each key has access per area, set when it's created: none, read (GET) or read & write (GET, POST, PATCH).
| Area | Resources |
|---|---|
| properties | /properties |
| units | /units |
| tenants | /tenants |
| leases | /leases |
| payments | /charges and /payments |
| workorders | /workorders |
| vendors | /vendors |
GET /api/v1 returns the key's name and scopes, which is handy for checking a key.
Pagination & filters
Lists are sorted by id. Use limit (1–200, default 50) and follow next until it's null. next uses a cursor (the last id you got), so records added meanwhile don't shift pages. offset also works.
| Filter | Works on | Meaning |
|---|---|---|
property=ID | all | Only records in that property |
updated_since=2026-10-01T00:00:00Z | all | Records changed since (by updatedAt) |
since=2026-10-01 | charges, payments, work orders, leases | Dated on or after |
lease=ID | charges, payments | One lease |
status=active | leases, work orders | By status |
Creating & changing
POST /api/v1/{resource} creates a record and answers 201. PATCH /api/v1/{resource}/{id} changes only the fields you send. Only the fields listed under each resource can be set; anything else is refused with 400 unknown_field.
- Charges and payments can be created only. To correct one, the office voids it in Rentsi. Dates inside closed books are refused.
- New payments land as undeposited funds for the office to deposit, and are applied to the oldest open charges.
- Deleting isn't available through the API.
- Send an
Idempotency-Keyheader on POST to make retries safe: the same key returns the first result instead of a duplicate.
curl -X POST https://YOUR-RENTBOOK/api/v1/workorders \
-H "Authorization: Bearer rbk_…" -H "Content-Type: application/json" \
-H "Idempotency-Key: 7f3c-leak-3b" \
-d '{"unitId":"u5","issue":"Leak under kitchen sink","priority":"High","category":"Plumbing"}'
Rate limits
120 requests a minute and 3,000 an hour per key. Each response carries X-RateLimit-Limit and X-RateLimit-Remaining; over the limit you get 429 with Retry-After. Every call, read or write, is written to the company's activity log with the key's name.
Errors
Errors look like {"error":{"code":"forbidden","message":"This key has no write access to tenants."}}.
| HTTP | code | When |
|---|---|---|
| 400 | bad_json, unknown_field | The body isn't JSON or sets a field that can't be set |
| 401 | unauthorized | Missing, wrong or revoked key |
| 403 | forbidden | The key has no access to that area |
| 404 | not_found | No such resource or record |
| 405 | method_not_allowed | For example PATCH on a payment |
| 422 | invalid | A value failed a check (missing field, unknown unit, closed books…) |
| 429 | rate_limited | Too many calls |
Data that is never sent
Resources
Loading the field list…
Webhooks
Add a webhook in Settings → 🧑💻 Developers → Webhooks: an https:// address and the events you want. Rentsi sends a JSON POST each time one happens, from the app, the tenant portal, imports or the API.
| Event | When |
|---|---|
tenant.created | A tenant is added |
payment.received | A payment is recorded (office, portal or API) |
workorder.created | A work order is opened |
workorder.updated | A work order's status, vendor, priority, dates or notes change; data.changes lists the fields |
lease.created | A lease is added |
ping | The “Send test” button |
POST /your/endpoint
Content-Type: application/json
RentBook-Event: workorder.created
RentBook-Delivery: whd_… (unique per attempt)
RentBook-Event-Id: evt_… (same for retries: use it to skip duplicates)
RentBook-Timestamp: 1760918400
RentBook-Signature: t=1760918400,v1=5f2b…
{"id":"evt_…","type":"workorder.created","created":"2026-10-20T00:00:00.000Z","company":"…",
"data":{"object":{"id":"wo…","number":1025,"unitId":"u5","issue":"Leak under kitchen sink","status":"Open",…}}}
Verifying signatures
Each webhook has its own signing secret (whsec_…, shown once when you add it). Compute HMAC-SHA256 of timestamp + "." + raw body with the secret and compare it to v1. Reject timestamps older than 5 minutes.
// Node.js
const crypto = require("crypto");
function verify(rawBody, header, secret) {
const p = Object.fromEntries(header.split(",").map(x => x.split("=")));
if (Math.abs(Date.now() / 1000 - Number(p.t)) > 300) return false;
const mac = crypto.createHmac("sha256", secret).update(p.t + "." + rawBody).digest("hex");
return crypto.timingSafeEqual(Buffer.from(mac), Buffer.from(p.v1));
}
# Python
import hmac, hashlib, time
def verify(raw_body: bytes, header: str, secret: str) -> bool:
p = dict(x.split("=", 1) for x in header.split(","))
if abs(time.time() - int(p["t"])) > 300: return False
mac = hmac.new(secret.encode(), p["t"].encode() + b"." + raw_body, hashlib.sha256).hexdigest()
return hmac.compare_digest(mac, p["v1"])
Retries & delivery log
Answer with any 2xx within 10 seconds. Otherwise Rentsi retries up to 7 times in all: right away, then after 1 min, 5 min, 30 min, 2 h, 6 h and 12 h. Redirects are not followed. Every attempt is in the delivery log in Settings → Developers, where the office can view the payload and resend it.
Rentsi API v1