---
description: Django Ninja API conventions
globs: ["**/*.py"]
alwaysApply: false
---

# Django Ninja

- One `NinjaAPI()` in `config/api.py`; each app adds a `Router()` in `<app>/api.py`, registered with `api.add_router("/orders", router)`.
- Schemas in `<app>/schemas.py`: `Schema` for input, `ModelSchema` (with explicit `fields`) for output. Never `fields = "__all__"`.
- Every operation declares `response=` (and error responses: `response={200: OrderOut, 404: Error}`) so output is validated and documented.
- Auth on the router or API (`auth=django_auth`, `JWTAuth()`, or `APIKeyHeader` subclass). Mark public endpoints explicitly with `auth=None`.
- Scope queries to `request.auth` / `request.user`; use `get_object_or_404(Order, id=id, owner=request.user)`.
- Pagination with `@paginate` on list endpoints.
- Avoid N+1 with `select_related`/`prefetch_related` in the queryset you return.
- Async operations only call async ORM methods (`aget`, `afirst`, `[x async for x in qs]`); otherwise keep views sync.
- Raise `HttpError(status, message)` for expected errors; register exception handlers for domain errors.
- Business logic in `services.py`, not in the operation function.

## Done when
`pytest` (with Ninja's `TestClient(router)` or Django's client) passes, `makemigrations --check` is clean, and `/api/docs` renders.
