for developers
API documentation
A small REST API for reading and managing your own inventory data programmatically — built for the real case of an insurance agent wanting to pull a policyholder's inventory into their own claims system, or anyone who wants to script something against their own data. This is the entire API surface — nothing hidden or undocumented.
Authentication
Generate a key from Account settings → Developer API. Every request needs it as a Bearer token — there's no session/cookie auth on this API, and a key only ever sees and modifies the data of the account that created it.
Authorization: Bearer <your-api-key>
Keep your key secret — anyone with it can read and change your inventory data. You can revoke a key at any time from the same account page.
Rate limits
Requests with a missing/invalid key are limited per IP (20 per 5 minutes) so a bad key can't be hammered indefinitely. Authenticated requests are limited per key (120 per minute) — generous for real polling or bulk sync, not for a runaway loop. Either limit returns 429.
Pagination
Every list endpoint accepts ?limit= (default 50, max 200) and ?offset= (default 0), and returns a pagination object with the total row count so you know when to stop paging.
GET /api/v1/inventories
Lists every inventory the key's account owns, newest first.
curl https://homeledger.casa/api/v1/inventories \ -H "Authorization: Bearer <your-api-key>"
{
"inventories": [
{ "id": "…", "name": "My home", "created_at": "…", "updated_at": "…" }
],
"pagination": { "limit": 50, "offset": 0, "total": 1 }
}GET /api/v1/inventories/:id/items
Lists items in one inventory, newest first. Returns 404 if the inventory doesn't exist or isn't owned by the key's account — a key can never see another account's data, regardless of the ID requested. Optional filters: room (exact room name), status (active or possibly_missing), and minValueCents / maxValueCents (replacement-value range).
curl "https://homeledger.casa/api/v1/inventories/<inventory-id>/items?room=Living%20Room&minValueCents=5000" \ -H "Authorization: Bearer <your-api-key>"
{
"items": [
{
"id": "…",
"description": "Blue leather sofa",
"category": "Furniture",
"brand": "IKEA",
"model_number": "KLIPPAN",
"estimated_value_cents": 89900,
"estimated_current_value_cents": 62300,
"receipt_url": null,
"thumbnail_url": "https://…signed-url…",
"status": "active",
"rooms": { "name": "Living room" }
}
],
"pagination": { "limit": 50, "offset": 0, "total": 1 }
}POST /api/v1/inventories/:id/items
Creates an item in the inventory. description is required; category, brand, model_number, estimated_value_cents, estimated_current_value_cents, and room (a room name — found or created for you) are optional. Returns 201 with the created item in the same shape the list endpoint returns.
curl -X POST https://homeledger.casa/api/v1/inventories/<inventory-id>/items \
-H "Authorization: Bearer <your-api-key>" \
-H "Content-Type: application/json" \
-d '{"description": "Standing lamp", "category": "Decor", "estimated_value_cents": 8900, "room": "Living Room"}'PATCH /api/v1/inventories/:id/items/:itemId
Updates an item's description, category, and/or estimated_value_cents— send only the fields you want to change. Returns the updated item, or 404 if the item doesn't exist in this inventory.
curl -X PATCH https://homeledger.casa/api/v1/inventories/<inventory-id>/items/<item-id> \
-H "Authorization: Bearer <your-api-key>" \
-H "Content-Type: application/json" \
-d '{"estimated_value_cents": 9500}'DELETE /api/v1/inventories/:id/items/:itemId
Deletes an item. Returns { "deleted": true }, or 404 if the item doesn't exist in this inventory.
GET /api/v1/inventories/:id/loss-reports
Lists loss/claim reports filed against this inventory, each with its damaged-item list and full claim-status timeline (every status change and milestone note, not just the current status). Read-only — claims are filed and updated in the app, not via this API. Pass ?itemId= to narrow to reports that reference one specific item.
curl https://homeledger.casa/api/v1/inventories/<inventory-id>/loss-reports \ -H "Authorization: Bearer <your-api-key>"
{
"lossReports": [
{
"id": "…",
"name": "Kitchen fire",
"incident_type": "fire",
"incident_date": "2026-06-01",
"created_at": "…",
"claim_status": "filed",
"claim_filed_at": "2026-06-03",
"claim_settled_amount_cents": null,
"claim_notes": null,
"claim_deadline": "2026-09-01",
"items": [
{ "id": "…", "description": "Espresso machine", "category": "Appliances", "damage_severity": "destroyed",
"damage_description": "Smoke and heat damage", "estimated_value_cents": 45000, "matched_item_id": "…" }
],
"events": [
{ "id": "…", "event_type": "status_change", "status": "filed", "note": null, "created_at": "…" }
]
}
]
}Webhooks
Subscribe from Account settings → Webhooks to get a POST request at your own URL instead of polling the API. Three events fire today:
item.created— an item was created viaPOST /api/v1/inventories/:id/items.claim.status_changed— a loss report's claim status actually transitioned (not a no-op re-save of the same status).scan.completed— an in-app scan saved at least one item to an inventory.
Payload shape
Every delivery is a POST with this envelope; data varies per event:
{
"event": "item.created",
"createdAt": "2026-07-16T12:00:00.000Z",
"data": { "inventoryId": "…", "item": { "id": "…", "description": "Standing lamp", … } }
}Verifying the signature
Every request carries an X-Webhook-Signature header in the same t=<timestamp>,v1=<signature> format Stripe uses: an HMAC-SHA256 of {timestamp}.{raw body}, keyed with the signing secret shown once when you create the webhook. Verify it before trusting the payload:
const crypto = require("crypto");
function verify(rawBody, header, secret, toleranceSeconds = 300) {
const parts = Object.fromEntries(header.split(",").map((kv) => kv.split("=")));
const timestamp = Number(parts.t);
if (Math.abs(Date.now() / 1000 - timestamp) > toleranceSeconds) return false;
const expected = crypto
.createHmac("sha256", secret)
.update(`${timestamp}.${rawBody}`)
.digest("hex");
return crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(parts.v1));
}Use the raw request body, not a re-serialized/parsed one — re-serializing JSON can change whitespace and break the signature. A disabled webhook stops receiving new events but keeps its delivery history and secret.
Retries
A delivery that doesn't get a 2xx response (or times out after 10s) is retried up to 4 more times with backoff: 5 minutes, 30 minutes, 2 hours, then 6 hours. Every attempt is logged and visible under that webhook's "Deliveries" in account settings.
Zapier
A Zapier integration (New Item, Claim Status Changed, Scan Completed triggers) is built on this exact webhook API — no separate signing or auth, same events and payloads as above. It's not yet published on Zapier's public app directory, so it won't show up in a Zapier search today. If you want to connect Roomtally to Zapier before that lands, get in touch and generate an API key from the Authentication section above in the meantime.
Errors
400 — invalid or missing required fields on a write. 401 — missing or invalid/revoked key. 404 — inventory or item not found, or not owned by this key. 429 — rate limit exceeded, see above.
This is the entire API surface. Claims can only be read, not written, via the API, and webhooks currently cover the three events above only. If you need something this doesn't cover, get in touch.