Base URL:
http://localhost:8000/api/Framework: Django 5.1 (Python) | Database: PostgreSQL via Supabase
This document covers every endpoint in the backend with example request/response payloads, explaining why the web dashboard (App) and browser extension (Extension) use each endpoint and how the data flows.
- Authentication Overview
- Organization Auth
- Employee Auth
- Employee Management
- Extension Endpoints
- Gamification
- Phishing Simulation
- Agent — Endpoint Security
- Appendix — Data Models & Risk Scoring
The API uses two token-based auth schemes:
| Scheme | Header Format | Who Uses It | Issued By |
|---|---|---|---|
| Org Token | Authorization: Token <64-char-hex> |
Web Dashboard (admin/manager) | POST /api/auth/register or POST /api/auth/login |
| Employee Token | Authorization: EmployeeToken <64-char-hex> |
Browser Extension, Employee-facing UI | POST /api/auth/employee/login |
- Tokens are random 64-character hex strings (
secrets.token_hex(32)). - No expiry — tokens remain valid until explicitly revoked via logout.
- Each login creates a new token (multi-session support).
These endpoints are consumed by the web dashboard for org-level account management.
Purpose: Create a new organization account and receive an auth token.
Consumer: Dashboard signup page.
Why: First-time onboarding — an admin creates their org, which provisions the entire workspace (employees, campaigns, agent data). The returned token is stored in the browser and used for all subsequent API calls.
Auth: None (public)
Request:
POST /api/auth/register
Content-Type: application/json
{
"email": "admin@acme-corp.com",
"name": "Acme Corporation",
"password": "Str0ngP@ss!"
}| Field | Type | Required | Constraints |
|---|---|---|---|
email |
string | Yes | Must be unique across all orgs, valid email format |
name |
string | Yes | Organization display name |
password |
string | Yes | Minimum 8 characters |
Response 201 Created:
{
"token": "a1b2c3d4e5f67890abcdef1234567890abcdef1234567890abcdef1234567890",
"organization": {
"id": "550e8400-e29b-41d4-a716-446655440000",
"name": "Acme Corporation",
"email": "admin@acme-corp.com"
}
}Error Responses:
| Status | Body | Cause |
|---|---|---|
| 400 | {"error": "Invalid JSON."} |
Malformed request body |
| 400 | {"error": "email, name, and password are required."} |
Missing required fields |
| 400 | {"error": "Password must be at least 8 characters."} |
Password too short |
| 409 | {"error": "An organization with this email already exists."} |
Duplicate email |
Purpose: Authenticate an existing organization and get a new auth token.
Consumer: Dashboard login page.
Why: Returning admin logs in to access their dashboard. Each login creates a fresh token (the previous one stays valid, enabling multi-device sessions).
Auth: None (public)
Request:
POST /api/auth/login
Content-Type: application/json
{
"email": "admin@acme-corp.com",
"password": "Str0ngP@ss!"
}Response 200 OK:
{
"token": "f7e8d9c0b1a23456789abcdef0123456789abcdef0123456789abcdef01234567",
"organization": {
"id": "550e8400-e29b-41d4-a716-446655440000",
"name": "Acme Corporation",
"email": "admin@acme-corp.com"
}
}Error Responses:
| Status | Body | Cause |
|---|---|---|
| 400 | {"error": "Invalid JSON."} |
Malformed body |
| 400 | {"error": "email and password are required."} |
Missing fields |
| 401 | {"error": "Invalid credentials."} |
Wrong email or password |
Purpose: Revoke the current org token (server-side session destruction).
Consumer: Dashboard "Sign out" button.
Why: Security best practice — ensures the token cannot be reused after logout.
Auth: Authorization: Token <key>
Request:
POST /api/auth/logout
Authorization: Token a1b2c3d4e5f67890...
No body required.
Response 200 OK:
{}Error Responses:
| Status | Body | Cause |
|---|---|---|
| 401 | {"error": "Authorization header missing or malformed."} |
Missing or wrong prefix |
| 401 | {"error": "Invalid or already revoked token."} |
Token not found in DB |
Purpose: Return the authenticated organization's profile.
Consumer: Dashboard on every page load / refresh.
Why: On page load, the App calls /auth/me to validate the stored token and hydrate the user's state (name, email, permissions). If it returns 401, the user is redirected to the login page.
Auth: Authorization: Token <key>
Request:
GET /api/auth/me
Authorization: Token a1b2c3d4e5f67890...
Response 200 OK:
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"email": "admin@acme-corp.com",
"name": "Acme Corporation",
"is_staff": false,
"is_superuser": false,
"created_at": "2025-03-15T10:30:00+00:00"
}Error Responses:
| Status | Body | Cause |
|---|---|---|
| 401 | {"error": "Authorization header missing or malformed."} |
No/bad header |
| 401 | {"error": "Invalid token."} |
Token doesn't exist |
These endpoints are consumed by the browser extension and any employee-facing UI. Employees log in with credentials created by the org admin.
Purpose: Authenticate an employee and issue an EmployeeToken.
Consumer: Browser extension login screen.
Why: When the extension is first installed, the employee enters their email + password. The returned token is stored in chrome.storage.local and used for all subsequent extension API calls. The employee's org context is automatically resolved from the employee record.
Auth: None (public)
Request:
POST /api/auth/employee/login
Content-Type: application/json
{
"email": "jane.doe@acme-corp.com",
"password": "Empl0yeeP@ss"
}Response 200 OK:
{
"token": "9a8b7c6d5e4f3210fedcba9876543210fedcba9876543210fedcba9876543210",
"employee": {
"id": "660e8400-e29b-41d4-a716-446655440001",
"name": "Jane Doe",
"email": "jane.doe@acme-corp.com",
"department": "Engineering",
"role": "Developer",
"seniority": "mid",
"organization": {
"id": "550e8400-e29b-41d4-a716-446655440000",
"name": "Acme Corporation"
}
}
}Error Responses:
| Status | Body | Cause |
|---|---|---|
| 400 | {"error": "Invalid JSON."} |
Malformed body |
| 400 | {"error": "email and password are required."} |
Missing fields |
| 401 | {"error": "Invalid credentials."} |
Wrong email/password or is_active=False |
Purpose: Revoke the current employee token.
Consumer: Extension sign-out action.
Why: When the employee signs out, the token is destroyed server-side and cleared from chrome.storage.local.
Auth: Authorization: EmployeeToken <key>
Request:
POST /api/auth/employee/logout
Authorization: EmployeeToken 9a8b7c6d5e4f3210...
No body required.
Response 200 OK:
{}Error Responses:
| Status | Body | Cause |
|---|---|---|
| 401 | {"error": "Authorization header missing or malformed."} |
Missing/wrong prefix |
| 401 | {"error": "Invalid or already revoked token."} |
Token not found |
Purpose: Return the authenticated employee's profile.
Consumer: Extension on startup.
Why: On extension startup, it calls /auth/employee/me to validate the saved token and display the employee's name, department, and org. If the call fails (401), the extension shows the login screen instead.
Auth: Authorization: EmployeeToken <key>
Request:
GET /api/auth/employee/me
Authorization: EmployeeToken 9a8b7c6d5e4f3210...
Response 200 OK:
{
"id": "660e8400-e29b-41d4-a716-446655440001",
"name": "Jane Doe",
"email": "jane.doe@acme-corp.com",
"department": "Engineering",
"role": "Developer",
"seniority": "mid",
"is_active": true,
"registered_at": "2025-03-20T14:00:00+00:00",
"organization": {
"id": "550e8400-e29b-41d4-a716-446655440000",
"name": "Acme Corporation"
}
}All endpoints require Org Token auth. Used by the web dashboard to manage the organization's employee roster.
Purpose: List all employees belonging to the authenticated organization.
Consumer: Dashboard "Employees" page + campaign target selection.
Why: Populates the employee table on the dashboard. Admins see every employee's department, role, seniority, and active status. This list is also used when selecting targets for phishing campaigns.
Auth: Authorization: Token <key>
Request:
GET /api/employees/
Authorization: Token a1b2c3d4...
Response 200 OK:
{
"employees": [
{
"id": "660e8400-e29b-41d4-a716-446655440001",
"name": "Jane Doe",
"email": "jane.doe@acme-corp.com",
"department": "Engineering",
"role": "Developer",
"seniority": "mid",
"is_active": true,
"registered_at": "2025-03-20T14:00:00+00:00"
},
{
"id": "770e8400-e29b-41d4-a716-446655440002",
"name": "Ahmed Benali",
"email": "ahmed.benali@acme-corp.com",
"department": "Finance",
"role": "Accountant",
"seniority": "senior",
"is_active": true,
"registered_at": "2025-03-21T09:15:00+00:00"
}
]
}Purpose: Create a new employee under the authenticated organization.
Consumer: Dashboard "Add Employee" form.
Why: Admin adds employees from the dashboard. Each employee gets login credentials for the browser extension.
Auth: Authorization: Token <key>
Request:
POST /api/employees/
Authorization: Token a1b2c3d4...
Content-Type: application/json
{
"name": "Karim Ouali",
"email": "karim.ouali@acme-corp.com",
"password": "Karim2025!",
"department": "Marketing",
"role": "Content Manager",
"seniority": "junior"
}| Field | Type | Required | Notes |
|---|---|---|---|
name |
string | Yes | Employee display name |
email |
string | Yes | Must be globally unique across all employees |
password |
string | Yes | Minimum 8 characters, hashed server-side |
department |
string | No | Default "" |
role |
string | No | Default "" |
seniority |
string | No | One of: junior, mid, senior, lead, manager, executive. Default mid |
Response 201 Created:
{
"id": "880e8400-e29b-41d4-a716-446655440003",
"name": "Karim Ouali",
"email": "karim.ouali@acme-corp.com"
}Error Responses:
| Status | Body | Cause |
|---|---|---|
| 400 | {"error": "name, email, and password are required."} |
Missing fields |
| 400 | {"error": "Password must be at least 8 characters."} |
Short password |
| 409 | {"error": "An employee with this email already exists."} |
Duplicate email |
Purpose: Update fields on an existing employee.
Consumer: Dashboard employee edit modal.
Why: Admin can change an employee's department, role, seniority, deactivate them, or reset their password.
Auth: Authorization: Token <key>
Request:
PATCH /api/employees/660e8400-e29b-41d4-a716-446655440001/
Authorization: Token a1b2c3d4...
Content-Type: application/json
{
"department": "Security",
"seniority": "senior",
"is_active": true
}| Updatable Field | Type | Notes |
|---|---|---|
name |
string | |
department |
string | |
role |
string | |
seniority |
string | junior/mid/senior/lead/manager/executive |
is_active |
boolean | Setting to false disables employee login |
password |
string | Min 8 chars, re-hashed server-side |
Response 200 OK:
{
"id": "660e8400-e29b-41d4-a716-446655440001",
"email": "jane.doe@acme-corp.com",
"name": "Jane Doe"
}Error Responses:
| Status | Body | Cause |
|---|---|---|
| 400 | {"error": "Password must be at least 8 characters."} |
Short password |
| 404 | {"error": "Employee not found."} |
Wrong ID or belongs to different org |
Purpose: Permanently delete an employee and all associated data.
Consumer: Dashboard employee delete action.
Why: Admin removes an ex-employee. Cascades through all related records: tokens, DLP logs, blacklist logs, simulation targets, training enrollments, quiz submissions, device snapshots.
Auth: Authorization: Token <key>
Request:
DELETE /api/employees/660e8400-e29b-41d4-a716-446655440001/
Authorization: Token a1b2c3d4...
Response 204 No Content:
{}Error Responses:
| Status | Body | Cause |
|---|---|---|
| 404 | {"error": "Employee not found."} |
Wrong ID or wrong org |
These endpoints are consumed by the browser extension running on employee workstations. They handle DLP event logging, URL blacklist enforcement, and admin event polling.
Purpose: Record a DLP (Data Loss Prevention) event when the extension detects a file upload.
Consumer: Browser extension content script.
Auth: None (uses employee_id in body)
Why & How:
- Extension's content script intercepts
<input type="file">events on every webpage - Extension shows a confirmation dialog: "You are uploading
report.xlsxtodrive.google.com. Allow / Cancel?" - Whatever the employee chooses (or if the extension force-blocks), the event is logged here
- The admin dashboard displays these logs in a DLP audit trail
Request:
POST /api/logs/dlp/
Content-Type: application/json
{
"employee_id": "660e8400-e29b-41d4-a716-446655440001",
"filename": "quarterly-report.xlsx",
"website": "https://drive.google.com/upload",
"action_taken": "allow"
}| Field | Type | Required | Values |
|---|---|---|---|
employee_id |
UUID string | Yes | Must match an existing Employee |
filename |
string | Yes | Name of the file being uploaded |
website |
URL string | Yes | URL where the upload was attempted |
action_taken |
enum string | Yes | allow (user approved), cancel (user cancelled), force (extension blocked) |
Response 200 OK:
{}Error Responses:
| Status | Body | Cause |
|---|---|---|
| 400 | {"error": "Missing required fields."} |
Any field null/empty |
| 400 | {"error": "action_taken must be allow, cancel, or force."} |
Invalid enum value |
| 404 | {"error": "Employee not found."} |
Bad employee_id |
Purpose: Record a blacklist violation — employee attempted to visit a blocked domain.
Consumer: Browser extension navigation listener.
Auth: None (uses employee_id in body)
Why & How:
- On startup, the extension fetches the blacklist from
/api/extension/blacklist/and caches it - On every
chrome.webNavigation.onBeforeNavigate, the URL's hostname is checked against the cached list - If the domain matches, the navigation is blocked, a warning page is shown, and this endpoint is called to log the attempt
- The admin sees these violations on the dashboard
Request:
POST /api/logs/blacklist/
Content-Type: application/json
{
"employee_id": "660e8400-e29b-41d4-a716-446655440001",
"attempted_url": "https://malware-test.local/payload"
}| Field | Type | Required |
|---|---|---|
employee_id |
UUID string | Yes |
attempted_url |
URL string | Yes |
Response 200 OK:
{}Error Responses:
| Status | Body | Cause |
|---|---|---|
| 400 | {"error": "Missing required fields."} |
Missing fields |
| 404 | {"error": "Employee not found."} |
Bad employee_id |
Purpose: Return the list of blacklisted domains for URL blocking.
Consumer: Browser extension on startup (and periodic refresh).
Auth: None (public)
Why: The extension needs to know which domains to block. Domains are configured server-side in settings.py under EXTENSION_BLACKLIST_DOMAINS, allowing centralized management.
Request:
GET /api/extension/blacklist/
Response 200 OK:
{
"domains": [
"malware-test.local",
"credential-harvest-test.local",
"eicar.org"
]
}Purpose: Poll for admin-pushed events (quizzes, alerts) targeting a specific employee.
Consumer: Browser extension (called every ~30 seconds).
Auth: None (uses emp_id query parameter)
Why & How:
- Admin creates an
AdminEvent(e.g., typeQUIZ) targeting a specific employee via Django admin or a future dashboard feature - Extension polls
GET /api/extension/poll/?emp_id=<uuid>every ~30 seconds - If an undelivered event exists, it's returned and immediately marked
is_delivered=True(won't be returned again) - Extension renders the event as a popup (e.g., a quiz with multiple-choice options)
- If no event is pending,
hasEvent: falseis returned
Request:
GET /api/extension/poll/?emp_id=660e8400-e29b-41d4-a716-446655440001
Response — No pending event 200 OK:
{
"hasEvent": false
}Response — Event available 200 OK:
{
"hasEvent": true,
"eventPayload": {
"type": "QUIZ",
"question": "Which of the following is a sign of a phishing email?",
"options": {
"a": "Email from your manager about a meeting",
"b": "Urgent request with a misspelled domain in the link",
"c": "Newsletter from a subscribed service",
"d": "Calendar invite from a known colleague"
}
}
}Error Responses:
| Status | Body | Cause |
|---|---|---|
| 400 | {"error": "emp_id is required."} |
Missing query parameter |
| 404 | {"error": "Employee not found."} |
Bad employee ID |
Security awareness quizzes managed by admins, answered by employees via the extension. Quizzes are pushed to employees via the poll mechanism (section 5.4) and answered inline.
Purpose: List all available quizzes, newest first. The correct_answer is never exposed in responses.
Consumer: Dashboard (admin review) and extension (quiz display).
Auth: None (public)
Why: The dashboard shows available quizzes for admin review. The extension can fetch this list to display available quizzes.
Request:
GET /api/gamification/quizzes/
Response 200 OK:
{
"quizzes": [
{
"id": "aa0e8400-e29b-41d4-a716-446655440010",
"question": "What should you do if you receive a suspicious email with an attachment?",
"options": {
"a": "Open the attachment to check if it's safe",
"b": "Forward it to your IT security team",
"c": "Reply asking who sent it",
"d": "Delete it and empty the trash"
},
"created_at": "2025-04-01T08:00:00+00:00"
},
{
"id": "bb0e8400-e29b-41d4-a716-446655440011",
"question": "Which URL is most likely a phishing attempt?",
"options": {
"a": "https://accounts.google.com/signin",
"b": "https://accounts-google.security-check.com/signin",
"c": "https://mail.google.com/inbox",
"d": "https://drive.google.com/file"
},
"created_at": "2025-04-02T10:30:00+00:00"
}
]
}Purpose: Get a single quiz by ID.
Consumer: Extension (when it receives a quiz event via polling and needs full quiz data).
Auth: None (public)
Request:
GET /api/gamification/quizzes/aa0e8400-e29b-41d4-a716-446655440010/
Response 200 OK:
{
"id": "aa0e8400-e29b-41d4-a716-446655440010",
"question": "What should you do if you receive a suspicious email with an attachment?",
"options": {
"a": "Open the attachment to check if it's safe",
"b": "Forward it to your IT security team",
"c": "Reply asking who sent it",
"d": "Delete it and empty the trash"
},
"created_at": "2025-04-01T08:00:00+00:00"
}Error Responses:
| Status | Body | Cause |
|---|---|---|
| 404 | {"error": "Quiz not found."} |
Invalid quiz_id |
Purpose: Submit an employee's answer to a quiz. Returns correctness and reveals the correct answer if wrong.
Consumer: Browser extension quiz popup.
Auth: None (uses employee_id in body)
Why & How:
- Employee receives a quiz via the poll mechanism (section 5.4) or the extension UI
- Employee selects an answer in the extension popup
- Extension POSTs the answer here
- Backend checks against
correct_answerstored in the Quiz model - Extension shows "Correct!" or "Wrong — the correct answer was B"
- Each employee can only submit once per quiz (enforced by unique constraint on
employee + quiz)
Request:
POST /api/gamification/submit-quiz/
Content-Type: application/json
{
"employee_id": "660e8400-e29b-41d4-a716-446655440001",
"quiz_id": "aa0e8400-e29b-41d4-a716-446655440010",
"answer_selected": "b"
}| Field | Type | Required |
|---|---|---|
employee_id |
UUID string | Yes |
quiz_id |
UUID string | Yes |
answer_selected |
string | Yes — must match one of the option keys (e.g., "a", "b", "c", "d") |
Response — Correct 200 OK:
{
"is_correct": true
}Response — Incorrect 200 OK:
{
"is_correct": false,
"correct_answer": "b"
}Error Responses:
| Status | Body | Cause |
|---|---|---|
| 400 | {"error": "Missing required fields."} |
Any field missing |
| 404 | {"error": "Employee not found."} |
Bad employee_id |
| 404 | {"error": "Quiz not found."} |
Bad quiz_id |
| 409 | {"error": "Quiz already submitted."} |
Duplicate submission |
The phishing module is the core of the platform. It lets admins create email templates (or AI-generate them), run simulation campaigns, track clicks, auto-enroll employees who fail into training, and view rich analytics. All endpoints require Org Token auth unless noted otherwise.
Enum reference for this section:
| Field | Valid Values |
|---|---|
attack_type |
IT_RESET, INVOICE, DELIVERY, HR_UPDATE |
language |
EN, FR, AR_MSA, AR_DARIJA |
difficulty |
1 (easy — obvious signals), 2 (medium — plausible but detectable), 3 (hard — spear-phishing) |
status (campaign) |
DRAFT, ACTIVE, COMPLETED |
Purpose: List all phishing email templates in the system.
Consumer: Dashboard "Templates" page.
Why: Admin browses available templates to pick one when creating a campaign. Templates are org-agnostic (shared across all orgs).
Auth: Authorization: Token <key>
Request:
GET /api/phishing/templates/
Authorization: Token a1b2c3d4...
Response 200 OK:
{
"templates": [
{
"id": "cc0e8400-e29b-41d4-a716-446655440020",
"attack_type": "IT_RESET",
"language": "EN",
"difficulty": 1,
"subject": "Urgent: Your password expires in 24 hours",
"sender_name": "IT Helpdesk",
"sender_domain": "it-support.acme-internal.com",
"created_at": "2025-04-05T12:00:00+00:00"
},
{
"id": "dd0e8400-e29b-41d4-a716-446655440021",
"attack_type": "INVOICE",
"language": "FR",
"difficulty": 2,
"subject": "Facture #INV-20250401 en attente de validation",
"sender_name": "Service Comptabilite",
"sender_domain": "finance.acme-internal.com",
"created_at": "2025-04-06T09:00:00+00:00"
}
]
}Purpose: Manually create a phishing email template.
Consumer: Dashboard "Create Template" form.
Why: Admin writes a custom template specifying exact subject, body, sender, language, difficulty, and attack type.
Auth: Authorization: Token <key>
Request:
POST /api/phishing/templates/
Authorization: Token a1b2c3d4...
Content-Type: application/json
{
"attack_type": "HR_UPDATE",
"language": "AR_DARIJA",
"difficulty": 2,
"subject": "تحديث مهم: سياسة الإجازات الجديدة",
"body": "<p>سلام {{employee_name}},</p><p>نعلمك بتغيير في سياسة الإجازات. اضغط هنا للتفاصيل: {{tracking_url}}</p>",
"sender_name": "قسم الموارد البشرية",
"sender_domain": "hr.acme-internal.com"
}| Field | Type | Required | Notes |
|---|---|---|---|
attack_type |
string | Yes | IT_RESET / INVOICE / DELIVERY / HR_UPDATE |
language |
string | Yes | EN / FR / AR_MSA / AR_DARIJA |
difficulty |
int | Yes | 1, 2, or 3 |
subject |
string | Yes | Email subject line |
body |
string | Yes | HTML or plain-text. Supports {{employee_name}} and {{tracking_url}} placeholders |
sender_name |
string | Yes | Display name in From header |
sender_domain |
string | Yes | Spoofed domain in From header |
Response 201 Created:
{
"id": "ee0e8400-e29b-41d4-a716-446655440022"
}Error Responses:
| Status | Body | Cause |
|---|---|---|
| 400 | {"error": "Required fields: attack_type, language, difficulty, subject, body, sender_name, sender_domain"} |
Missing fields |
| 400 | {"error": "attack_type must be one of {...}"} |
Invalid attack_type |
| 400 | {"error": "language must be one of {...}"} |
Invalid language |
| 400 | {"error": "difficulty must be 1, 2, or 3."} |
Invalid difficulty |
Purpose: AI-generate a culturally-localised phishing email template.
Consumer: Dashboard "Generate Template" button.
Why & How:
- Admin selects attack type + language (and optionally a target employee)
- If
OPENAI_API_KEYis set, the backend uses OpenAI to generate a realistic template; otherwise, a built-in culturally-localised template library is used - If
employee_idis provided anddifficultyis omitted, the backend callsrecommend_difficulty()— analyzing the employee's last 5 simulation results to auto-suggest difficulty - If
save=true, the generated template is persisted to the database for use in campaigns
Auth: Authorization: Token <key>
Request:
POST /api/phishing/templates/generate/
Authorization: Token a1b2c3d4...
Content-Type: application/json
{
"attack_type": "IT_RESET",
"language": "FR",
"difficulty": 2,
"employee_id": "660e8400-e29b-41d4-a716-446655440001",
"save": true
}| Field | Type | Required | Notes |
|---|---|---|---|
attack_type |
string | Yes | See enum table above |
language |
string | No | Default EN |
difficulty |
int | No | 1/2/3. Omit to auto-recommend based on employee history |
employee_id |
UUID string | No | For name personalisation (spear-phishing) and difficulty recommendation |
save |
boolean | No | Default false. If true, persists as a PhishingTemplate |
Response — save=true 201 Created:
{
"id": "ff0e8400-e29b-41d4-a716-446655440023",
"attack_type": "IT_RESET",
"language": "FR",
"difficulty": 2,
"subject": "Action requise : votre mot de passe expire dans 24h",
"body": "<p>Bonjour Jane,</p><p>Votre mot de passe expire demain. Cliquez ici pour le renouveler : {{tracking_url}}</p><p>Service Informatique</p>",
"sender_name": "Service Informatique",
"sender_domain": "it-support.acme-corp.com"
}Response — save=false 200 OK: Same shape but without "id".
Error Responses:
| Status | Body | Cause |
|---|---|---|
| 400 | {"error": "attack_type must be one of [...]"} |
Invalid attack_type |
| 400 | {"error": "language must be one of [...]"} |
Invalid language |
| 400 | {"error": "difficulty must be 1, 2, or 3."} |
Invalid difficulty |
| 404 | {"error": "Employee not found."} |
Bad employee_id |
| 500 | {"error": "Generation failed: ..."} |
AI/template generation error |
Purpose: List all phishing campaigns for this organization with summary stats.
Consumer: Dashboard "Campaigns" page.
Why: Admins see every campaign with its status (DRAFT / ACTIVE / COMPLETED), template info, target count, and click rate.
Auth: Authorization: Token <key>
Request:
GET /api/phishing/campaigns/
Authorization: Token a1b2c3d4...
Response 200 OK:
{
"campaigns": [
{
"id": "110e8400-e29b-41d4-a716-446655440030",
"name": "Q2 Phishing Awareness - IT Reset",
"status": "ACTIVE",
"template_id": "cc0e8400-e29b-41d4-a716-446655440020",
"template_subject": "Urgent: Your password expires in 24 hours",
"attack_type": "IT_RESET",
"language": "EN",
"difficulty": 1,
"created_at": "2025-04-10T08:00:00+00:00",
"launched_at": "2025-04-10T08:30:00+00:00",
"completed_at": null,
"total_targets": 25,
"clicked_count": 7,
"click_rate": 28.0
}
]
}Purpose: Create a new campaign in DRAFT status, linked to an existing template.
Consumer: Dashboard "Create Campaign" form.
Why: Admin selects a template, names the campaign, and creates it. It starts as DRAFT — no emails are sent yet. The admin launches it separately (7.4).
Auth: Authorization: Token <key>
Request:
POST /api/phishing/campaigns/
Authorization: Token a1b2c3d4...
Content-Type: application/json
{
"name": "Q2 Phishing Awareness - IT Reset",
"template_id": "cc0e8400-e29b-41d4-a716-446655440020"
}| Field | Type | Required |
|---|---|---|
name |
string | Yes |
template_id |
UUID string | Yes — must reference an existing PhishingTemplate |
Response 201 Created:
{
"id": "110e8400-e29b-41d4-a716-446655440030"
}Error Responses:
| Status | Body | Cause |
|---|---|---|
| 400 | {"error": "name and template_id are required."} |
Missing fields |
| 404 | {"error": "Template not found."} |
Bad template_id |
Purpose: Launch a DRAFT campaign — create simulation targets and send phishing emails.
Consumer: Dashboard "Launch" button on campaign detail page.
Why & How:
- Admin clicks "Launch" on a DRAFT campaign
- If
employee_idsis provided, only those employees are targeted; if omitted, all active employees in the org are targeted - A
PhishingSimulationTargetrecord is created for each employee with a uniquetracking_token(UUID) - Campaign transitions from
DRAFT→ACTIVE - Phishing emails are sent via SMTP (Django email backend). Each email has
{{employee_name}}replaced with the real name and{{tracking_url}}replaced with{PHISHING_BASE_URL}/api/phishing/click/{tracking_token}/
Auth: Authorization: Token <key>
Request:
POST /api/phishing/campaigns/110e8400-e29b-41d4-a716-446655440030/launch/
Authorization: Token a1b2c3d4...
Content-Type: application/json
{
"employee_ids": [
"660e8400-e29b-41d4-a716-446655440001",
"770e8400-e29b-41d4-a716-446655440002"
]
}| Field | Type | Required | Notes |
|---|---|---|---|
employee_ids |
array of UUID strings | No | Omit to target all active employees in the org |
Response 200 OK:
{
"targets_created": 2,
"status": "ACTIVE",
"emails_sent": 2,
"emails_failed": 0,
"email_errors": []
}Error Responses:
| Status | Body | Cause |
|---|---|---|
| 404 | {"error": "Campaign not found."} |
Bad campaign_id or wrong org |
| 409 | {"error": "Only DRAFT campaigns can be launched."} |
Campaign already launched |
| 400 | {"error": "No eligible employees found."} |
No active employees match |
Purpose: Mark an ACTIVE campaign as COMPLETED.
Consumer: Dashboard "Complete Campaign" button.
Why: Admin manually closes a campaign when they've gathered enough data. Sets completed_at timestamp.
Auth: Authorization: Token <key>
Request:
POST /api/phishing/campaigns/110e8400-e29b-41d4-a716-446655440030/complete/
Authorization: Token a1b2c3d4...
No body required.
Response 200 OK:
{
"status": "COMPLETED"
}Error Responses:
| Status | Body | Cause |
|---|---|---|
| 404 | {"error": "Campaign not found."} |
Bad ID or wrong org |
| 409 | {"error": "Only ACTIVE campaigns can be completed."} |
Not in ACTIVE status |
Purpose: List all simulation targets for a campaign with per-employee details.
Consumer: Dashboard campaign detail page.
Why: Shows a table of every targeted employee, whether the email was sent, and whether they clicked. Drives the "per-employee results" view.
Auth: Authorization: Token <key>
Request:
GET /api/phishing/campaigns/110e8400-e29b-41d4-a716-446655440030/targets/
Authorization: Token a1b2c3d4...
Response 200 OK:
{
"targets": [
{
"id": "220e8400-e29b-41d4-a716-446655440040",
"employee_id": "660e8400-e29b-41d4-a716-446655440001",
"employee_email": "jane.doe@acme-corp.com",
"employee_name": "Jane Doe",
"department": "Engineering",
"role": "Developer",
"seniority": "mid",
"tracking_token": "aabbccdd-1122-3344-5566-778899aabb00",
"sent_at": "2025-04-10T08:35:00+00:00",
"clicked_at": "2025-04-10T14:22:00+00:00"
},
{
"id": "330e8400-e29b-41d4-a716-446655440041",
"employee_id": "770e8400-e29b-41d4-a716-446655440002",
"employee_email": "ahmed.benali@acme-corp.com",
"employee_name": "Ahmed Benali",
"department": "Finance",
"role": "Accountant",
"seniority": "senior",
"tracking_token": "eeff0011-2233-4455-6677-8899aabbccdd",
"sent_at": "2025-04-10T08:35:01+00:00",
"clicked_at": null
}
]
}Purpose: Click-tracking endpoint embedded in simulated phishing emails.
Consumer: Employee's browser (when they click the link in the phishing email).
Auth: None (public — the <token> UUID is the opaque tracking identifier)
Why & How:
- Each simulated phishing email contains a link like
https://yourdomain.com/api/phishing/click/aabbccdd-1122-3344-5566-778899aabb00/ - When the employee clicks it, this endpoint:
- Looks up the
PhishingSimulationTargetbytracking_token - Records
clicked_attimestamp (idempotent — only set on first click) - Auto-enrolls the employee in the matching
TrainingModule(matched byattack_type+language; falls back to English if no localised module exists)
- Looks up the
- Returns an HTML awareness page explaining this was a simulation test, with tips on spotting phishing
Request:
GET /api/phishing/click/aabbccdd-1122-3344-5566-778899aabb00/
Response 200 OK (text/html):
<!DOCTYPE html>
<html>
<head><title>Security Awareness</title></head>
<body>
<h1>This was a simulated phishing test.</h1>
<p>Don't worry — no harm was done.</p>
<p>Signals you could have spotted:</p>
<ul>
<li>The sender's email domain did not match the official organisation domain.</li>
<li>The message created artificial urgency.</li>
<li>The link URL did not point to a known, trusted domain.</li>
</ul>
<p>A short 5-minute training module has been assigned to your profile.</p>
</body>
</html>Error: 404 Not found. (plain text) if the tracking token is invalid.
Purpose: Manually mark a simulation target as "email sent" and get its tracking token.
Consumer: Dashboard (backup mechanism).
Why: Primary send happens automatically during campaign launch (7.4). This endpoint is a fallback for cases where emails were dispatched via an external system or needed retry.
Auth: Authorization: Token <key>
Request:
POST /api/phishing/targets/220e8400-e29b-41d4-a716-446655440040/sent/
Authorization: Token a1b2c3d4...
Response 200 OK:
{
"id": "220e8400-e29b-41d4-a716-446655440040",
"tracking_token": "aabbccdd-1122-3344-5566-778899aabb00",
"sent_at": "2025-04-10T08:35:00+00:00"
}Purpose: Organization-wide phishing simulation analytics summary.
Consumer: Dashboard main analytics page.
Why: Provides the big-picture view: total campaigns, total emails sent, total clicks, overall click rate. Also breaks down stats by department, role, and seniority to identify the most vulnerable groups.
Auth: Authorization: Token <key>
Request:
GET /api/phishing/analytics/
Authorization: Token a1b2c3d4...
Response 200 OK:
{
"total_campaigns": 3,
"total_sent": 75,
"total_clicked": 18,
"overall_click_rate": 24.0,
"campaigns": [
{
"id": "110e8400-e29b-41d4-a716-446655440030",
"name": "Q2 Phishing Awareness - IT Reset",
"status": "ACTIVE",
"attack_type": "IT_RESET",
"difficulty": 1,
"sent": 25,
"clicked": 7,
"click_rate": 28.0,
"launched_at": "2025-04-10T08:30:00+00:00"
}
],
"departments": {
"by_department": {
"Engineering": { "sent": 30, "clicked": 5, "click_rate": 16.7 },
"Finance": { "sent": 20, "clicked": 8, "click_rate": 40.0 },
"Marketing": { "sent": 25, "clicked": 5, "click_rate": 20.0 }
},
"by_role": {
"Developer": { "sent": 20, "clicked": 3, "click_rate": 15.0 },
"Accountant": { "sent": 15, "clicked": 6, "click_rate": 40.0 }
},
"by_seniority": {
"junior": { "sent": 20, "clicked": 8, "click_rate": 40.0 },
"mid": { "sent": 30, "clicked": 7, "click_rate": 23.3 },
"senior": { "sent": 25, "clicked": 3, "click_rate": 12.0 }
}
}
}Purpose: Monthly time-series of click rate and training completions.
Consumer: Dashboard trend chart.
Why: Shows whether the organization is improving over time — decreasing click rates and increasing training completions indicate the program is working.
Auth: Authorization: Token <key>
Request:
GET /api/phishing/analytics/trend/
Authorization: Token a1b2c3d4...
Response 200 OK:
{
"trend": [
{
"month": "2025-01",
"sent": 50,
"clicked": 20,
"click_rate": 40.0,
"trainings_completed": 5
},
{
"month": "2025-02",
"sent": 60,
"clicked": 15,
"click_rate": 25.0,
"trainings_completed": 12
},
{
"month": "2025-03",
"sent": 55,
"clicked": 8,
"click_rate": 14.5,
"trainings_completed": 18
}
]
}Purpose: Department-level risk alerts with AI-generated recommendations for managers.
Consumer: Dashboard "Manager Alerts" section.
Why: Shows each department's phishing risk level, the most-clicked attack type, training completion rate, and a natural-language recommendation. Designed for managers to take action on their team's security posture.
Risk level thresholds:
- HIGH: click rate >= 50%
- MEDIUM: click rate >= 25%
- LOW: click rate < 25%
Auth: Authorization: Token <key>
Request:
GET /api/phishing/alerts/managers/
Authorization: Token a1b2c3d4...
Response 200 OK:
{
"department_alerts": [
{
"department": "Finance",
"simulations_sent": 20,
"click_rate": 55.0,
"risk_level": "HIGH",
"top_attack_type": "INVOICE",
"training_enrolled": 8,
"training_completion_rate": 25.0,
"recommendation": "Your team shows a high susceptibility to invoice approval fraud. Consider scheduling a live awareness session and ensuring all outstanding training modules are completed this week. Note: only 25.0% of enrolled training has been completed — following up on completion will significantly reduce future risk."
},
{
"department": "Engineering",
"simulations_sent": 30,
"click_rate": 16.7,
"risk_level": "LOW",
"top_attack_type": "IT_RESET",
"training_enrolled": 3,
"training_completion_rate": 100.0,
"recommendation": "Your team is performing well. Continue reinforcing good habits with periodic refresher simulations."
}
]
}Purpose: List all training modules available in the system.
Consumer: Dashboard "Training" tab.
Why: Admin views what training modules exist. Modules are auto-assigned to employees who click phishing links, but the admin can review them here. One module per (attack_type, language) pair.
Auth: Authorization: Token <key>
Request:
GET /api/phishing/training/
Authorization: Token a1b2c3d4...
Response 200 OK:
{
"modules": [
{
"id": "440e8400-e29b-41d4-a716-446655440050",
"attack_type": "IT_RESET",
"language": "EN",
"title": "Recognizing IT Password Reset Phishing",
"duration_minutes": 5,
"created_at": "2025-03-01T00:00:00+00:00"
},
{
"id": "550e8400-e29b-41d4-a716-446655440051",
"attack_type": "INVOICE",
"language": "FR",
"title": "Reconnaitre les faux emails de facturation",
"duration_minutes": 5,
"created_at": "2025-03-01T00:00:00+00:00"
}
]
}Purpose: List all training enrollments for employees in this organization.
Consumer: Dashboard "Training Enrollments" tab.
Why: Shows which employees have been enrolled in training (usually auto-enrolled after clicking a phishing link) and whether they've completed it. Drives follow-up actions and completion tracking.
Auth: Authorization: Token <key>
Request:
GET /api/phishing/training/enrollments/
Authorization: Token a1b2c3d4...
Response 200 OK:
{
"enrollments": [
{
"id": "660e8400-e29b-41d4-a716-446655440060",
"employee_id": "660e8400-e29b-41d4-a716-446655440001",
"employee_email": "jane.doe@acme-corp.com",
"employee_name": "Jane Doe",
"department": "Engineering",
"module_id": "440e8400-e29b-41d4-a716-446655440050",
"module_title": "Recognizing IT Password Reset Phishing",
"attack_type": "IT_RESET",
"enrolled_at": "2025-04-10T14:22:01+00:00",
"completed_at": null
},
{
"id": "770e8400-e29b-41d4-a716-446655440061",
"employee_id": "770e8400-e29b-41d4-a716-446655440002",
"employee_email": "ahmed.benali@acme-corp.com",
"employee_name": "Ahmed Benali",
"department": "Finance",
"module_id": "550e8400-e29b-41d4-a716-446655440051",
"module_title": "Reconnaitre les faux emails de facturation",
"attack_type": "INVOICE",
"enrolled_at": "2025-04-11T09:00:00+00:00",
"completed_at": "2025-04-12T11:30:00+00:00"
}
]
}Purpose: Mark a training enrollment as completed.
Consumer: Dashboard training management.
Why: After an employee completes the training module, the admin marks it complete. This updates the training completion rate in analytics and manager alerts.
Auth: Authorization: Token <key>
Request:
POST /api/phishing/training/enrollments/660e8400-e29b-41d4-a716-446655440060/complete/
Authorization: Token a1b2c3d4...
No body required.
Response 200 OK:
{
"completed_at": "2025-04-15T16:00:00+00:00"
}Error Responses:
| Status | Body | Cause |
|---|---|---|
| 404 | {"error": "Enrollment not found."} |
Bad ID or wrong org |
| 409 | {"error": "Already completed."} |
Already has completed_at set |
Purpose: Get the recommended phishing difficulty level for a specific employee.
Consumer: Dashboard campaign creation / template generation.
Why: Enables adaptive difficulty. Employees who consistently fail get easier tests; those who pass get harder ones.
Algorithm: Examines the employee's last 5 simulation results:
- Fewer than 2 tests → difficulty 1 (easy — need baseline data)
- Click rate >= 60% → difficulty 1 (needs basic training first)
- Click rate >= 30% → difficulty 2 (moderate challenge)
- Click rate < 30% → difficulty 3 (ready for hard spear-phishing)
Auth: Authorization: Token <key>
Request:
GET /api/phishing/employees/660e8400-e29b-41d4-a716-446655440001/difficulty/
Authorization: Token a1b2c3d4...
Response 200 OK:
{
"employee_id": "660e8400-e29b-41d4-a716-446655440001",
"employee_email": "jane.doe@acme-corp.com",
"recommended_difficulty": 2,
"history_window": 5,
"failed_count": 2,
"click_rate": 40.0
}Response — No history 200 OK:
{
"employee_id": "660e8400-e29b-41d4-a716-446655440001",
"employee_email": "jane.doe@acme-corp.com",
"recommended_difficulty": 1,
"history_window": 0,
"failed_count": 0,
"click_rate": null
}Error Responses:
| Status | Body | Cause |
|---|---|---|
| 404 | {"error": "Employee not found."} |
Bad ID or wrong org |
The agent module handles device health monitoring. A lightweight desktop agent runs on each employee's workstation, collects system telemetry (OS, software, processes, network, security posture), and ships it to the backend. The backend computes a risk score and provides audit/drift analysis.
All endpoints require Org Token auth.
Purpose: Ingest a device telemetry snapshot from the desktop agent.
Consumer: Desktop agent (runs on employee workstation).
Why & How:
- The agent periodically collects a comprehensive snapshot of the device state
- It POSTs the full payload to this endpoint
- The backend parses indexed fields (hostname, OS, hardware), then runs the risk scoring engine (100-point scale, penalty-based)
- The full raw payload + computed risk are stored for historical analysis and drift detection
Auth: Authorization: Token <key>
Request:
POST /api/agent/snapshot/?employee_id=660e8400-e29b-41d4-a716-446655440001
Authorization: Token a1b2c3d4...
Content-Type: application/json
{
"collectedAtUtc": "2025-04-15T12:00:00Z",
"device": {
"hostname": "DESKTOP-JD01",
"osName": "Windows",
"osVersion": "11 Pro 23H2",
"architecture": "x64",
"uptimeSeconds": 86400,
"hardware": {
"cpuCores": 8,
"totalMemoryMb": 16384
}
},
"user": {
"isAdminEstimate": false,
"localAdmins": ["Administrator", "jane.doe"]
},
"security": {
"antivirus": [
{ "name": "Windows Defender", "enabled": true, "upToDate": true }
],
"firewallStatus": "enabled",
"diskEncryptionEnabled": true,
"osUpdatesCurrent": true
},
"network": {
"listeningPorts": [
{ "port": 80, "process": "nginx" },
{ "port": 443, "process": "nginx" }
]
},
"processes": [
{ "name": "nginx.exe", "pid": 1234, "executablePath": "C:\\Program Files\\nginx\\nginx.exe" },
{ "name": "code.exe", "pid": 5678, "executablePath": "C:\\Program Files\\VS Code\\code.exe" }
],
"software": {
"software": [
{ "name": "Visual Studio Code", "version": "1.89.0" },
{ "name": "nginx", "version": "1.25.4" },
{ "name": "SuspiciousTool", "version": "1.0", "riskFlag": true }
]
}
}| Field | Type | Required | Notes |
|---|---|---|---|
employee_id |
UUID | Yes | Query param OR in body |
collectedAtUtc |
ISO 8601 string | Yes | When the agent collected data |
device |
object | Yes | hostname, osName, osVersion, architecture, uptimeSeconds, hardware |
security |
object | Yes | antivirus array, firewallStatus, diskEncryptionEnabled, osUpdatesCurrent |
network |
object | No | listeningPorts array |
processes |
array | No | Running processes with name, pid, executablePath |
software |
object | No | Installed software list with optional riskFlag |
user |
object | No | isAdminEstimate, localAdmins array |
Response 201 Created:
{
"id": "990e8400-e29b-41d4-a716-446655440070",
"received_at": "2025-04-15T12:00:05+00:00",
"risk": {
"score": 82,
"level": "low",
"signals": [
"1 software item(s) flagged as risky: SuspiciousTool"
]
}
}Error Responses:
| Status | Body | Cause |
|---|---|---|
| 400 | {"error": "employee_id is required."} |
Missing employee_id |
| 400 | {"error": "collectedAtUtc is required."} |
Missing timestamp |
| 400 | {"error": "Invalid collectedAtUtc format."} |
Bad datetime format |
| 404 | {"error": "Employee not found."} |
Bad employee_id or wrong org |
Purpose: Return the last 30 snapshots as a time-series of risk scores.
Consumer: Dashboard employee detail page — "Risk Trend" chart.
Why: Shows how the employee's device security posture has changed over time. Decreasing scores indicate worsening security; increasing scores indicate improvement.
Auth: Authorization: Token <key>
Request:
GET /api/agent/risk-trend/660e8400-e29b-41d4-a716-446655440001/
Authorization: Token a1b2c3d4...
Response 200 OK:
{
"employee_id": "660e8400-e29b-41d4-a716-446655440001",
"employee_name": "Jane Doe",
"hostname": "DESKTOP-JD01",
"data_points": 3,
"trend": [
{
"snapshot_id": "aa0e8400-0001-0001-0001-000000000001",
"collected_at": "2025-04-11T12:00:00+00:00",
"risk_score": 65,
"risk_level": "medium",
"signals": ["Firewall is disabled", "OS is not up to date"]
},
{
"snapshot_id": "aa0e8400-0001-0001-0001-000000000002",
"collected_at": "2025-04-12T12:00:00+00:00",
"risk_score": 75,
"risk_level": "medium",
"signals": ["OS is not up to date"]
},
{
"snapshot_id": "aa0e8400-0001-0001-0001-000000000003",
"collected_at": "2025-04-13T12:00:00+00:00",
"risk_score": 82,
"risk_level": "low",
"signals": []
}
]
}Purpose: Detect changes between the last two device snapshots.
Consumer: Dashboard employee detail page — "Device Drift" section.
Why: Highlights what changed since the last snapshot: new/removed software, opened/closed ports, new/disappeared processes. Helps admins spot unauthorized installs, unexpected port openings, or suspicious activity.
Auth: Authorization: Token <key>
Request:
GET /api/agent/drift/660e8400-e29b-41d4-a716-446655440001/
Authorization: Token a1b2c3d4...
Response — Changes detected 200 OK:
{
"employee_id": "660e8400-e29b-41d4-a716-446655440001",
"employee_name": "Jane Doe",
"previous_snapshot": {
"id": "aa0e8400-0001-0001-0001-000000000002",
"collected_at": "2025-04-12T12:00:00+00:00",
"risk_score": 75,
"risk_level": "medium"
},
"latest_snapshot": {
"id": "aa0e8400-0001-0001-0001-000000000003",
"collected_at": "2025-04-13T12:00:00+00:00",
"risk_score": 63,
"risk_level": "medium"
},
"risk_delta": -12,
"risk_changed": true,
"drift": {
"software_installed": ["TeamViewer", "Wireshark"],
"software_removed": [],
"ports_opened": [3389, 5900],
"ports_closed": [],
"processes_new": ["TeamViewer.exe"],
"processes_gone": []
},
"has_changes": true
}Response — Not enough snapshots 200 OK:
{
"employee_id": "660e8400-e29b-41d4-a716-446655440001",
"employee_name": "Jane Doe",
"message": "Not enough snapshots to compute drift (need at least 2).",
"drift": null
}Purpose: Organization-wide audit of risky open ports across all employee devices.
Consumer: Dashboard "Port Audit" page.
Why: Identifies exposed services across the org. Uses only the latest snapshot per employee. Results sorted by most affected devices first.
Monitored risky ports:
| Port | Label |
|---|---|
| 3389 | RDP |
| 445 | SMB |
| 22 | SSH |
| 5900 | VNC |
| 23 | Telnet |
| 21 | FTP |
| 1433 | MSSQL |
| 3306 | MySQL |
| 5432 | PostgreSQL |
| 6379 | Redis |
| 27017 | MongoDB |
Auth: Authorization: Token <key>
Request:
GET /api/agent/port-audit/
Authorization: Token a1b2c3d4...
Response 200 OK:
{
"organization": "Acme Corporation",
"devices_scanned": 25,
"risky_ports_found": 2,
"port_audit": [
{
"port": 3389,
"label": "RDP (3389) exposed",
"affected_devices": [
{
"employee_id": "660e8400-e29b-41d4-a716-446655440001",
"hostname": "DESKTOP-JD01",
"snapshot_id": "aa0e8400-0001-0001-0001-000000000003",
"collected_at": "2025-04-13T12:00:00+00:00"
},
{
"employee_id": "770e8400-e29b-41d4-a716-446655440002",
"hostname": "LAPTOP-AB02",
"snapshot_id": "bb0e8400-0002-0002-0002-000000000001",
"collected_at": "2025-04-13T14:00:00+00:00"
}
]
},
{
"port": 5432,
"label": "PostgreSQL (5432) exposed",
"affected_devices": [
{
"employee_id": "660e8400-e29b-41d4-a716-446655440001",
"hostname": "DESKTOP-JD01",
"snapshot_id": "aa0e8400-0001-0001-0001-000000000003",
"collected_at": "2025-04-13T12:00:00+00:00"
}
]
}
]
}Purpose: Organization-wide Shadow IT detection.
Consumer: Dashboard "Software Audit" page.
Why & How:
- Loads the org's approved software allowlist (from
ApprovedSoftwaremodel) - For each employee's latest snapshot, checks every installed software name
- If no approved entry is a case-insensitive substring of the software name, it's flagged as unapproved
- Results sorted by install count (most widespread unapproved software first)
Auth: Authorization: Token <key>
Request:
GET /api/agent/software-audit/
Authorization: Token a1b2c3d4...
Response 200 OK:
{
"organization": "Acme Corporation",
"approved_software_count": 15,
"unapproved_software_count": 3,
"unapproved_software": [
{
"software_name": "TeamViewer",
"install_count": 5,
"devices": [
{
"employee_id": "660e8400-e29b-41d4-a716-446655440001",
"employee_name": "Jane Doe",
"hostname": "DESKTOP-JD01"
},
{
"employee_id": "770e8400-e29b-41d4-a716-446655440002",
"employee_name": "Ahmed Benali",
"hostname": "LAPTOP-AB02"
}
]
},
{
"software_name": "Wireshark",
"install_count": 2,
"devices": [
{
"employee_id": "660e8400-e29b-41d4-a716-446655440001",
"employee_name": "Jane Doe",
"hostname": "DESKTOP-JD01"
}
]
}
]
}Manage the organization's approved software allowlist.
Purpose: List the org's approved software allowlist.
Consumer: Dashboard "Approved Software" management page.
Auth: Authorization: Token <key>
Request:
GET /api/agent/approved-software/
Authorization: Token a1b2c3d4...
Response 200 OK:
{
"approved_software": [
{
"id": "ff0e8400-e29b-41d4-a716-446655440080",
"name": "Microsoft Office",
"notes": "Standard office suite",
"added_at": "2025-03-01T00:00:00+00:00"
},
{
"id": "ff0e8400-e29b-41d4-a716-446655440081",
"name": "Visual Studio Code",
"notes": "Approved IDE for engineering",
"added_at": "2025-03-01T00:00:00+00:00"
},
{
"id": "ff0e8400-e29b-41d4-a716-446655440082",
"name": "Google Chrome",
"notes": "",
"added_at": "2025-03-05T10:00:00+00:00"
}
]
}Purpose: Add a software name to the approved allowlist.
Consumer: Dashboard "Add Approved Software" form.
Why: Admin approves a piece of software. Matching is case-insensitive substring — e.g., adding "Microsoft" approves all software whose name contains "Microsoft". Idempotent (returns existing entry if already present).
Auth: Authorization: Token <key>
Request:
POST /api/agent/approved-software/
Authorization: Token a1b2c3d4...
Content-Type: application/json
{
"name": "Slack",
"notes": "Approved communication tool"
}| Field | Type | Required |
|---|---|---|
name |
string | Yes |
notes |
string | No |
Response — New entry 201 Created:
{
"id": "ff0e8400-e29b-41d4-a716-446655440083",
"name": "Slack",
"notes": "Approved communication tool",
"created": true
}Response — Already exists 200 OK:
{
"id": "ff0e8400-e29b-41d4-a716-446655440083",
"name": "Slack",
"notes": "Approved communication tool",
"created": false
}Purpose: Remove an entry from the approved software list.
Consumer: Dashboard approved software management.
Why: Admin revokes approval — the software will now appear in the software audit as unapproved.
Auth: Authorization: Token <key>
Request:
DELETE /api/agent/approved-software/ff0e8400-e29b-41d4-a716-446655440083/
Authorization: Token a1b2c3d4...
Response 200 OK:
{
"deleted": true,
"id": "ff0e8400-e29b-41d4-a716-446655440083"
}Error Responses:
| Status | Body | Cause |
|---|---|---|
| 400 | {"error": "entry_id is required."} |
Missing entry_id in URL |
| 404 | {"error": "Entry not found."} |
Bad ID or wrong org |
The risk score starts at 100 and penalties are deducted based on the device snapshot payload:
| Signal | Penalty | Description |
|---|---|---|
| No antivirus detected | -20 | No AV software found |
| AV present but disabled | -15 | AV installed but not enabled |
| AV out of date | -8 | AV definitions not current |
| Firewall disabled | -20 | Firewall explicitly off |
| Firewall status unknown | -8 | Cannot determine firewall state |
| Disk encryption disabled | -15 | Full-disk encryption off |
| Disk encryption unknown | -5 | Cannot determine encryption state |
| OS not up to date | -15 | OS updates not current |
| OS update status unknown | -5 | Cannot determine OS update state |
| User has local admin | -10 | Current user is a local admin |
| >2 local admin accounts | -8 | Excessive admin accounts |
| Processes from suspicious paths | up to -15 | e.g., %TEMP%, %APPDATA%, Downloads |
| Each risky port open | -12 each | See port list in section 8.4 |
| Flagged risky software | -5 to -15 | Software with riskFlag: true |
Risk levels:
low— score >= 80medium— score >= 55high— score >= 30critical— score < 30
Score is floored at 0 (cannot go negative).
| Model | Field | Type | Notes |
|---|---|---|---|
| Organization | id |
UUID | Primary key, auto-generated |
email |
string | Unique, used for login | |
name |
string | Display name | |
password |
string | bcrypt-hashed | |
is_staff |
boolean | Django admin access | |
is_superuser |
boolean | Django superuser | |
created_at |
datetime | Auto-set on creation | |
| Employee | id |
UUID | Primary key |
organization |
FK → Organization | CASCADE delete | |
name |
string | Display name | |
email |
string | Unique globally | |
password |
string | bcrypt-hashed | |
department |
string | Default "" |
|
role |
string | Default "" |
|
seniority |
string | junior/mid/senior/lead/manager/executive |
|
is_active |
boolean | Default True |
|
registered_at |
datetime | Auto-set on creation | |
| AuthToken | key |
string (64-hex) | Unique, primary key |
organization |
FK → Organization | CASCADE delete | |
created_at |
datetime | Auto-set | |
| EmployeeAuthToken | key |
string (64-hex) | Unique, primary key |
employee |
FK → Employee | CASCADE delete | |
created_at |
datetime | Auto-set |
| Model | Field | Type | Notes |
|---|---|---|---|
| DLPLog | id |
UUID | Primary key |
employee |
FK → Employee | CASCADE delete | |
filename |
string | Uploaded file name | |
website |
string | Upload target URL | |
action_taken |
string | allow / cancel / force |
|
logged_at |
datetime | Auto-set | |
| BlacklistLog | id |
UUID | Primary key |
employee |
FK → Employee | CASCADE delete | |
attempted_url |
string | Blocked URL | |
logged_at |
datetime | Auto-set | |
| AdminEvent | id |
UUID | Primary key |
employee |
FK → Employee | CASCADE delete | |
event_type |
string | Currently only QUIZ |
|
payload |
JSON | Event-specific data | |
is_delivered |
boolean | Marked True after extension picks it up |
|
created_at |
datetime | Auto-set | |
| PhishingLog | id |
UUID | Primary key |
employee |
FK → Employee | CASCADE delete | |
clicked |
boolean | Whether the phishing link was clicked | |
website |
string | Phishing URL | |
logged_at |
datetime | Auto-set |
| Model | Field | Type | Notes |
|---|---|---|---|
| Quiz | id |
UUID | Primary key |
question |
string | Quiz question text | |
options |
JSON | {"a": "...", "b": "...", ...} |
|
correct_answer |
string | Key of correct option (never exposed to clients) | |
created_at |
datetime | Auto-set | |
| QuizSubmission | id |
UUID | Primary key |
employee |
FK → Employee | CASCADE delete | |
quiz |
FK → Quiz | CASCADE delete | |
answer_selected |
string | Employee's chosen option key | |
is_correct |
boolean | Computed on submission | |
Unique constraint: (employee, quiz) |
| Model | Field | Type | Notes |
|---|---|---|---|
| PhishingTemplate | id |
UUID | Primary key |
attack_type |
string | IT_RESET / INVOICE / DELIVERY / HR_UPDATE |
|
language |
string | EN / FR / AR_MSA / AR_DARIJA |
|
difficulty |
int | 1, 2, or 3 |
|
subject |
string | Email subject line | |
body |
text | HTML body with {{employee_name}} and {{tracking_url}} placeholders |
|
sender_name |
string | From display name | |
sender_domain |
string | From domain | |
created_at |
datetime | Auto-set | |
| PhishingCampaign | id |
UUID | Primary key |
organization |
FK → Organization | CASCADE delete | |
template |
FK → PhishingTemplate | CASCADE delete | |
name |
string | Campaign display name | |
status |
string | DRAFT / ACTIVE / COMPLETED |
|
created_at |
datetime | Auto-set | |
launched_at |
datetime | Set on launch (nullable) | |
completed_at |
datetime | Set on completion (nullable) | |
| PhishingSimulationTarget | id |
UUID | Primary key |
campaign |
FK → PhishingCampaign | CASCADE delete | |
employee |
FK → Employee | CASCADE delete | |
tracking_token |
UUID | Unique, auto-generated, used in click-tracking URL | |
sent_at |
datetime | When email was sent (nullable) | |
clicked_at |
datetime | When employee clicked (nullable) | |
Unique constraint: (campaign, employee) |
|||
| TrainingModule | id |
UUID | Primary key |
attack_type |
string | Matches template attack types | |
language |
string | Matches template languages | |
title |
string | Module display title | |
content |
text | Training content | |
duration_minutes |
int | Estimated completion time | |
created_at |
datetime | Auto-set | |
Unique constraint: (attack_type, language) |
|||
| TrainingEnrollment | id |
UUID | Primary key |
employee |
FK → Employee | CASCADE delete | |
module |
FK → TrainingModule | CASCADE delete | |
simulation_target |
FK → PhishingSimulationTarget | Nullable, links to the sim that triggered enrollment | |
enrolled_at |
datetime | Auto-set | |
completed_at |
datetime | Nullable, set on completion |
| Model | Field | Type | Notes |
|---|---|---|---|
| DeviceSnapshot | id |
UUID | Primary key |
employee |
FK → Employee | CASCADE delete | |
collected_at |
datetime | When agent collected data | |
received_at |
datetime | When backend received it | |
hostname |
string | Device hostname | |
os_name |
string | e.g., "Windows" | |
os_version |
string | e.g., "11 Pro 23H2" | |
architecture |
string | e.g., "x64" | |
cpu_cores |
int | Number of CPU cores | |
total_memory_mb |
int | Total RAM in MB | |
risk_score |
int | 0–100, computed by risk engine | |
risk_level |
string | low / medium / high / critical |
|
risk_signals |
JSON | Array of signal descriptions | |
raw |
JSON | Full original payload from agent | |
| ApprovedSoftware | id |
UUID | Primary key |
organization |
FK → Organization | CASCADE delete | |
name |
string | Software name (case-insensitive substring match) | |
notes |
string | Admin notes | |
added_at |
datetime | Auto-set | |
Unique constraint: (organization, name) |
| # | Method | URL | Auth | Consumer |
|---|---|---|---|---|
| 2.1 | POST | /api/auth/register |
None | Dashboard |
| 2.2 | POST | /api/auth/login |
None | Dashboard |
| 2.3 | POST | /api/auth/logout |
Org Token | Dashboard |
| 2.4 | GET | /api/auth/me |
Org Token | Dashboard |
| 3.1 | POST | /api/auth/employee/login |
None | Extension |
| 3.2 | POST | /api/auth/employee/logout |
Employee Token | Extension |
| 3.3 | GET | /api/auth/employee/me |
Employee Token | Extension |
| 4.1 | GET | /api/employees/ |
Org Token | Dashboard |
| 4.2 | POST | /api/employees/ |
Org Token | Dashboard |
| 4.3 | PATCH | /api/employees/<id>/ |
Org Token | Dashboard |
| 4.4 | DELETE | /api/employees/<id>/ |
Org Token | Dashboard |
| 5.1 | POST | /api/logs/dlp/ |
None | Extension |
| 5.2 | POST | /api/logs/blacklist/ |
None | Extension |
| 5.3 | GET | /api/extension/blacklist/ |
None | Extension |
| 5.4 | GET | /api/extension/poll/ |
None | Extension |
| 6.1 | GET | /api/gamification/quizzes/ |
None | Both |
| 6.2 | GET | /api/gamification/quizzes/<id>/ |
None | Extension |
| 6.3 | POST | /api/gamification/submit-quiz/ |
None | Extension |
| 7.1 | GET | /api/phishing/templates/ |
Org Token | Dashboard |
| 7.1b | POST | /api/phishing/templates/ |
Org Token | Dashboard |
| 7.2 | POST | /api/phishing/templates/generate/ |
Org Token | Dashboard |
| 7.3 | GET | /api/phishing/campaigns/ |
Org Token | Dashboard |
| 7.3b | POST | /api/phishing/campaigns/ |
Org Token | Dashboard |
| 7.4 | POST | /api/phishing/campaigns/<id>/launch/ |
Org Token | Dashboard |
| 7.5 | POST | /api/phishing/campaigns/<id>/complete/ |
Org Token | Dashboard |
| 7.6 | GET | /api/phishing/campaigns/<id>/targets/ |
Org Token | Dashboard |
| 7.7 | GET | /api/phishing/click/<token>/ |
None | Employee browser |
| 7.8 | POST | /api/phishing/targets/<id>/sent/ |
Org Token | Dashboard |
| 7.9 | GET | /api/phishing/analytics/ |
Org Token | Dashboard |
| 7.10 | GET | /api/phishing/analytics/trend/ |
Org Token | Dashboard |
| 7.11 | GET | /api/phishing/alerts/managers/ |
Org Token | Dashboard |
| 7.12 | GET | /api/phishing/training/ |
Org Token | Dashboard |
| 7.13 | GET | /api/phishing/training/enrollments/ |
Org Token | Dashboard |
| 7.14 | POST | /api/phishing/training/enrollments/<id>/complete/ |
Org Token | Dashboard |
| 7.15 | GET | /api/phishing/employees/<id>/difficulty/ |
Org Token | Dashboard |
| 8.1 | POST | /api/agent/snapshot/ |
Org Token | Desktop Agent |
| 8.2 | GET | /api/agent/risk-trend/<id>/ |
Org Token | Dashboard |
| 8.3 | GET | /api/agent/drift/<id>/ |
Org Token | Dashboard |
| 8.4 | GET | /api/agent/port-audit/ |
Org Token | Dashboard |
| 8.5 | GET | /api/agent/software-audit/ |
Org Token | Dashboard |
| 8.6 | GET/POST/DELETE | /api/agent/approved-software/ |
Org Token | Dashboard |
Total endpoints: 37 across 5 Django apps (organizations, extension, gamification, phishing, agent).