Documentation

FairMark Documentation

Human-in-the-loop AI pre-evaluation for Canvas, Moodle, and Blackboard via LTI 1.3.

Overview

FairMark is a FastAPI service that integrates with your LMS as an LTI 1.3 external tool. When an instructor launches FairMark from within a course, it fetches the student's submission, runs a two-stage GPT-4o evaluation, and presents the AI pre-evaluation for instructor review. The instructor can edit and approve the result, which is then posted back to the LMS gradebook via AGS.

Students never see the AI comment until the instructor approves it. The full evaluation chain — evidence pass, scoring pass, verifier output — is stored and exportable as a PDF audit report.

Quick start

  1. Copy .env.production.example to .env.production and fill in your keys
  2. Generate RSA keys: bash scripts/generate_keys.sh
  3. Start the stack: cd docker && docker compose -f docker-compose.prod.yml up -d
  4. Register your LMS: bash scripts/register_platform.sh canvas
  5. Configure FairMark as an external tool in your LMS (see LTI 1.3 section)

Environment variables

VariableRequiredDescription
OPENAI_API_KEYConditionalOpenAI API key (required unless OPENAI_BASE_URL set)
OPENAI_BASE_URLConditionalOpenAI-compatible endpoint (e.g. Ollama)
OPENAI_MODELDefault: gpt-4o
FAIRMARK_ADMIN_SECRETProtects /admin/* endpoints
FAIRMARK_BASE_URLPublic URL of this server
FAIRMARK_DB_PATHDefault: /data/fairmark.db
CANVAS_BASE_URLCanvas instance URL
CANVAS_TOKENCanvas API token
FAIRMARK_PRIVATE_KEYRSA PEM private key (for Deep Linking JWTs)
FAIRMARK_RSA_NRSA public key N component (for /lti/jwks)

LTI 1.3 auth flow

1. LMS → GET/POST /lti/login   (OIDC login init with nonce + redirect_uri)
2. FairMark → redirect to LMS auth_login_url
3. LMS → POST /lti/launch      (signed id_token JWT)
4. FairMark validates JWT signature against LMS JWKS
5. Instructor sees dashboard / Student sees feedback view

Register FairMark in your LMS using these URLs:

SettingValue
OIDC Login URL/lti/login
Launch URL/lti/launch
JWKS URL/lti/jwks
Redirect URI/lti/launch
Deep Link URL/lti/launch

Required LTI services: Names and Role Provisioning, Assignment and Grade Services, Deep Linking.

Canvas setup

  1. Admin → Developer Keys → + LTI Key → Method: Manual Entry
  2. Fill all URLs from the table above
  3. Enable scopes: lti-ags/scope/score and lti-nrps/scope/contextmembership.readonly
  4. Copy the Client ID, then run: CANVAS_CLIENT_ID=<id> bash scripts/register_platform.sh canvas

Moodle setup

  1. Site Admin → Plugins → External Tool → Manage Tools → Configure manually
  2. LTI Version: LTI 1.3, Tool URL: /lti/launch
  3. Copy the Client ID, then run: MOODLE_CLIENT_ID=<id> bash scripts/register_platform.sh moodle

Blackboard setup

  1. System Admin → LTI Tool Providers → Register LTI 1.3/Advantage Tool
  2. Note the Application ID provided by Blackboard
  3. Run: BB_CLIENT_ID=<id> BB_APP_ID=<app_id> bash scripts/register_platform.sh blackboard

System endpoints

MethodPathDescription
GET/healthSystem status, version, registered platforms
GET/api-docsInteractive Swagger UI
GET/lti/jwksTool public JWKS (LMS fetches once on registration)

Admin endpoints

All admin endpoints require the X-Admin-Secret header matching your FAIRMARK_ADMIN_SECRET env variable.
MethodPathDescription
GET/admin/platformsList all registered LMS platforms
POST/admin/platformsRegister a new LMS platform
DELETE/admin/platforms/{id}Deactivate a platform

Register platform body

{
  "issuer":         "https://canvas.instructure.com",
  "client_id":      "YOUR_CLIENT_ID",
  "auth_login_url": "https://canvas.instructure.com/api/lti/authorize_redirect",
  "auth_token_url": "https://canvas.instructure.com/login/oauth2/token",
  "key_set_url":    "https://canvas.instructure.com/api/lti/security/jwks",
  "platform_name":  "Canvas Production",
  "lms_type":       "canvas",
  "deployment_ids": []
}

Policy endpoints

MethodPathDescription
GET/admin/policy/{platform_id}List all policies for a platform
POST/admin/policy/{platform_id}Set university-level policy
POST/admin/policy/{platform_id}/course/{course_id}Set course-level policy
DELETE/admin/policy/{policy_id}Delete a policy

Assignment config endpoints

MethodPathDescription
POST/config/assignmentsCreate assignment config
GET/config/assignments/{id}Get assignment config
POST/config/assignments/{id}/versionsCreate config version
GET/config/assignments/{id}/materialsList material selections
POST/config/assignments/{id}/materials/selectSave material selections
POST/config/assignments/{id}/materials/syncSync Canvas materials to snapshots

Evaluation endpoints

MethodPathDescription
POST/evaluateGenerate AI pre-evaluation
POST/evaluate/finalizeInstructor approves → post grade to LMS
GET/evaluation/auditRetrieve persisted stage outputs + lineage
GET/evaluation/report.pdfExport evaluation PDF with audit appendix
POST/instructor/noteSave instructor note on an evaluation
POST/instructor/reevaluateRe-run AI evaluation for a submission
GET/instructor/rosterGet course roster with submission status

LTI endpoints

MethodPathDescription
GET POST/lti/loginOIDC login initiation (LMS calls this first)
POST/lti/launchJWT validation + UI render (LMS calls this second)
GET/lti/jwksTool public key set
POST/lti/deep-link-responseBuild signed Deep Linking response JWT

Docker deployment

cd docker
docker compose -f docker-compose.prod.yml up -d --build

# View logs
docker compose -f docker-compose.prod.yml logs -f fairmark

# Health check
curl http://localhost:8010/health

The container binds to 127.0.0.1:8010 and expects Caddy or Nginx to proxy HTTPS traffic from the outside.

RSA keys

# Generate key pair
bash scripts/generate_keys.sh

# Extract the N component for JWKS
python3 scripts/extract_jwk_n.py

# Copy FAIRMARK_PRIVATE_KEY and FAIRMARK_RSA_N into .env.production

Keys are stored in the fairmark_keys Docker volume and auto-generated on first startup if not provided.