Interact with leihs equipment lending system via REST API. Use when managing equipment, items, models, categories, users, or inventory pools in a leihs instance. Triggers: leihs, inventory, equipment, lending, items, models, pools, Ausleihe.
Manage equipment inventory and lending via the leihs REST API. No browser needed.
https://leihs.example.org)curl installedexport LEIHS_URL="https://leihs.example.org"
export LEIHS_TOKEN="<api-token>"
If not set, ask the user before making any requests.
Leihs is a multi-service application. Each service has different auth:
| Service | Path | Token Auth |
|---|
| What It Manages |
|---|
| Admin | /admin/ | YES | Users, pools, groups, buildings, rooms, categories, suppliers |
| Inventory | /inventory/ | YES | Models, items, images, attachments, export (newer instances only) |
| Legacy | /manage/ | NO (session only) | Models, items, orders, contracts (older instances) |
| Borrow | /borrow/ | NO (GraphQL + session) | Reservations, orders, availability |
| Procure | /procure/ | NO (GraphQL + session) | Procurement requests |
Check what's available on your instance:
# These should return 200 if the service is running:
curl -s -o /dev/null -w "%{http_code}" "${LEIHS_URL}/admin/status"
curl -s -o /dev/null -w "%{http_code}" "${LEIHS_URL}/inventory/status"
If /inventory/status returns 503, the instance runs an older version — only the admin API is available for token-based automation.
Header format:
Authorization: Token <40-char-hex-token>
Scopes (set when token is created):
| Scope | Access |
|---|---|
scope_read | Read data (GET requests) |
scope_write | Create/update data (POST/PUT/DELETE) |
scope_admin_read | Read admin resources |
scope_admin_write | Manage admin resources |
Verify your token:
curl -s "${LEIHS_URL}/my/auth-info" \
-H "Authorization: Token ${LEIHS_TOKEN}" \
-H "Accept: application/json"
Returns your user info, scopes, and pool access rights.
Creating tokens: Tokens are managed at /admin/users/{user-id}/api-tokens/ or through the admin UI under user settings.
These will bite you if you don't know them:
/admin/inventory-pools/ NOT /admin/inventory_pools//admin/users/ (with slash) returns 200. /admin/users (without) returns 404./admin/inventory-pools/{id} (no slash) returns 200. With slash returns 544.?page=1&per-page=50. Page 0 causes "OFFSET must not be negative".All endpoints need Accept: application/json header. No CSRF token needed for admin mutations (unlike inventory API).
The admin API does NOT expose models or items — those live in the inventory or legacy service.
curl -s "${LEIHS_URL}/admin/inventory-pools/" \
-H "Authorization: Token ${LEIHS_TOKEN}" \
-H "Accept: application/json"
List responses are wrapped in a key matching the resource name:
{"inventory-pools": [...]}
{"users": [...]}
{"buildings": [...]}
{"user-api-tokens": [...]}
Extract with jq: | jq '.["inventory-pools"]'
Note: User/pool responses may include large base64-encoded avatar images (img32_url). Pipe through jq to extract specific fields if bandwidth matters.
| Method | Endpoint | Description |
|---|---|---|
| GET | /admin/inventory-pools/ | List all pools |
| GET | /admin/inventory-pools/{id} | Pool detail (no trailing slash!) |
| GET | /admin/inventory-pools/{id}/users/ | Pool users with roles |
| GET | /admin/inventory-pools/{id}/groups/ | Pool groups |
| GET | /admin/inventory-pools/{id}/delegations/ | Pool delegations |
| GET | /admin/users/?page=1&per-page=50 | List users (paginated) |
| GET | /admin/users/{id} | User detail |
| GET | /admin/users/{id}/api-tokens/ | User's API tokens |
| GET | /admin/groups/ | List groups |
| POST | /admin/groups/ | Create group ({"name": "..."}) |
| DELETE | /admin/groups/{id} | Delete group (returns 204) |
| GET | /admin/buildings/ | List buildings |
| GET | /admin/rooms/ | List rooms |
| GET | /admin/categories/ | Category tree (nested) |
| GET | /admin/suppliers/ | List suppliers |
| GET | /admin/inventory-fields/ | Field definitions |
| GET | /admin/mail-templates/ | Email templates |
| GET | /my/auth-info | Current user + scopes |
Admin mutations work with token auth and do not require CSRF tokens:
# Create a group
curl -s -X POST "${LEIHS_URL}/admin/groups/" \
-H "Authorization: Token ${LEIHS_TOKEN}" \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
-d '{"name": "New Group", "org_id": "example.org"}'
# Delete a group
curl -s -X DELETE "${LEIHS_URL}/admin/groups/${GROUP_ID}" \
-H "Authorization: Token ${LEIHS_TOKEN}"
Conventions for writes:
/admin/groups//admin/groups/{id}Content-Type: application/json for POST/PUTcurl -s "${LEIHS_URL}/admin/inventory-pools/${POOL_ID}/users/" \
-H "Authorization: Token ${LEIHS_TOKEN}" \
-H "Accept: application/json"
Available when /inventory/status returns 200. Has OpenAPI docs:
# Discover all endpoints
curl -s "${LEIHS_URL}/inventory/openapi.json" | jq '.paths | keys'
# Interactive docs (browser)
# ${LEIHS_URL}/inventory/api-docs/
All inventory endpoints are scoped to a pool: /inventory/{pool_id}/...
| Method | Endpoint | Description |
|---|---|---|
| GET | /inventory/{pool_id}/models/ | Search models |
| GET | /inventory/{pool_id}/models/{id} | Model detail |
| POST | /inventory/{pool_id}/models/ | Create model |
| GET | /inventory/{pool_id}/items/ | List items (rich filters) |
| GET | /inventory/{pool_id}/category-tree/ | Category hierarchy |
| GET | /inventory/{pool_id}/buildings/ | Buildings |
| GET | /inventory/{pool_id}/rooms/ | Rooms |
| GET | /inventory/{pool_id}/templates/ | Reservation templates |
| GET | /inventory/{pool_id}/suppliers/ | Suppliers |
| GET | /inventory/{pool_id}/entitlement-groups/ | Access groups |
| GET | /inventory/{pool_id}/export/csv | CSV export |
| GET | /inventory/{pool_id}/export/excel | Excel export |
CSRF for mutations: POST/PUT/DELETE may require x-csrf-token header:
CSRF=$(curl -s "${LEIHS_URL}/inventory/csrf-token/" | jq -r '.["csrf-token"]')
curl -X POST ... -H "x-csrf-token: ${CSRF}"
| Status | Meaning | Fix |
|---|---|---|
| 302 | Redirect to login | Token not accepted by this service (legacy/borrow) |
| 401 | Not authenticated | Check token value and expiry (default 90 days) |
| 403 | Not authorized | Token scopes insufficient |
| 404 | Not found | Check trailing slash convention |
| 503 | Service unavailable | Service not deployed on this instance |
| 544 | No handler | Route doesn't exist — check hyphens and path |
/manage/ (session auth, no token support). Use browser automation as fallback./borrow/graphql — requires session + CSRF, not token auth./procure/graphql — session only./admin/users/{id}/api-tokens/.search-term parameter on some admin endpoints may not filter results.If you encounter an endpoint that behaves differently, a missing operation, or unclear guidance:
Never silently work around a skill gap.