How the API is organized
Hubit is built as a set of focused backend services sitting behind a single gateway. The gateway exposes two separate surfaces: an internal API that only the Hubit web and mobile apps use (session-based, evolves freely), and a stable external API at /api/external/v1 for third-party integrations — kept deliberately separate so internal changes never silently break something you built against.
Every response is wrapped in a consistent envelope — success flag, message, data, and pagination metadata where relevant — so client code can handle any endpoint the same way.
Authentication
The external API authenticates with an API key, not a login session. Generate one from Workspace Settings → Developer — the key is shown exactly once at creation, so copy it somewhere safe immediately. A key always speaks for your workspace and acts with owner-level read access.
- Create a key from Workspace Settings → Developer (owner only)
- Send it as Authorization: Bearer hbt_live_... on every request to /api/external/v1
- Revoke a key any time from the same settings screen — revocation takes effect immediately
- Keys are workspace-scoped: a key only ever sees the data of the workspace that issued it
Available endpoints
The external API is read-primary at launch — a small, explicitly documented set of GET endpoints, not a mirror of the full internal surface:
- GET /api/external/v1/properties and /properties/{id} — your properties
- GET /api/external/v1/properties/{propertyId}/units and /units/{unitId} — units within a property
- GET /api/external/v1/tenants and /tenants/{id} — active and past leases
- GET /api/external/v1/payments and /payments/{id} — your payment history
Webhooks
Register a webhook endpoint from Workspace Settings → Developer to receive events as they happen instead of polling. The endpoint must be an https URL that resolves to a public address — plain http, localhost, and private/internal addresses are all rejected at registration. The current event catalog: payment.received, task.completed, and lease.created — each delivered as a POST with a JSON body containing the event name, the underlying data, and a timestamp.
Every delivery is signed. Compute an HMAC-SHA256 over the raw request body using the endpoint's signing secret (shown on the same settings screen, any time — unlike an API key, you'll need to keep referring back to it), and compare it — using a constant-time comparison — against the X-Hub-Signature-256 header, which arrives as "sha256=" followed by the hex-encoded digest. Reject any delivery whose signature doesn't match.
A failed delivery (a non-2xx response, a timeout, or a connection error) is retried up to 5 times with increasing delays — 1 minute, 5 minutes, 30 minutes, 2 hours, then 6 hours — before being marked permanently failed. Respond quickly with a 2xx as soon as you've durably queued the event on your end; don't do slow processing inline in the request handler.
Delivery is at-least-once, not exactly-once — in rare cases (e.g. a retry racing a slow response) you may receive the same event twice. Every delivery carries a unique X-Hubit-Delivery-Id header; store the IDs you've already processed and skip anything you've seen before, rather than assuming each delivery is new.
Rate limits and errors
The external API has its own, tighter rate-limit budget than the in-app traffic the internal API serves, so a scheduled integration polling on a timer never competes with your own dashboard's responsiveness. Errors return a structured error code and human-readable message in the same response envelope as success responses, so you can branch on the code rather than parsing text.
