Loan Agreements
A loan agreement tracks a free equipment loan from creation through reserve, check-out, and return — with availability enforcement and a full audit trail, but no billing.
A loan agreement is the record that drives a free equipment loan. It names a borrower, a date window, and the specific equipment going out — then steps through a lifecycle (reserve → check out → return) that holds availability, triggers checkout records on the underlying units, and releases everything back to stock on return. No rates, no invoice: if you need billing, see Rental Agreements instead.
Loans live in the Inventory module. Any plan that includes Inventory can use them — the paid Rentals module is not required.
Creating a loan agreement #
Open Inventory → Loans → New Loan to create an agreement. The form asks for:
- Borrower — required. The person or organization receiving the equipment. Choose from Customer, Customer Contact, or User (staff). Use the type selector first, then search. A customer or contact is found by name, email address or phone number, and a staff user by name or email address (see Choosing a customer on a form).
- Location — the inventory location the equipment is coming from. Optional if you have only one location.
- Appointment — optionally link the loan to an appointment so both records stay connected.
- Start date — when the equipment leaves (or is reserved from). Required.
- Expected return — the due-back date. Required and must be on or after the start date. The system uses this to flag overdue agreements. Filled in for you when the location you are working at has a Default Loan / Checkout Period set — counted from today, and yours to change; blank when it has not.
- Notes (printed on the customer's copy) — free text that appears on the agreement PDF the borrower receives, and in the change history.
- Internal notes (staff only) — free text that is never printed, emailed or shown in the customer portal.
- Custom fields — any extra attributes configured on your account for loans appear at the bottom of the form.
The system assigns a sequential loan number (format LN-0001, LN-0002,
…) automatically on creation, which is used as the agreement's display title
throughout the UI and API.
Loan statuses #
A loan agreement is always in exactly one status, shown as a badge in the list and on the view page:
- Draft — the agreement is being assembled. Equipment can be added or edited freely; no availability hold is in place yet.
- Reserved — the equipment is held for the loan's date window. Any other loan or rental that tries to claim the same units over overlapping dates will be blocked.
- Checked Out — the units are physically with the borrower and their
individual unit records now show Checked Out. The agreement records the
exact
checked_out_attimestamp. - Partially Checked Out — some of the loan is with the borrower, at least one line has never gone out, and nothing has come back yet. That happens when equipment is added to a loan after it was handed over. Check Out is available again to hand the rest over. (Once something has come back the loan reads Partially Returned instead, whatever is still waiting.)
- Partially Returned — something has come back while the loan is not finished. Usually that means some units are back and others are still out; it also covers a loan whose every out unit is home while a line is still waiting to go out.
- Returned — all units are back in a bin, their unit records show In
Stock, and
returned_atis stamped. - Cancelled — the loan was called off. Cancelling is refused while any unit is still with the borrower — return the equipment first. Cancelling takes the loan's equipment lines with it.
Every line has a status of its own #
The agreement's badge is a summary. The answer to which of these has actually gone out is on the line, in the Equipment tab: each line carries a status of its own, read from the units that line handed out.
That matters as soon as a loan stops being one simple thing. Add a line to a loan that is already checked out and the new line reads Draft while the rest read Checked Out, and the loan itself reads Partially Checked Out — part of it is with the borrower, part of it has never left the shelf. Before this the agreement said Checked Out over a line that had never gone anywhere, and nothing on the screen distinguished them.
Partially Checked Out is an agreement's reading and never a line's. A line is all-or-nothing on the way out: checking a line out hands over everything it asked for or none of it.
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 or has come back. 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 with the borrower shows its Location, if it has one, and no bin — checking out 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 has no units to show yet, and says so. Reserving holds the line's quantity for the date window; the specific units are chosen when the equipment is checked out. So a reserved line reads No units yet — each unit is named when the equipment is checked out, and fills in the moment you press Check Out.
One more thing you may see, 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.
The agreement's status is worked out from its lines, not typed in. Reserve, Check Out, Return and Cancel are what move it, and they move it by moving equipment. Editing the equipment list re-reads the lines, and there are two things that reading is not allowed to conclude: that equipment has come back, and that a loan holding equipment has stopped holding it. So adding a line can take a loan from Checked Out to Partially Checked Out, and removing one can take it back — but no edit to the list finishes a loan.
Two consequences worth knowing:
- A partial return is decided by units, not lines. Check part of a loan back in and the agreement becomes Partially Returned; the lines that came back read Returned and the rest read Checked Out. A single line can be Partially Returned in its own right when some of its quantity is home.
- Returning the last out unit does not finish a loan that still has a line waiting. The loan reads Partially Returned rather than Returned — some of it has come home and it is not over, so no "loan returned" automation fires. It finishes when that line goes out and comes back. Checking it out again hands out only the waiting lines; the ones that already came back are left alone.
- Removing the waiting line does not finish it either. Taking a line off moves no equipment, so it cannot be what decides a loan is over — the same rule that stops a loan brought across from an older system being declared finished the moment somebody tidies its equipment list. Such a loan goes on reading Partially Returned, which means it keeps appearing under Overdue Returns once it is past its date. Cancel it to close it off; that leaves the lines that genuinely went out and came back exactly as they are.
Adding a line to a loan that has already finished does not re-open it: the loan goes on reading Returned. That is also the only day you can do it — see When a loan will not take more equipment.
Each line is also stamped with a Returned timestamp once its last outstanding unit is in. On a line brought across from an older system that timestamp is the date the old system recorded for the last of that line's units, not the date it was imported. A line whose units the old system never dated — including the many lines that record what was asked for and never which unit went out — is left blank rather than given a date. A later import fills a blank one and never replaces one already there, but it does clear the date on a line the old system now shows holding a unit again — that line stops reading Returned, and a return date on it would contradict its own badge.
A return you record here normally survives the next import. An import run settles what each brought-across loan's equipment did by reading the old system's own records, and the old system knows nothing about a return made in this app since the last run — customers usually go on working in the old system while their data is being brought across. The unit's check-in here is read alongside it, so a loan whose equipment this app holds the records for goes on reading Returned after you finish it here, and a loan an earlier import moved off Returned for this reason is put back on the next run.
One case it does not survive, worth knowing if you work in both systems at once. An import rewrites any record the old system has changed since the last run. So if somebody over there edits the handover you already closed here — extending its due date, say — that run rewrites it from the old system's view, in which the equipment is still out, and the return you recorded here goes with it. A later run does not bring it back. Until the move is finished, record a return in the old system as well as here, with its return date filled in: the loan then stays Returned through the next import, carrying the date the old system holds rather than the one this app recorded. Marked returned over there with no usable date, the import has no check-in to carry and the loan reads Checked Out again.
Equipment the old system says is out that has no checkout record here at all is different again: there is nothing here to check in, so it goes on holding its loan open. That is deliberate — the equipment is still in somebody's house.
Loans already in your account keep what they say. Existing lines were given a status when this shipped — read from their units where there were units to read, and otherwise copied from the agreement they belong to. Nothing on the agreements themselves was rewritten: not their status, not their check-out date and not their return date. A loan brought across from an older system therefore reads exactly as it did, including where that system knew about equipment whose records did not come across with it.
Two things follow from a loan being able to sit with equipment already returned against it and a line still waiting to go out:
- Checking it out again hands out only what is still waiting. The lines that already came back are left alone; nothing is re-issued to the borrower.
- Cancelling it leaves those lines alone too. They keep saying Returned, with their return date — cancelling gives up what was still promised, not what already happened.
The lifecycle: reserve, check out, return #
The three lifecycle actions are available as buttons on the loan's view page and as dedicated API endpoints:
-
Reserve — holds every line still reading Draft, one line at a time. A line for a product or for one named unit is checked against what is free over the loan's window, counting what this loan has already taken as well as what everybody else has; if it cannot be covered the whole press is refused, with an error naming the equipment it could not get, and the loan is left as it was. Reserving is optional; you can go straight to Check Out if you're handing equipment over immediately.
A kit line is not held this way. Kits are not counted against availability, so reserving one takes nothing off anybody else's numbers and nothing warns you if two loans reserve the same kit. A kit is tested when you check it out, which is where you will find out. This is a known gap rather than a design: product and named-unit lines are held properly.
Pressing Reserve is what holds the stock. A line you have just added holds nothing until you do — see Equipment you have added but not held. That is why the button comes back on a loan that is already reserved or checked out when you add something to it.
-
Check Out — hands over every line that has not gone out yet, re-checking availability at the moment of checkout. Each underlying inventory unit is checked out to the borrower automatically — including the specific units allocated for a Product or kit line — creating a checkout record tied to the loan number and the line it came from. Available on a Draft or Reserved loan, and on a Partially Checked Out or Partially Returned one that still has a line waiting. A Draft or Reserved loan 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 — moves a loan with units out towards Returned, or to Partially Returned when only some come back. The UI and API both require a destination bin, and the return form lets you pick which units are coming back (all of them by default). Each selected unit is checked in, its status returns to In Stock, and
returned_atis stamped on the agreement once the loan is genuinely over. A Partially Returned loan that still has units out can be returned again later to bring in the rest.
Cancel is offered while the loan is not finished and nothing is out with the borrower — so on a Draft or Reserved loan, and on a Partially Returned one whose equipment is all home. Cancelling releases what the loan was still holding and leaves the lines that already went out and came back exactly as they are. A loan that is already Returned or Cancelled is not cancelled again.
Equipment you have added but not held #
A line you have just added reads Draft, and a Draft line holds nothing: it takes no unit off anybody else's availability, and nobody has promised it to this borrower. The Equipment tab says so while one is there, and Reserve and Check Out are both on the page to act on it.
This is the deliberate change from how it used to work. A line added to a committed loan used to be marked Reserved the moment it was saved, so it came off availability with nobody having decided anything — and the badge could not tell a line somebody had reserved from one they had merely typed.
What a hold is has not changed: the line's quantity comes off the availability figure for the loan's window, and a line naming one particular unit takes that unit. What decides is now the line's own status rather than the agreement's, so a line that is Draft, Returned or Cancelled holds nothing while the rest of the loan carries on holding what it holds.
When a loan will not take more equipment #
The New button on the Equipment tab is greyed out, with the reason on it, when the loan is past taking more:
| The loan reads | Add equipment? |
|---|---|
| Draft, Reserved | yes |
| Checked Out, Partially Checked Out, Partially Returned | yes — the loan is live |
| Returned | only on the same calendar day the equipment came back |
| Cancelled | no |
"Same day" is read on the loan's own clock — its location's timezone, then the account's, then the system's — so a location closing at nine in the evening is still on the same working day.
A finished loan that does not record when the equipment came back will not take more either. Loans brought across from an older system are usually like this: the old system recorded that the loan was over and not the day it ended, so there is no way to tell whether that day was today. Start a new loan for equipment going out again.
The same rule applies to POST /api/v1/loan-agreement-items, which refuses with
the same sentence. It does not apply to the legacy importer, which writes
lines onto loans it has already settled as finished — those are the record of
what was lent rather than a new promise.
Equipment lines (loan agreement items) #
Each piece of equipment on a loan is a line item added via the Equipment tab on the loan's view page. A line item specifies:
- Equipment — what is going out. This is a polymorphic link that can point to a Product (lend any available unit of that Product), a Specific unit (one particular inventory unit identified by its lot number or barcode), or a Kit (an assembly whose component units all go out together).
- Quantity — how many units. Defaults to
1. - Notes — optional per-line notes.
- Status — where this line's equipment is. Read-only: it follows the units and the buttons, and neither the form nor the API accepts it. A new line starts at Draft; Reserve moves it to Reserved and holds its stock.
- Units — the individual units this line has handed over, once it has handed any over. See Which units a line handed over.
Because Knowledge tracks the life of every individual inventory unit, checking out always resolves to specific units: a Product line of quantity 2 allocates two real units, and a kit checks out each of its component units. Every checkout is linked back to its loan line, which is what makes per-unit (partial) returns possible. The Returned timestamp on each line is filled in once that line's last outstanding unit is checked back in — with the moment it happened here, or with the date the old system recorded for a line that came from one.
Loans are for Returnable equipment only — reserving or checking out a loan whose Product is marked Consumable is rejected. Consumable stock isn't loaned; it belongs on a purchase order or sold through Sales.
Availability and conflict prevention #
A loan holds equipment line by line, for the entire start-to-end-date window. Both halves count: the loan has to be Reserved, Checked Out, Partially Checked Out or Partially Returned, and so does the line — so a line still in Draft, or already Returned, or Cancelled, holds nothing while the rest of the loan carries on holding what it holds. A loan can therefore be in one of those four statuses and be holding nothing at all: a half-returned loan whose only remaining line came back, with a newly typed line beside it, is exactly that. The same availability engine serves both loans and rental agreements, so a unit held on a loan cannot be simultaneously booked on a rental and vice versa. Trying to reserve or check out when a conflict exists raises a validation error naming the equipment that is unavailable.
Overdue loans #
An agreement is overdue when it has equipment out — Checked Out or Partially
Checked Out — and today is past the end_date. The Overdue Returns page is
a little wider: it also lists Partially Returned loans past their date, on the
grounds that a loan half returned and past due is worth chasing too. The loan list flags overdue agreements so staff can follow up. The
end_date on an agreement can be extended by editing the record while it is
still in a non-returned state.
Due-date reminders & extensions #
Loans can remind borrowers before they're due and let customers extend them themselves through the customer portal:
- Reminders. Set a reminder lead time under Location → Loans & Checkouts and the borrower is emailed once before the due date — with the borrowed-equipment list and a magic-link button into the portal. The reminder fires only once per due date, and the marker resets if the loan is extended.
- Extensions. From the loan's portal detail page the customer can request a
due-date extension of 1, 2, 4, 8, or 16 weeks. By default the request waits in
an Extension Requests queue (under the Loans navigation, with a pending
badge) for staff to approve or decline; turn on auto-approve extensions
on the location to apply requests immediately. Approving moves the
end_dateout and emails the borrower; a customer can withdraw a pending request.
What lives on the loan view page #
Opening a loan shows its header section (loan number, status, borrower, location, dates, notes, custom fields) and several tabs. The borrower and the location are links through to their own records, as is each piece of equipment on the Equipment tab — where the link only appears if you have access to that section of the app, and a customer-contact borrower has no link because contacts are edited inside their customer.
- Equipment — all line items, each with its own status badge, return timestamp and the units it handed over, and a line saying so while any of them is not held yet.
- Attachments — files linked to this loan (signed forms, photos, etc.).
- Change history — a full audit trail of every field edit and status transition.
Finding a loan #
Inventory → Loans → Loan Agreements lists loan number, borrower, status, start date and expected return. Search by loan number or borrower name, and sort by loan number, status, start date or expected return. Borrower is searchable but not sortable: a borrower can be a customer, a contact or a staff user, and those names live in three different tables.
Filters:
- Status — one or more of the statuses above, plus Fully returned. The
chips read the equipment, not only the badge, so a mixed loan appears
under every chip it has equipment for: Draft finds a loan with a line
nobody has held, Reserved one with a line held and not yet handed over,
Checked Out one with a line still out, and Partially Returned one with a
line half back. Returned is the loose question — has anything come back —
and Fully returned the strict one: is every line home. Partially Checked
Out and Cancelled match the agreement's own status only. A loan with no
equipment lines on it always matches its own status, which is what the loans
brought across from an older system need. (The API's
statusfilter is the simpler one: it matches the agreement's own status exactly.) - Borrower — searchable, and it only offers people who actually have loans.
- Location — the location the loan is filed at. It starts empty, so loans with no location set are listed too until you narrow by location.
- Expected return — Due from / Due until, which is how you ask for "everything due back this week".
- Start date — Starting from / Starting until.
Loans → Overdue Returns is the same list already narrowed to active loans past their due date.
Doing it from the API #
# Create a loan agreement
curl -X POST "https://your-domain.com/api/v1/loan-agreements" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"borrower_type": "customer",
"borrower_id": "<customer-id>",
"start_date": "2026-07-01",
"end_date": "2026-07-07",
"notes": "Demo unit for site visit"
}'
# Add a Product line item
curl -X POST "https://your-domain.com/api/v1/loan-agreement-items" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"loan_agreement_id": "<loan-id>",
"loanable_type": "sku",
"loanable_id": "<product-id>",
"quantity": 2
}'
# Reserve, then check out
curl -X POST "https://your-domain.com/api/v1/loan-agreements/<loan-id>/reserve" \
-H "Authorization: Bearer $TOKEN"
curl -X POST "https://your-domain.com/api/v1/loan-agreements/<loan-id>/check-out" \
-H "Authorization: Bearer $TOKEN"
# Return everything to a bin
curl -X POST "https://your-domain.com/api/v1/loan-agreements/<loan-id>/return" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"bin_id": "<bin-id>"}'
A checked-out loan lists the units still out under out_units (each with a
checkout_id). Pass a subset of those ids as checkout_ids on the return call
to do a partial return — the loan reports partially_returned until the last
unit is back. Omit checkout_ids to return everything still out.
# Partial return — only the listed units come back
curl -X POST "https://your-domain.com/api/v1/loan-agreements/<loan-id>/return" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"bin_id": "<bin-id>", "checkout_ids": ["<checkout-id>"]}'
Each line on GET /api/v1/loan-agreement-items (and each entry under items on
an agreement) carries a status of its own, with the same values as the
agreement's. It is read-only — POST and PATCH accept quantity and notes,
and a status in the body is ignored.
Filter GET /api/v1/loan-agreements by status (e.g. checked_out,
partially_returned) or borrower_id to slice the paginated list. Loan agreement
Lines accept a loan_agreement_id filter on GET /api/v1/loan-agreement-items.
Loans carry no monetary values, so there are no cent-denominated fields on
either resource.