Skip to content
Features Pricing About Contact Log in Start free trial
Developers

API and webhooks

Read your portfolio from your own software, and be told the moment a lease, a rent payment or an expense is recorded.

Getting started

The API gives read-only JSON access to one business's records (included in the Pro and Enterprise plans). The owner or an admin makes a key under Settings → API keys; send it with every request:

curl "https://leasystream.com/api/v1/properties?limit=5" \
  -H "Authorization: Bearer lsapi_your_key_here"
  • Base address: https://leasystream.com/api/v1. HTTPS only.
  • The key goes in the Authorization: Bearer header and nowhere else. Cookies and keys in the address are ignored.
  • A key reads the whole business: it is not limited to a person, branch or property. It cannot change anything.
  • Amounts are numbers, and every record with money has a currency code next to them. Times are ISO 8601 in the business's timezone; dates are YYYY-MM-DD.
  • GET https://leasystream.com/api/v1/me tells you which business a key belongs to, its currency and timezone.

Lists and pages

GET /{resource} returns up to limit records (default 50, most 100). Ask for the next page with page=2, or follow links.next until it is null:

{
  "data":  [ { … }, { … } ],
  "meta":  { "page": 1, "limit": 50, "has_more": true },
  "links": { "next": "/api/v1/properties?page=2&limit=50" }
}

GET /{resource}/{id} returns one record as {"data": { … }}. Filters go in the query string; a filter the resource does not have is ignored. Remember to URL-encode values (a + in a time must be sent as %2B).

Errors and limits

StatuscodeMeaning
400bad_requestA filter value is wrong, for example a date not written as YYYY-MM-DD.
401unauthorizedMissing, wrong or revoked key.
403plan, subscription_suspendedThe business's plan does not include the API, or its subscription is suspended.
404not_foundNo such resource or record.
405method_not_allowedOnly GET is supported.
429rate_limitedMore than 120 requests in a minute with one key. Wait for the seconds given in Retry-After.

Errors look like {"error": {"code": "not_found", "message": "No record with that id."}}. Answers carry X-RateLimit-Limit and X-RateLimit-Remaining.

Resources

ResourceWhat it isSingle recordFilters
properties Buildings and plots in the portfolio. yes q, status, branch_id, updated_since
units Rentable units inside properties. yes property_id, status, updated_since
renters People and organisations who rent. yes q, status, updated_since
landlords Property owners the business manages for. yes q, status, updated_since
leases Leases, newest first. yes status, renter_id, landlord_id, property_id, unit_id, updated_since
rent-charges What is owed for each rent period, newest first. yes lease_id, status, from, to
rent-payments Rent received, newest first. yes lease_id, renter_id, status, from, to
payments Other money received (deposits, application and reservation fees), newest first. yes purpose, status, from, to
expenses Money spent, newest first. yes property_id, vendor_id, payment_status, from, to
disbursements Payouts to landlords, newest first. yes landlord_id, property_id, status
maintenance Maintenance requests, newest first. yes property_id, unit_id, status, priority
branches The business's offices. list only none

properties

Buildings and plots in the portfolio. GET /api/v1/properties and GET /api/v1/properties/{id}

Filters: q (name or code contains); status (active, inactive, under_construction, sold, archived); branch_id (a branch); updated_since (changed on or after this date or time).

FieldMeaning
idRecord id
codeProperty code
nameName
typeresidential, commercial, mixed_use, industrial or land
categoryKind of building
ownership_modeowner_operator, managed or mixed
units_countNumber of units recorded on the property
addressline1, line2, city, state, postal_code, country
branchid and name, or null
landlordPrimary owner: id and name, or null
statusStatus
created_atWhen it was added
updated_atWhen it last changed

units

Rentable units inside properties. GET /api/v1/units and GET /api/v1/units/{id}

Filters: property_id (a property); status (available, occupied, reserved, maintenance, off_market); updated_since (changed on or after this date or time).

FieldMeaning
idRecord id
propertyid and name
codeUnit code
nameUnit name
blockBlock
floorFloor
typeUnit type
bedroomsBedrooms
bathroomsBathrooms
floor_areaFloor area
floor_area_unitsqm or sqft
base_rentAsking rent
base_rent_frequencymonthly, weekly, daily, quarterly or annually
currencyCurrency of base_rent (the business currency)
statusStatus
available_fromDate it becomes available
created_atWhen it was added
updated_atWhen it last changed

renters

People and organisations who rent. GET /api/v1/renters and GET /api/v1/renters/{id}

Filters: q (name, email or phone contains); status (active, pending, suspended, blacklisted, archived); updated_since (changed on or after this date or time).

FieldMeaning
idRecord id
numberRenter number
typeperson or organization
nameName
emailEmail
phonePhone
alt_phoneSecond phone
statusStatus
branchid and name, or null
created_atWhen it was added
updated_atWhen it last changed

landlords

Property owners the business manages for. GET /api/v1/landlords and GET /api/v1/landlords/{id}

