Skip to content
Knowledge ERP Docs
API Reference ↗
Inventory

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 by name.
  • 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.
  • Notes — free-text notes visible on the agreement and in the change history.
  • 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_at timestamp.
  • Partially Returned — some units are back in stock while others are still out with the borrower. The loan stays in this status until the last unit is returned.
  • Returned — all units are back in a bin, their unit records show In Stock, and returned_at is stamped.
  • Cancelled — the loan was called off before check-out. Cancelling a Checked Out or Partially Returned agreement is not allowed — return the equipment first.

The agreement-level status summarizes the loan as a whole, but returns happen at the level of individual units. You can check part of a loan back in — the agreement becomes Partially Returned and the units you returned go back to stock — and finish the rest later. Each equipment line is stamped Returned only once its last outstanding unit is in.

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:

  1. Reserve — moves Draft → Reserved. The service checks every line item for availability over the loan's date window. If anything is unavailable (already out on another loan or rental for those dates) the transition fails with an error naming the conflicting unit. Reserving is optional; you can go straight to Check Out if you're handing equipment over immediately.

  2. Check Out — moves Draft or Reserved → Checked Out, 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.

  3. Return — moves Checked Out → Returned, or Checked Out → Partially Returned when only some units 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_at is stamped on the agreement once the final unit is back. A Partially Returned loan can be returned again later to bring in the rest.

While the loan is still in Draft or Reserved status you can also Cancel it. Cancelling a Reserved agreement releases its availability hold. Cancelling a Checked Out or Partially Returned agreement is not allowed — return the equipment first.

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.

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.

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's Reserved or Checked Out status blocks the equipment for the entire start-to-end-date window. The same availability engine serves both loans and rental agreements, so a unit reserved 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 identifying the specific unit that is unavailable.

Overdue loans #

An agreement is overdue when its status is Checked Out and today is past the end_date. 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 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_date out 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:

  • Equipment — all line items and their individual return timestamps.
  • Attachments — files linked to this loan (signed forms, photos, etc.).
  • Change history — a full audit trail of every field edit and status transition.

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>"]}'

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.