API Overview¶
The LMS exposes a RESTful JSON API at /api/v1/ built with django-ninja-extra. All endpoints are async, return typed Pydantic schemas, and are documented via OpenAPI.
Base URL¶
In local development:
The tenant is resolved from the request domain via DomainRoutingMiddleware. All API responses are scoped to the resolved tenant's schema.
Authentication¶
All endpoints (except portal config and auth endpoints) require an authenticated session.
Session-Based Auth¶
The API uses session-based authentication with httpOnly cookies --- no Bearer tokens:
- Obtain a CSRF token from any
GETrequest (set in thecsrftokencookie) POST /api/v1/auth/loginwith email, password, andX-CSRFTokenheader- The server sets a
sessionidhttpOnly cookie - Subsequent requests include both cookies automatically
CSRF Protection¶
All state-changing requests (POST, PUT, PATCH, DELETE) require a CSRF token:
The CSRF token is set as a cookie on every response and must be sent back as a header.
Content Type¶
All requests and responses use application/json:
OpenAPI Documentation¶
Interactive API documentation is available at:
This provides a Swagger UI with all endpoints, schemas, and example payloads.
Pagination¶
List endpoints follow the react-admin data provider convention.
Query Parameters¶
| Parameter | Format | Example | Description |
|---|---|---|---|
sort |
["field","ASC\|DESC"] |
["created_at","DESC"] |
Sort field and direction |
range |
[start,end] |
[0,24] |
Zero-based item range |
filter |
{"field":"value"} |
{"status":"active"} |
Filter criteria |
Response Headers¶
List responses include a Content-Range header:
The response body is a JSON array of objects.
Example¶
curl 'http://acme.localhost:8000/api/v1/borrowers?sort=["last_name","ASC"]&range=[0,9]&filter={"status":"active"}' \
-H 'Cookie: sessionid=...; csrftoken=...'
Filtering¶
Filter parameters support several patterns:
| Pattern | Example | Description |
|---|---|---|
| Exact match | {"status": "active"} |
Field equals value |
| Multiple values | {"status": ["active", "pending"]} |
Field in list |
| Search | {"q": "john"} |
Full-text search (where supported) |
| Foreign key | {"borrower_id": "uuid"} |
Filter by related object |
| External ID | {"external_id": "EXT-123"} |
Look up by external ID |
Error Responses¶
Validation Errors (422)¶
Pydantic validation errors are converted to a react-admin compatible format:
{
"message": "Validation failed",
"errors": {
"email": "Enter a valid email address.",
"principal_amount": "Value must be greater than 0."
}
}
Business Errors (409)¶
Business rule violations return a 409 Conflict:
Not Found (404)¶
Permission Denied (403)¶
Rate Limiting¶
Authentication endpoints are rate-limited to prevent brute-force attacks:
| Endpoint | Rate Limit |
|---|---|
POST /auth/login |
10/min |
POST /auth/password/request |
5/min |
POST /auth/password/reset |
5/min |
Other endpoints use configurable throttle rates defined in NINJA_EXTRA settings.
Endpoint Conventions¶
CRUD Resources¶
Standard resource endpoints follow REST conventions:
| Method | Path | Description |
|---|---|---|
GET |
/{resource} |
List with pagination, sorting, filtering |
GET |
/{resource}/{id} |
Get single resource |
POST |
/{resource} |
Create resource |
PUT |
/{resource}/{id} |
Update resource |
DELETE |
/{resource}/{id} |
Delete or archive resource |
Business Actions¶
Non-CRUD operations use POST with an action path:
Examples:
POST /loans/{id}/approvePOST /loans/{id}/disbursePOST /payments/{id}/reversePOST /disbursements/{id}/process
Nested Resources¶
Child resources are accessed via parent paths:
GET /loans/{id}/payments # List loan's payments
POST /loans/{id}/payments # Create payment on loan
GET /borrowers/{id}/addresses # List borrower's addresses
POST /borrowers/{id}/addresses # Add address to borrower
Nested endpoints provide list + create only. Update, delete, and get-by-ID use the flat endpoint (e.g., PUT /payments/{id}).
Archive / Unarchive¶
Soft-delete uses archive endpoints instead of DELETE:
External ID Lookup¶
Resources with external IDs support lookup via query parameter:
Permissions¶
Endpoints are protected by role-based permission classes:
| Permission Class | Minimum Role |
|---|---|
IsViewerOrAbove |
viewer |
IsCollectorOrAbove |
collector |
IsLoanOfficerOrAbove |
loan_officer |
IsAdminOrAbove |
admin |
IsTenantAdmin |
superadmin |
Role hierarchy: viewer < collector < loan_officer < admin < superadmin
Individual endpoints may override the controller-level permission. Action endpoints typically require higher permissions than read operations.
See Also¶
- Authentication --- Login, logout, session management, password reset
- Architecture Overview --- System design and request lifecycle
- Layered Architecture --- How API controllers interact with services