Skip to content

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

https://{tenant-domain}/api/v1/

In local development:

http://acme.localhost:8000/api/v1/

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:

  1. Obtain a CSRF token from any GET request (set in the csrftoken cookie)
  2. POST /api/v1/auth/login with email, password, and X-CSRFToken header
  3. The server sets a sessionid httpOnly cookie
  4. Subsequent requests include both cookies automatically

CSRF Protection

All state-changing requests (POST, PUT, PATCH, DELETE) require a CSRF token:

X-CSRFToken: <value from csrftoken cookie>

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:

Content-Type: application/json

OpenAPI Documentation

Interactive API documentation is available at:

GET /api/v1/docs

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:

Content-Range: items 0-24/100

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:

{
  "detail": "Cannot approve loan LN-0001: status is 'active', expected 'pending'."
}

Not Found (404)

{
  "detail": "Loan not found."
}

Permission Denied (403)

{
  "detail": "You do not have permission to perform this action."
}

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:

POST /{resource}/{id}/{action}

Examples:

  • POST /loans/{id}/approve
  • POST /loans/{id}/disburse
  • POST /payments/{id}/reverse
  • POST /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:

POST /{resource}/{id}/archive
POST /{resource}/{id}/unarchive

External ID Lookup

Resources with external IDs support lookup via query parameter:

GET /{resource}?external_id=EXT-123

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