Rental Agreements
A Rental Agreement tracks the full paid-rental lifecycle — from draft through reservation, pickup, return, and final close-out — and generates a Customer Invoice with the actual duration, late fees, and any damage charges when you close it.
A Rental Agreement is the central record for a paid equipment rental. It ties a customer to one or more pieces of equipment for a date range, tracks the deposit and billing terms, and drives the status transitions that move equipment from your shelf to the customer and back. When you close the agreement a Customer Invoice is generated automatically, priced against the actual rental duration — not just the planned one.
Rental Agreements cover paid rentals billed to a customer. If you need to lend equipment at no charge, use a Loan Agreement instead.
Creating a rental agreement #
New agreements are created from Rentals → Rental Agreements → New Rental Agreement. Every agreement starts in Draft status, which lets you fill in details and add line units before committing to anything.
- Customer — the customer being billed. Required; search by name, email address or phone number (see Choosing a customer on a form). Drives the contact list and is used to resolve customer-specific rental rates.
- Contact — an optional contact from the selected customer for the person picking up the equipment.
- Location — which inventory location the equipment comes from. Used for location-scoped rate lookups.
- Start date / Expected return — the planned rental window. Both are required. The expected return date is used for overdue detection and for availability checks.
- Security deposit — the deposit amount for this rental (in dollars in the UI; stored in cents). Knowledge ERP does not record whether it was paid. When you close the agreement, the deposit goes against any damage and replacement charges, with their tax, and whatever is left of it is refunded as a credit memo; when those charges use it all up, there is no memo.
- Late fee % — a surcharge on each day a unit is kept past the expected return, worked out as this percentage of the daily equivalent of the line's rate and added as its own line on the final invoice. The late fee is on top of the normal rate: a unit kept past the expected return is also billed its line's normal rate for each late day, up to the day it comes back, or the day you close the agreement if that comes first.
- Damage charge — what the damage cost. You will normally not type this here: the Return action asks about damage as the equipment comes back, and adds what you record to this field. The whole charge goes on the invoice and is taxed at the rental's tax rate; the security deposit then goes against it. Entered in dollars; stored in cents.
- Billing cycle — Bill at close only, Weekly, or Monthly. Weekly or monthly
creates a Draft invoice for each cycle that bills something, and the
billed_through_dateadvances after each cycle run. A weekly cycle is 7 days: picked up on July 1, the first covers July 1 to July 7 and is invoiced on July 8. A monthly cycle is a calendar month: picked up on March 3, the first covers March 3 to April 2. Each cycle invoice bills the days, weeks or months of each line's rate that start in that cycle, so a monthly rate on a weekly cycle is billed in the week each month starts. A cycle in which none starts has no invoice. Whatever no cycle has billed is on the final invoice, counted as your account counts them. A rental still out after its expected return keeps being billed on its cycle invoices, at the normal rate, until the equipment comes back or you close the agreement; the late fee is charged once, on the final invoice. When a customer's request to extend an overdue rental is approved (by your staff or automatically) and its location charges the late fee on extension, the late fee for the days it was overdue before the extension starts is kept and shown on the final invoice as its own line, for example Late fee (Oct 11 – Oct 14, 2026, before the extension). Undoing the extension from the change log removes it with the new return date. - Email each period's invoice to the customer — shown for a weekly or monthly cycle. When it is on, each period's invoice is emailed to the customer as soon as it is billed, marked Sent, and so shown in their portal, with a Pay online button if you take online payments. It is on for a new agreement; agreements that existed before this setting was added start with it off. A customer with no email address gets nothing, and the invoice stays a Draft for you to send. The invoice at close is not emailed automatically.
- Tax rate — an optional tax rate applied when the invoice is generated.
- Notes (printed on the customer's copy) — free text that appears on the agreement PDF the customer receives. It is not copied onto the invoice.
- Internal notes (staff only) — free text that is never printed, emailed or shown in the customer portal.
- Custom fields — any fields your account has defined for rental agreements appear at the bottom of the form, and on the agreement's details page.
The agreement number is auto-generated in the format RA-0001, RA-0002, etc.
Rental Agreement Items #
Line items are added from the Line Items tab on the agreement's view page. Each line represents one type or unit of equipment being rented. Lines can be added, changed and removed only while the agreement is in Draft — see What you can change at each stage.
- Rentable — what is being rented. A line can point to a Product (a class of equipment, e.g. "Lift, 40ft"), a specific Unit (a serialized unit), or a Kit (a bundle). Product-based lines use quantity; unit-based lines check out the exact unit.
- Quantity — how many units (relevant for Product-based lines).
- Rate period — Daily, Weekly, or Monthly. Determines the billing unit.
- Rate amount — the price per period, in dollars in the UI (cents in the API). Leave it blank and the system snapshots the matching Rental Rate when you reserve. Type 0 and the line is rented out free of charge — a goodwill loaner, a warranty replacement, a demo unit you still want tracked and invoiced at $0. A rate below the product's rental rate, or 0, needs the Rent below the list rate, or for nothing permission; without it the line is refused and says why. The permission counts at the agreement's Location; on an agreement with no Location, holding it only at some Locations is not enough, so set the Location first. In the Line Items tab, a line priced differently from the rental rate shows the list rate under its own, for staff only.
- Notes — a note on the line for your own reference. Notes are not copied onto the invoice.
- Units — the individual units this line has handed over, once it has handed any over. See Which units a line handed over.
The Equipment picker only offers what can actually be rented. Consumable products — and individual units of them — are left out entirely, because renting one is refused outright. A returnable product with no rental rate is still offered, since typing a rate on the line rents it perfectly legally, but it is labelled as unpriced so you know before choosing it that a rate will be needed.
Searching Specific unit matches the product's name, the unit's own name, its lot number, and its barcode — so a unit can be found by scanning or typing the label in your hand, and each option reads as the product, the lot and the barcode together rather than a lot number on its own.
When the agreement has a Location, Specific unit lists only the units at that Location, and a unit at another Location is refused if it is named anyway, in the app or through the API. While a specific unit is on the agreement, its Location cannot be changed: not on the Edit page, not through the API, and not by Undo in the change log. Remove the unit lines first, which only a Draft allows. When the agreement has a Location, Pick Up is also refused if a line's unit is no longer at that Location. Someone who can see the unit's Location is told where it is. A unit shows its status after its name when it is not In Stock, and not available for these dates when it cannot be had for the agreement's dates, including a unit on the shelf that another rental has already reserved over those dates. It stays in the list rather than disappearing, because a unit that is out now can be due back before the hire starts. If a unit has gone out since the agreement was reserved, Pick Up is refused with a notice that stays on screen until you close it, naming the product and why.
Rentals are for Returnable equipment only — reserving or picking up a rental whose Product is marked Consumable is rejected, in the app and over the API alike. Renting something out promises its return, which consumable stock cannot make: it belongs on a purchase order or sold through Sales. The same rule applies to loans.
Which units a line handed over #
Under each line, the Units column names the individual units that line has handed out — the unit's name, its barcode, the Location it belongs to, and whether it is still out, has come back, or was marked lost. Each one is a link through to that unit's own record, if you have access to units.
The bin shown is the bin the unit is in now, so you see one only while the unit is on a shelf. A unit still out with the hirer shows its Location, if it has one, and no bin — picking it up takes it off its shelf — and so does one that has since gone out again. It is not a record of where that return put it: move the unit to another bin afterwards and this column follows it to the new one.
A reserved line for a product has no units to show yet, and says so. Reserving holds the line's quantity for the hire window; the specific units are chosen at Pick Up. So a reserved product line reads No units yet — each unit is named when the equipment is picked up, and fills in once the equipment leaves the Location.
A line you added with Specific unit already names its unit. That line shows the unit in its Equipment column, and its Units column reads Not picked up yet until the equipment leaves the Location. If your role does not cover that unit's Location, the Equipment column reads Unit not visible to you, and so does the unit's name in the Units column, which still shows the line's state beside it.
If your role only covers some of your Locations, a line can hand out a unit that lives at a Location you do not have access to. That unit still gets a row here — otherwise a line of two would appear to have handed out one — but instead of the unit's name the row reads Unit not visible to you, with the Location the unit went out from where that was recorded. A unit that has since been removed from your inventory reads the same way.
Blank is not the same as zero #
A blank rate means nobody has priced this line yet. A rate of 0 means someone decided this is free. Knowledge ERP keeps the two apart, because they lead to very different places.
If a line is still blank when you try to reserve or pick up — because no rental rate is configured for that Product and period, or the one that exists is inactive — the action is refused, naming the product and the period that is missing. The same check runs again when the invoice is raised at close. Nothing silently invoices $0.
A rate you typed yourself is never overwritten by the catalog, so a deliberate 0 stays 0 through the whole lifecycle. Zero is legitimate; unpriced is not.
If a pickup is refused, add the missing Rental Rate (or type a rate straight onto the line) and press Pick Up again — the rate is re-snapshotted on the retry. There is no need to unwind the reservation.
What the hire costs before it is invoiced #
The money columns on an agreement — the line totals, the subtotal, the total — are
written when the invoice is raised at close, and are zero before that. So every screen
that shows a rental's total shows an estimate until it is closed: the line items
table, the agreement's Charges panel, the agreements list, and the customer's own view in
the portal all label it as one. Over the API the same
number is estimated_total on the agreement, and line_total on each line.
The estimate runs the invoice's own arithmetic — the same window, the same per-unit period counts, the same damage, replacement and tax — so the price quoted at the counter is the price the invoice will carry. It moves for the same reasons the invoice would: extending the return date, changing a rate, or bringing a unit back early. A unit still out after its expected return is quoted up to today. The estimate covers only the days not yet invoiced, so on a weekly or monthly billing cycle it drops back each time a cycle invoice is raised.
An unpriced line contributes nothing to the estimate, which is the honest answer — nobody has decided what it costs, and reserving it is refused for that reason.
How rental days are counted #
Your account chooses how the days a rental is charged for are counted, under Account Settings → Rentals → Count rental days as. Only administrators can change it, and the box is shown to accounts with the Rentals module.
- 24-hour periods (the default). Out on October 22 and back on October 25 is 3 days. Out and back on the same day is 1 day. Days are counted on calendar dates, not by the hour, so the time of day a unit left or came back does not change the count.
- Count the pickup and return days. The pickup day and the return day both count: out on October 22 and back on October 25 is 4 days.
Each unit is counted to its own return day the same way. A rental still out is counted to its expected return date, or, once it is overdue, to today, so it is quoted what returning it that day would bill. A weekly or monthly rate bills enough whole weeks or calendar months to cover the days: 10 days on a weekly rate is 2 weeks, and July 1 to July 31 on a monthly rate is 1 month.
The late fee counts the days past the expected return the same way under either setting: back on October 27 against an expected return of October 25 is 2 late days.
A change to the setting applies to every rental that is not yet closed, including the rest of a rental already out. Invoices already raised do not change.
Each rental line on the invoice says how many days it covers and at what rate — "Scissor Lift — 3 days × $95.00", or on a weekly rate "Scissor Lift — 10 days, billed as 2 weeks × $400.00". Units of one line that were out the same number of days share a line, which says how many: "Scissor Lift (2 units) — 3 days × $95.00".
The rental lifecycle #
A rental agreement moves through a fixed set of statuses. Each transition is a distinct action in the UI and an API sub-action:
| Status | What it means |
|---|---|
| Draft | Being built — no holds placed, nothing committed. |
| Reserved | Availability confirmed; equipment is held for this date range. Rates are snapshotted. |
| Picked Up | Equipment has left the building; individual units are checked out and marked unavailable. |
| Partially Returned | Some units are back in stock or marked lost; the rest are still out. The agreement stays here until the last unit is returned or marked lost. |
| Returned | Every unit is back and checked in to a bin, or marked lost; not yet invoiced. The Return action starts on the Location's default receiving bin when the equipment comes back to one Location, and you can choose another. |
| Closed | Invoice generated as a Draft; any deposit left after the damage and replacement charges and their tax is refunded as a credit memo. Terminal. |
| Cancelled | Agreement ended without completion. Cannot cancel while status is Picked Up or Partially Returned — return the equipment first. |
Reserve checks availability across the full date range against other reserved and picked-up rentals and loans, and confirms each line can be fulfilled. If a line can't be, Reserve is refused and the message names it.
Reserving also refuses a line whose product, unit or kit has since been deleted, and names it; remove the line first. Picking up refuses it the same way.
What you can change at each stage #
Reserving checks the lines against availability and fills in any rate a line is missing, so from then on the parts of the agreement that check covered stay as they were:
| Status | Fixed | Can still change |
|---|---|---|
| Draft | Nothing. | Everything. |
| Reserved | The line items, the security deposit, the late fee, the damage charge and the tax rate. | The dates (checked against availability again), the customer, the contact, the Location, the billing cycle, whether each period's invoice is emailed, the notes, internal notes and custom fields. |
| Picked Up, Partially Returned, Returned | As Reserved, plus the customer, the Location, the start date and the billing cycle. | The expected return date (checked again; moving it later is extending the rental, which needs the Change a Rental Extension Request permission), the contact, whether each period's invoice is emailed, the notes, internal notes and custom fields. |
| Closed, Cancelled | Everything else. | The notes and internal notes. |
On the agreement's form a fixed field is greyed out and says why. On a line, its notes can still be changed from the API.
A damage charge recorded when equipment comes back (see Return below) is still added to the agreement while the equipment is out; it is the damage charge field on the form that is fixed.
Return to Draft takes a Reserved agreement back to Draft. The equipment it held is released, so another booking can take it, and the lines and charges can change again. Reserve it again when you are done: that checks availability again. A line that already has a rate keeps it, so if you change the customer or the Location and want their rate, change the line's rate too. Return to Draft is offered only while the agreement is Reserved.
Pickup re-checks availability at the moment of departure and checks out specific inventory units for every line — Knowledge tracks the life of each individual unit, so a Product line of quantity 3 allocates three real units and a kit checks out each of its component units. Every checkout is linked back to its line. The units become Checked Out in inventory, so they can't be checked out to anyone else until they come back. A Reserved agreement with a custom field that is Required to move past draft left blank is refused, with a message naming the field, until it is filled in.
Return requires a destination bin, and lets you choose which units are coming back (all of them by default). The bins offered are the ones at the location the units went out from — a bin at another location is refused, since the bin is what sets the unit's location and returning to one elsewhere would move the machine rather than put it away. Permission to view bins is not needed to be offered them; returning a hire is rental work.
Return everything and the agreement moves to Returned; return only some and it becomes Partially Returned, with the returned units back in stock and available again while the rest stay out. A Partially Returned agreement can be returned again later to bring in the remainder.
The same screen is where you record damage, while the equipment is in front of you: tick the units that came back damaged, enter what the damage costs, and say what happened. The charge is added to the agreement's damage charge (a second damaged unit on a later partial return adds to it rather than replacing it), the account of it is added to the agreement's Internal notes, which are not printed on the agreement, and each damaged unit's check-in movement is marked — so "which one broke?" is answerable from the unit's own history months later. Only a unit being returned right now can be marked damaged. A damaged unit is put in Needs repair rather than back In Stock, so the next rental is not handed a broken one; choose Back in service on the unit once it is fixed.
Marking a unit as lost #
When the customer cannot bring a unit back, use Mark as Lost on the agreement instead of Return. It is offered while the rental is Picked Up or Partially Returned, to anyone who may edit the agreement. Choose the unit, enter a Replacement charge (before tax) and say what happened.
- The unit is written off as Missing, the same way a stock count writes off a unit nobody can find: an adjustment movement out of stock, with the rental's number in its note. If it turns up later, use Mark as Found on the unit.
- The unit stops renting on the day you mark it lost, and no late fee accrues on it after that day.
- The agreement moves the way a return moves it: Partially Returned while other units are still out, Returned once none are. A lost unit is no longer shown as overdue or listed under On Rent, and it stops holding its place on the line.
- What happened is added to the agreement's Internal notes.
The replacement charge is settled at close, the same way as damage: each lost unit is one Replacement charge line on the invoice, for the whole charge, taxed at the rental's tax rate. The security deposit then goes against the damage and replacement charges, with their tax.
Mark as Lost is in the app only. The API and the MCP tools have no way to mark a unit lost.
Taking equipment back at another location.
Return it against a bin at its origin, then move it with the Transfer action on the unit itself. (Return is the action that takes a bin; Close & Invoice takes none — it is what raises the invoice afterwards.) Two steps rather than one on purpose: it records an inter-location movement, which is what actually happened, so the stock figures at both locations stay right.
Both steps together need a role at both ends — the origin, because the
Transfer action is only offered on a unit you may edit, and the destination,
because the same form asks for update_inventory_items at the location you are
moving it to. Both ends want update_inventory_items, not merely view. A clerk who covers only
one end gets stuck halfway, so in practice this is a job for somebody who covers
both locations, or for an administrator. (One exception, itself a bug: a reader who
can see every bin in the account can leave the Transfer form's location blank and
pick the far bin directly.)
A machine you cannot see cannot be checked in, and the app now says so. A clerk with a role only at the location where it was handed back cannot do step one: the unit belongs to a location they hold no role at, so it is invisible to their account and there is nothing for a return to write to. The return is refused, in these words:
Your role does not cover the location it went out from, so it cannot be checked in under your account. Ask a colleague with a role at that location to receive it, or ask an administrator to give you one.
The same sentence on the screen, from
POST /rental-agreements/{id}/returnas a 422, and from the MCP tool. Nothing is written — the hire stays as it was, the machine stays checked out where it is, and billing is untouched. If the agreement happens to name a location you hold no role at either, you will not be able to open it at all: Not Found on the page, 404 from the API. From the mobile app it is refused too, whichever way you come at it — its endpoints address the unit, and the unit is the thing you cannot see.So the answer is simply to hand the job to a colleague who covers the origin location. There is no half-finished state to worry about any more: this used to report success and quietly write the hire to Partially Returned over a machine that never moved — which left it unbookable, kept overdue reminders running, and invoiced the customer as though it had never come back. It does not do that now.
Close generates the Customer Invoice priced by the actual rental duration. Every unit has to be returned or marked lost first: while any unit is still out, Close is refused with a message saying how many. Each unit is billed per its own time out, to the day it came back or the day it was marked lost, so a partial return is reflected automatically. The day a rental was picked up, and the day a unit came back, is the day at the rental's location: in the location's time zone when it has one, otherwise your account's. A rental picked up at 7:30 pm is billed from that evening's date, and a unit returned at 7:30 pm stops accruing on that date, whatever the time is on the server. The same location's today decides when a rental is overdue, how many days its late fee counts, when a weekly or monthly cycle has run its course, and the date on the invoices and deposit refund it produces. (The same holds east of UTC: a rental picked up at 6 am in Sydney is billed from that morning's date, not the day before.) Close is available from Picked Up, Partially Returned, and Returned. Late fees (computed per unit on the days each was held past the due date), damage charges and replacement charges are added as separate invoice lines, each for the whole charge, and the invoice's tax is on all of them. The invoice starts in Draft status.
The security deposit goes against the damage and replacement charges after tax, and only against those: it never pays the rental charges. A $1,200 replacement at 8% tax is $1,296; with a $500 deposit, $796 of it is left to pay. If some of the deposit is left after those charges and their tax, a credit memo is issued automatically for what is left. Knowledge ERP does not record the deposit being collected; it treats it as collected before the equipment went out.
At close, the part of the deposit those charges and their tax need is held, and the agreement shows it as Deposit held. It is settled when the invoice leaves Draft: when you mark it as sent, email it, or set its status to sent over the API. Until then the Draft shows the whole amount due; the copy the customer is emailed already has the deposit taken off.
The deposit is settled against the invoice as it is when it leaves Draft: the damage and replacement lines that closing the rental added, as they then stand, with their tax at its rate then. A damage line you add to the Draft yourself is not one of them. It pays that amount, but never more than is held or than the invoice owes. When that amount is less than what is held (a charge lowered or its line deleted on the Draft, say, or the tax rate lowered), a credit memo refunds the rest of what was held. When it is more, the deposit pays what is held and the customer owes the difference. What it pays is recorded as a payment on the invoice. Once it is, Mark as Sent cannot be undone, as for any invoice that has taken a payment.
Setting a sent invoice back to Draft takes the payment back and holds that amount again, to be settled when the invoice is sent again. While the invoice has another payment on it, it cannot go back to Draft; reverse that payment first. Voiding the invoice takes the payment back and refunds everything still held as a credit memo; sending the invoice again later applies no deposit. The deposit's payment is not reversed on its own, and a Draft that holds a deposit cannot be deleted: void the invoice to return the deposit. Void is not offered while the invoice is paid in full.
The close confirmation spells the whole settlement out before you commit to it ("Rental $128.82 · damage $85.00 · invoicing $213.82 · $85.00 of the $200.00 deposit goes against the damage, leaving $128.82 to pay · refunding $115.00 of the deposit"), and the agreement's Charges panel repeats it afterwards with links to the invoice and, when there is one, the credit memo.
Picked Up and Reserved statuses count against availability. Reserving another rental or a loan for the same equipment over overlapping dates is refused when there is not enough of it left. A rental still out after its expected return date holds what has not come back until it is returned.
Availability checking #
Before committing an agreement, you can check availability from Rentals → Rental Calendar or via the API. The calendar shows what equipment is out and when, so you can spot gaps before creating agreements.
The rental agreements list #
The list (Rentals → Rental Agreements) shows each agreement's number, customer, status badge, start date, expected return, and total amount. Search by customer name or agreement number (status filtering is available via the API). Agreements are sorted newest-first. A rental agreement custom field with Show in List View on is a column here too.
Related records #
When you close a rental agreement, two records are created automatically:
- A Customer Invoice linked via
customer_invoice_id— see Customer Invoices for how to collect payment. - A Credit Memo linked via
deposit_credit_memo_id, when some of the deposit is left to refund after the damage and replacement charges and their tax, which can be applied to any outstanding invoice for the customer. Another credit memo refunds the deposit still held if the invoice leaves Draft needing less of it, or is voided.
Rental agreements also carry a full change history tab powered by the change tracker. Each lifecycle button is recorded in the change log with who pressed it. An entry that changed the agreement's status cannot be undone from History.
Doing it from the API #
# Create a rental agreement in Draft
curl -X POST "https://your-domain.com/api/v1/rental-agreements" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"customer_id": "...",
"start_date": "2026-07-01",
"end_date": "2026-07-07",
"deposit_amount": 25000,
"billing_cycle": "none"
}'
# Reserve it (confirms availability and snapshots rates)
curl -X POST "https://your-domain.com/api/v1/rental-agreements/{id}/reserve" \
-H "Authorization: Bearer $TOKEN"
# Release the reservation to change its lines or charges, then reserve it again
curl -X POST "https://your-domain.com/api/v1/rental-agreements/{id}/return-to-draft" \
-H "Authorization: Bearer $TOKEN"
# Return only some units (partial return). Omit checkout_ids to return everything.
curl -X POST "https://your-domain.com/api/v1/rental-agreements/{id}/return" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"bin_id": "<bin-id>", "checkout_ids": ["<checkout-id>"]}'
# Return everything and record damage on one of the units (cents).
curl -X POST "https://your-domain.com/api/v1/rental-agreements/{id}/return" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"bin_id": "<bin-id>", "damage_charge": 8500,
"damaged_checkout_ids": ["<checkout-id>"], "damage_notes": "Cracked chuck."}'
# Close it (generates the invoice and refunds the deposit)
curl -X POST "https://your-domain.com/api/v1/rental-agreements/{id}/close" \
-H "Authorization: Bearer $TOKEN"
A picked-up agreement lists the units still out under out_units (each with a
checkout_id) — pass a subset as checkout_ids on the return call to return only those.
The agreement reports partially_returned until the last unit is back, and a
rental.partially_returned webhook fires on that transition.
custom_fields is an object keyed by each field's key, returned on every agreement and
accepted when creating or updating one. Sending it on an update replaces the values
already stored, so send every value you want to keep. A string or a number sent as
custom_fields is rejected with a 422.
Money fields (
deposit_amount,damage_charge, andrate_amounton line items) are integers in cents over the API (25000= $250.00). Invalid lifecycle transitions (e.g. closing a Draft) return HTTP 422 with an error message. A change to something the agreement's status fixes (see What you can change at each stage) returns HTTP 409, with the reason.