Filters: q (name, email or phone contains); status (active, pending, suspended, blacklisted, archived); updated_since (changed on or after this date or time).

FieldMeaning
idRecord id
numberLandlord number
typeperson or organization
nameName
emailEmail
phonePhone
alt_phoneSecond phone
statusStatus
branchid and name, or null
created_atWhen it was added
updated_atWhen it last changed

leases

Leases, newest first. GET /api/v1/leases and GET /api/v1/leases/{id}

Filters: status (draft, active, expired, terminated, renewed); renter_id (a renter); landlord_id (a landlord); property_id (a property); unit_id (a unit); updated_since (changed on or after this date or time).

FieldMeaning
idRecord id
numberLease number
kindhead or sublease
statusStatus
unitid, code and name
propertyid and name
renterid and name
landlordid and name (on a sub-lease: the head tenant, who is a renter and not in landlords)
start_dateStart date
end_dateEnd date
rent_amountRent per period
currencyCurrency of the lease
rent_frequencymonthly, weekly, daily, quarterly or annually
payment_due_dayDay of the month rent falls due
security_depositSecurity deposit agreed
move_in_dateMove-in date
move_out_dateMove-out date
terminated_atDate terminated
created_atWhen it was added
updated_atWhen it last changed
is_subleasetrue when this lease is a sub-lease
head_leaseid and number of the lease it is a sub-lease of, or null
sublease_scopewhole_unit or partial on a sub-lease, otherwise null
sublease_portionThe part of the unit sub-let, or null
sublease_admin_fee_pctPercentage of each sub-rent payment the business keeps, or null when not a sub-lease

rent-charges

What is owed for each rent period, newest first. GET /api/v1/rent-charges and GET /api/v1/rent-charges/{id}

Filters: lease_id (a lease); status (pending, partial, paid, overdue, waived); from (due on or after); to (due on or before).

FieldMeaning
idRecord id
numberCharge number
leaseid and number
period_startPeriod start
period_endPeriod end
due_dateDue date
rent_dueRent
extras_dueOther charges
late_fee_dueLate fees
total_dueTotal
amount_paidPaid so far
balanceStill owed
currencyCurrency
statusStatus
paid_atWhen it was paid in full
created_atWhen it was raised

rent-payments

Rent received, newest first. GET /api/v1/rent-payments and GET /api/v1/rent-payments/{id}

Filters: lease_id (a lease); renter_id (a renter); status (recorded or voided); from (paid on or after); to (paid on or before).

FieldMeaning
idRecord id
receipt_numberReceipt number
leaseid and number
renterid and name
dateDate paid
amountAmount
currencyCurrency
methodPayment method name
referenceTransaction or cheque reference
statusrecorded or voided
voided_atWhen it was voided
created_atWhen it was recorded
verification_codeThe code printed on the receipt for checking it at /verify (XXXX-XXXX-XXXX), or null when the receipt has none yet
allocationsSingle record only: the rent charges this payment was applied to (rent_charge_id, charge_number, amount)

payments

Other money received (deposits, application and reservation fees), newest first. GET /api/v1/payments and GET /api/v1/payments/{id}

Filters: purpose (application_fee, deposit_only, refund_received, reservation_fee, utility_deposit, other); status (recorded or voided); from (paid on or after); to (paid on or before).

FieldMeaning
idRecord id
numberPayment number
purposeWhat it was for
partyWho paid: id and name, or null
leaseid and number, or null
dateDate paid
amountAmount
currencyCurrency
methodPayment method name
referenceTransaction or cheque reference
statusrecorded or voided
voided_atWhen it was voided
created_atWhen it was recorded
verification_codeThe code printed on the receipt for checking it at /verify (XXXX-XXXX-XXXX), or null when the receipt has none yet

expenses

Money spent, newest first. GET /api/v1/expenses and GET /api/v1/expenses/{id}

Filters: property_id (a property); vendor_id (a vendor); payment_status (paid, pending, voided); from (dated on or after); to (dated on or before).

FieldMeaning
idRecord id
numberExpense number
dateExpense date
categoryCategory name
descriptionDescription
amountAmount
currencyCurrency
payment_statuspaid, pending or voided
vendorid and name, or null
propertyid and name, or null
unit_idUnit, or null
chargeable_to_landlordWhether the landlord bears it
voided_atWhen it was voided
created_atWhen it was recorded

disbursements

Payouts to landlords, newest first. GET /api/v1/disbursements and GET /api/v1/disbursements/{id}

Filters: landlord_id (a landlord); property_id (a property); status (draft, approved, paid, cancelled).

FieldMeaning
idRecord id
numberDisbursement number
landlordid and name
propertyid and name, or null
period_startPeriod start
period_endPeriod end
gross_collectedRent collected
management_feeManagement fee
tax_withheldTax withheld
expenses_deductedExpenses deducted
adjustmentsAdjustments
net_paidNet amount to the landlord
currencyCurrency
statusStatus
paid_atWhen it was paid
created_atWhen it was prepared

maintenance

Maintenance requests, newest first. GET /api/v1/maintenance and GET /api/v1/maintenance/{id}

