fAIr Backend

July 24, 2026 ยท View on GitHub

Thin coordination layer for the fAIr AI-Assisted Mapping platform. Owns the public REST API, the user database, and the orchestration of dataset builds, training runs and predictions. ML pipelines and STAC catalog operations live in fair-py-ops and run on a ZenML stack.

Model code lives in per-model docker images that the ZenML orchestrator pulls.

Quick start

The compose file at the repository root runs this backend and everything it depends on. See docs/Docker-installation.md.

Running the backend on the host

Useful when iterating on backend code. Start the dependencies in docker, then run Django outside it:

cd ..
docker compose up -d postgres minio stac mlflow zenml
cd backend
just setup                 
cp env_example .env      
just migrate
just run                   
just worker               tasks

The two sample files differ only in host names: the root env_example uses compose service names (postgres, minio, stac, zenml), while backend/env_example uses localhost with the ports those services publish.

OpenAPI schema at /api/schema/, Swagger UI at /api/docs/, ReDoc at /api/redoc/.

Environment

.env is read by pydantic-settings. Empty values are treated as unset and fall back to defaults. Required vars raise at process boot if missing. SecretStr values are masked in tracebacks. env_example mirrors the tables below.

Core Django

NameRequiredDefaultDescription
DEBUGnofalseDjango debug mode. In prod (false), SECRET_KEY must be at least 32 chars and not contain dev or unsafe.
SECRET_KEYyes(none)Django secret. Strict length/strength check when DEBUG=false.
DATABASE_URLyes(none)Postgres URL. Scheme must be postgres, postgresql, or postgis.
DATABASE_SSL_MODEnonullpsycopg sslmode. Set to require for hosted Postgres.
ALLOWED_HOSTSno[]Comma-separated list.
CSRF_TRUSTED_ORIGINSno[]Comma-separated list.
CORS_ALLOWED_ORIGINSno[]Comma-separated list.
SECURE_SSL_REDIRECTnotrueForce HTTPS redirect at the Django layer.
FRONTEND_URLyes(none)Public URL of the SPA. Used in emails and CORS.
API_BASE_URLyes(none)Public URL of this backend (e.g. http://localhost:8000/api/v1).
HOSTNAMEno127.0.0.1Used by the OpenAPI server URL.

Authentication

AUTH_PROVIDER selects the auth backend. Both share one contract: Authorization: Bearer <token>. hanko (production) validates a per-user JWT issued by Hanko (sent via Bearer header or hanko cookie). dev (local only) compares the Bearer token against the static FAIR_DEV_TOKEN; anyone with the token gets full dev-user access. Same header in dev and prod, only the issuer differs.

GET on datasets, local-models, and predictions is open to anonymous callers for rows with visibility="public". Owner-scoped lifecycle data (AOIs, trainings, feedback, notifications) and every write require Bearer auth.

NameRequiredDefaultDescription
AUTH_PROVIDERnohankoOne of hanko, dev.
FAIR_DEV_TOKENwhen AUTH_PROVIDER=devnullStatic dev token. Generate with openssl rand -hex 32. Never commit.
HANKO_API_URLwhen AUTH_PROVIDER=hankonullHanko backend URL.
COOKIE_SECRETwhen AUTH_PROVIDER=hankonullUsed to verify Hanko-signed cookies.
COOKIE_DOMAINnonullCookie scope domain.
COOKIE_SECUREnonullForce Secure cookie flag.
JWT_AUDIENCEnonullExpected aud claim.
LOGIN_URLnohttps://login.hotosm.orgHOT login portal URL.
LOGIN_INTERNAL_API_KEYnonullServer-to-server key against the login portal.
LOGIN_BACKEND_URLnonullLogin portal backend URL.
OSM_LOGIN_REDIRECT_URInonullRequired only when Hanko's "connect existing OSM account" flow is enabled.

fair-py-ops (ZenML + STAC)

FAIR_* values are read by this backend. The ZENML_STORE_* values are read by the zenml library itself when it opens a connection, so both sets point at the same server. Authenticate with either an API key or a username and password.

NameRequiredDefaultDescription
FAIR_ZENML_STORE_URLyes (at runtime)nullURL of the deployed ZenML server. Optional at boot, raises loud at first call site.
FAIR_STAC_API_URLyes (at runtime)nullURL of the STAC API root (eoapi-stac-fastapi). Trailing slashes are stripped.
FAIR_STAC_API_KEYprodnullBearer token for the STAC Transactions extension.
ZENML_STORE_URLyes (at runtime)nullSame server as FAIR_ZENML_STORE_URL.
ZENML_STORE_API_KEYone of the twonullService-account key. Mint with zenml service-account create fair-backend.
ZENML_STORE_USERNAMEone of the twonullUsername, paired with ZENML_STORE_PASSWORD. The compose stack's default user is default with an empty password.
ZENML_STORE_PASSWORDwith usernamenullPassword for the above.

Object storage (S3 / MinIO)

NameRequiredDefaultDescription
BUCKET_NAMEyes (at runtime)nullS3 / MinIO bucket.
PARENT_BUCKET_FOLDERnodevPer-environment prefix inside the bucket.
AWS_REGIONnous-east-1S3 region.
AWS_ACCESS_KEY_IDyes (at runtime)null
AWS_SECRET_ACCESS_KEYyes (at runtime)null
AWS_ENDPOINT_URLnonullSet for non-AWS S3 (MinIO, Cloudflare R2, etc.).
PRESIGNED_URL_EXPIRYno900Presigned-URL TTL in seconds.

Rate limits + database pool

DRF throttle scopes use <count>/<period>, e.g. 1000/h. Use the Django-native pool OR PgBouncer, not both.

NameRequiredDefaultDescription
USER_RATE_LIMITno1000/hAuthenticated requests.
ANON_RATE_LIMITno100/hAnonymous requests.
TRAINING_RATE_LIMITno10/hTraining submission throttle.
PREDICTION_RATE_LIMITno50/hPrediction submission throttle.
DB_POOL_MIN_SIZEno4Django 5.1+ native PostgreSQL connection pool minimum.
DB_POOL_MAX_SIZEno20Pool maximum.
DB_POOL_TIMEOUTno30Seconds to wait for a free connection.

OSM raw-data API

NameRequiredDefaultDescription
RAW_DATA_API_URLnohttps://api-prod.raw-data.hotosm.org/v1HOT Raw Data API root.

Mapswipe

Off by default. When ENABLE_MAPSWIPE=false, POST /api/v1/predictions/<id>/mapswipe/ returns 503. The /api/v1/health/ endpoint reports mapswipe.reachable when enabled.

NameRequiredDefaultDescription
ENABLE_MAPSWIPEnofalseMaster toggle.
MAPSWIPE_BACKEND_URLnonullMapswipe backend URL.
MAPSWIPE_MANAGER_URLnonullMapswipe manager URL.
MAPSWIPE_WEB_URLnonullMapswipe web app URL.
MAPSWIPE_CSRFTOKEN_KEYwhen ENABLE_MAPSWIPE=truenullCSRF cookie name on the Mapswipe backend.
MAPSWIPE_FB_AUTH_URLwhen ENABLE_MAPSWIPE=truenullFirebase auth endpoint.
MAPSWIPE_FB_USERNAMEwhen ENABLE_MAPSWIPE=truenullFirebase service-account username.
MAPSWIPE_FB_PASSWORDwhen ENABLE_MAPSWIPE=truenullFirebase service-account password.
MAPSWIPE_TUTORIAL_IDno37Mapswipe tutorial ID injected into pushed projects.
MAPSWIPE_ORGANIZATION_IDno4Mapswipe organization ID.
MAPSWIPE_VERIFICATION_NUMBERno3Number of crowd verifications required per tile.
MAPSWIPE_POLL_INTERVALno10Seconds between Mapswipe push-status polls.
MAPSWIPE_POLL_TIMEOUTno600Maximum total seconds to wait for a Mapswipe push.

Email

Only checked when DEBUG=false.

NameRequiredDefaultDescription
EMAIL_HOSTnosmtp.gmail.comSMTP host.
EMAIL_PORTno587SMTP port.
EMAIL_USE_TLSnotrueSTARTTLS.
EMAIL_USE_SSLnofalseImplicit TLS (mutually exclusive with EMAIL_USE_TLS).
EMAIL_HOST_USERno""SMTP username.
EMAIL_HOST_PASSWORDno""SMTP password.
DEFAULT_FROM_EMAILnono-reply@fair.hotosm.orgFrom: header for outbound mail.

Logging + pagination

NameRequiredDefaultDescription
LOG_PATHno./logsDirectory for rotating log files.
DEFAULT_PAGINATION_SIZEno50DRF page size.
SESSION_COOKIE_AGEno3600Session cookie TTL in seconds.
CACHE_TIMEOUT_MINUTESno5Default LocMem cache TTL for non-STAC entries.
LOG_LINE_STREAM_TRUNCATE_VALUEno10Per-step log-line truncation factor for streamed pipeline logs.

Operational tuning

All have safe defaults; only set to override.

NameRequiredDefaultDescription
HEALTH_PROBE_TIMEOUTno2.0Seconds each HTTP probe waits before marking a dependency unreachable in GET /api/v1/health/.
PREDICTION_SYNC_INTERVALno15Seconds between prediction-status poll re-enqueues.
TRAINING_SYNC_INTERVALno30Seconds between training-status poll re-enqueues.
STAC_CACHE_TTLno300Seconds the LocMem cache holds a STAC item before re-fetching. Raise in prod for lower STAC load; lower in dev to see property changes faster.
STAC_BULK_FETCH_WORKERSno16Thread pool size for parallel STAC item fetches when ?expand=stac is used on list endpoints.
PMTILES_MIN_ZOOMno10Min zoom passed to tippecanoe for PMTiles generation.
PMTILES_MAX_ZOOMno20Max zoom passed to tippecanoe for PMTiles generation.

Worker process

Background work uses Django's built-in django.tasks framework with the django-tasks-db backend (Postgres-backed, no Redis required).

just worker   # runs python manage.py db_worker

Tests, lint, types

just test                  # pytest
just lint                  # pre-commit run --all-files (ruff + format + ty + uv-lock-check + commitizen)

Tests under backend/tests/ import across apps and mock shared.integrations.zenml; nothing hits a live ZenML server. For end-to-end checks against a running stack, run test.py, which drives the public API end to end.