Filters: property_id (a property); unit_id (a unit); status (pending, triaged, work_order_created, in_progress, completed, cancelled, duplicate); priority (emergency, high, medium, low).

FieldMeaning
idRecord id
numberRequest number
titleTitle
descriptionWhat was reported
categoryCategory name
priorityemergency, high, medium or low
statusStatus
propertyid and name, or null
unitid, code and name, or null
reported_atWhen it was reported
completed_atWhen it was completed
created_atWhen it was logged
updated_atWhen it last changed

branches

The business's offices. GET /api/v1/branches

FieldMeaning
idRecord id
nameName
codeBranch code
locationLocation
is_mainThe main branch
is_activeIn use

Not available through the API or webhooks: staff accounts, internal notes and void reasons, national IDs and dates of birth, renters' employment details, landlords' bank details, and uploaded documents.

Webhooks

Webhooks (Growth, Pro and Enterprise plans) push events to your server instead of you asking. The owner or an admin adds an https address under Settings → Webhooks and picks the events. A POST with a JSON body arrives within about a minute:

POST /your/path HTTP/1.1
Content-Type: application/json
Leasystream-Event: lease.created
Leasystream-Delivery: evt_<number>
Leasystream-Signature: t=<unix time>,v1=<64 hex characters>

{
  "id": "evt_<number>",
  "type": "lease.created",
  "created_at": "<ISO 8601 time>",
  "business": { "id": <number>, "name": "<business name>" },
  "data": { … the record, in the same shape as the API returns it … }
}
  • Answer with any 2xx status within 10 seconds; do slow work after answering.
  • Otherwise it is tried again after 1 min, 5 min, 30 min, 2 h, 6 h, 12 h, 24 h, then marked failed. After 50 failures in a row the webhook is switched off.
  • The same event can occasionally arrive twice, and events can arrive out of order: use id to ignore repeats.
  • The address must be https with a valid certificate and must be on the public internet. Redirects are not followed.
  • data shows the record as it was when the message was first sent; retries carry the same body.

Checking the signature

Leasystream-Signature holds a time (t) and v1, the HMAC-SHA256 of t + "." + the raw request body, keyed with the webhook's signing secret (whsec_…, shown once when the webhook is added or a new secret is made). Compute it yourself over the bytes exactly as received, compare in constant time, and refuse messages more than 5 minutes old.

// PHP
$body   = file_get_contents('php://input');
$secret = getenv('LEASYSTREAM_WEBHOOK_SECRET');           // whsec_…
$header = $_SERVER['HTTP_LEASYSTREAM_SIGNATURE'] ?? '';
$ok = preg_match('/^t=(\d+),v1=([0-9a-f]{64})$/', $header, $m)
   && abs(time() - (int) $m[1]) <= 300
   && hash_equals(hash_hmac('sha256', $m[1] . '.' . $body, $secret), $m[2]);
if (!$ok) { http_response_code(400); exit; }
$event = json_decode($body, true);
http_response_code(200);
// Node.js (Express: use express.raw({ type: 'application/json' }) on this route,
// so req.body is the raw Buffer; header is req.get('Leasystream-Signature'))
const crypto = require('crypto');
function verify(rawBody, header, secret) {
  const m = /^t=(\d+),v1=([0-9a-f]{64})$/.exec(header || '');
  if (!m || Math.abs(Date.now() / 1000 - Number(m[1])) > 300) return false;
  const expected = crypto.createHmac('sha256', secret)
    .update(m[1] + '.').update(rawBody).digest();
  return crypto.timingSafeEqual(expected, Buffer.from(m[2], 'hex'));
}

Events

EventWhendata is
lease.createdA lease is entered (as a draft or already active)one of leases
lease.activatedA lease becomes activeone of leases
lease.endedA lease is terminated or expiresone of leases
rent_charge.createdA rent charge is raised for a periodone of rent-charges
rent_charge.paidA rent charge is paid in fullone of rent-charges
rent_charge.overdueA rent charge passes its due date unpaidone of rent-charges
rent_payment.receivedA rent payment is recordedone of rent-payments
rent_payment.voidedA rent payment is voidedone of rent-payments
payment.receivedAnother payment is recorded (deposit, application or reservation fee, …)one of payments
payment.voidedOne of those other payments is voidedone of payments
renter.createdA renter is addedone of renters
renter.updatedA renter's name, email, phone or status changesone of renters
landlord.createdA landlord is addedone of landlords
landlord.updatedA landlord's name, email, phone or status changesone of landlords
disbursement.paidA landlord disbursement is marked paidone of disbursements
expense.createdAn expense is recordedone of expenses
expense.voidedAn expense is voidedone of expenses
maintenance.createdA maintenance request is loggedone of maintenance
maintenance.completedA maintenance request is completedone of maintenance
pingYou press "Send a test event"{"message": "…"}

renter.updated and landlord.updated are combined when a record changes several times before the message goes out. A rent_payment.received message includes allocations, the rent charges the payment was applied to.