Skip to main content
Students get 3 attempts per case study by default. When a student runs out (due to technical issues, genuine failure, or special circumstances), faculty and admins can grant extra attempts — individually or in bulk — with optional expiry dates and full audit trails. All mutations are append-only: every grant, revoke, and expiry is persisted as a transaction record, so the full history is always reconstructable.

List Student Attempts

GET /v1/console/attempts
List every student’s attempt entitlement for a given case study. One row per student, with usage, remaining count, best score, and grant status.

Authentication

Requires ATTEMPT_MANAGEMENT.can_view permission.

Query parameters

case_study_id
string
required
The case study to scope the list to.
Search by student name or email (case-insensitive).
status
string
Filter by entitlement state: has_remaining, exhausted, or has_extra.
sort_by
string
default:"student_name"
Sort field: student_name, attempts_used, attempts_remaining, best_score, or latest_attempt_at.
sort_order
string
default:"asc"
asc or desc.
skip
integer
default:"0"
Number of records to skip.
limit
integer
default:"50"
Max records to return (1–100).

Example request

curl "https://staging-be.mind.miva.university/v1/console/attempts?case_study_id=6650c3d4e5f6a7b8c9d0e1f2&status=exhausted&sort_by=attempts_used&sort_order=desc" \
  -H "Authorization: Bearer <access_token>"

Response

{
  "success": true,
  "data": [
    {
      "user_id": "6650a1b2c3d4e5f6a7b8c9d0",
      "student_name": "Jane Smith",
      "student_email": "jane.smith@example.com",
      "case_study_id": "6650c3d4e5f6a7b8c9d0e1f2",
      "case_study_title": "Ethiopian Airlines Case Study",
      "base_attempts": 3,
      "extra_attempts": 2,
      "revoked_attempts": 0,
      "attempts_used": 4,
      "total_allowed": 5,
      "attempts_remaining": 1,
      "best_score": 78.5,
      "latest_attempt_at": "2026-04-01T14:30:00Z",
      "has_active_grants": true
    }
  ],
  "total": 1,
  "page": 1,
  "page_size": 50,
  "total_pages": 1,
  "message": null
}

Field reference

FieldDescription
base_attemptsDefault allowance (usually 3)
extra_attemptsTotal granted beyond base, after expiries applied
revoked_attemptsTotal revoked by faculty/admins
attempts_usedReal attempts the student has consumed (60s+ sessions)
total_allowedbase + extra − revoked
attempts_remainingmax(0, total_allowed − attempts_used)
best_scoreHighest graded score, or null if never graded
has_active_grantstrue if the student has any unexpired grant on this case study

Notes

  • Expired grants are processed lazily on read — the first request after expiry rolls back extra_attempts and writes an EXPIRY transaction. No cron needed.
  • Students with zero attempts used but with grants still appear in the list.

Get Student Detail

GET /v1/console/attempts/{user_id}
Full detail for a single student on a case study: entitlement summary, transaction history (grants, revokes, expiries), and per-session attempt list.

Authentication

Requires ATTEMPT_MANAGEMENT.can_view permission.

Path parameters

user_id
string
required
The student’s user ID.

Query parameters

case_study_id
string
required
The case study to scope the detail to.

Example request

curl "https://staging-be.mind.miva.university/v1/console/attempts/6650a1b2c3d4e5f6a7b8c9d0?case_study_id=6650c3d4e5f6a7b8c9d0e1f2" \
  -H "Authorization: Bearer <access_token>"

Response

{
  "success": true,
  "data": {
    "user_id": "6650a1b2c3d4e5f6a7b8c9d0",
    "student_name": "Jane Smith",
    "student_email": "jane.smith@example.com",
    "case_study_id": "6650c3d4e5f6a7b8c9d0e1f2",
    "case_study_title": "Ethiopian Airlines Case Study",
    "entitlement": {
      "base_attempts": 3,
      "extra_attempts": 2,
      "revoked_attempts": 0,
      "attempts_used": 4,
      "total_allowed": 5,
      "attempts_remaining": 1
    },
    "transactions": [
      {
        "id": "6650f1a2b3c4d5e6f7a8b9c0",
        "transaction_type": "grant",
        "amount": 2,
        "reason": "Student reported audio issues on 2nd attempt",
        "actor_user_id": "6650b2c3d4e5f6a7b8c9d0e1",
        "actor_name": "Dr. Mark Adebayo",
        "expires_at": "2026-04-20T23:59:59Z",
        "expired": false,
        "created_at": "2026-04-02T10:15:00Z"
      }
    ],
    "attempts": [
      {
        "session_id": "6650d4e5f6a7b8c9d0e1f2a3",
        "attempt_label": "Attempt 4",
        "score": 78.5,
        "status": "ended",
        "started_at": "2026-04-01T14:20:00Z",
        "ended_at": "2026-04-01T14:30:00Z",
        "duration_seconds": 542.0,
        "counted_as_attempt": true
      }
    ]
  },
  "message": null
}

Transaction types

TypeMeaning
grantExtra attempts added by a faculty/admin
revokeAttempts removed (cannot drop total below used)
expirySystem-generated when a grant’s expires_at passes

Grant Attempts (Single Student)

POST /v1/console/attempts/grant
Grant extra attempts to one student. Always requires an expiry date and reason. Supports idempotency.

Authentication

Requires ATTEMPT_MANAGEMENT.can_edit permission.

Request body

user_id
string
required
The student’s user ID.
case_study_id
string
required
The case study to grant on.
amount
integer
required
Number of extra attempts (must be > 0).
reason
string
required
Why the grant is being made (1–1000 chars). Shown in audit logs and the student email.
expires_at
datetime
required
When the grant expires. Must be in the future (ISO 8601).
idempotency_key
string
Client-generated unique key. Replaying the same key returns the current entitlement without re-applying.

Example request

curl -X POST "https://staging-be.mind.miva.university/v1/console/attempts/grant" \
  -H "Authorization: Bearer <access_token>" \
  -H "Content-Type: application/json" \
  -d '{
    "user_id": "6650a1b2c3d4e5f6a7b8c9d0",
    "case_study_id": "6650c3d4e5f6a7b8c9d0e1f2",
    "amount": 2,
    "reason": "Student reported audio issues on 2nd attempt",
    "expires_at": "2026-04-20T23:59:59Z",
    "idempotency_key": "grant-jane-eth-airlines-2026-04-02"
  }'

Response

{
  "success": true,
  "data": {
    "base_attempts": 3,
    "extra_attempts": 2,
    "revoked_attempts": 0,
    "attempts_used": 4,
    "total_allowed": 5,
    "attempts_remaining": 1
  },
  "message": "Attempts granted successfully"
}

Side effects

  • Writes a grant transaction to the ledger
  • Publishes attempt.granted event → recorded in audit log
  • Sends a branded email to the student with the new entitlement and expiry

Error responses

StatusCondition
400expires_at is in the past, amount <= 0, or reason missing
404Student or case study not found

Revoke Attempts (Single Student)

POST /v1/console/attempts/revoke
Revoke attempts from a student. Guarded: cannot revoke below the number of attempts already used.

Authentication

Requires ATTEMPT_MANAGEMENT.can_edit permission.

Request body

user_id
string
required
The student’s user ID.
case_study_id
string
required
The case study to revoke on.
amount
integer
required
Number of attempts to revoke (must be > 0).
reason
string
required
Why the revoke is being made (1–1000 chars).
idempotency_key
string
Optional client-generated unique key.

Example request

curl -X POST "https://staging-be.mind.miva.university/v1/console/attempts/revoke" \
  -H "Authorization: Bearer <access_token>" \
  -H "Content-Type: application/json" \
  -d '{
    "user_id": "6650a1b2c3d4e5f6a7b8c9d0",
    "case_study_id": "6650c3d4e5f6a7b8c9d0e1f2",
    "amount": 1,
    "reason": "Accidental over-grant — correcting"
  }'

Response

{
  "success": true,
  "data": {
    "base_attempts": 3,
    "extra_attempts": 2,
    "revoked_attempts": 1,
    "attempts_used": 4,
    "total_allowed": 4,
    "attempts_remaining": 0
  },
  "message": "Attempts revoked successfully"
}

Revoke guard

Revoke is rejected if it would reduce total_allowed below attempts_used. For example, if a student has used 4 of 5 allowed, the maximum revocable is 1. Attempting more returns a 400 with the exact available headroom.

Side effects

  • Writes a revoke transaction to the ledger
  • Publishes attempt.revoked event → recorded in audit log
  • Sends a branded email to the student

Bulk Grant Attempts

POST /v1/console/attempts/grant/bulk
Grant extra attempts to up to 500 students at once. Runs asynchronously via Celery with per-row result tracking. Supports dry-run and idempotency.

Authentication

Requires ATTEMPT_MANAGEMENT.can_edit permission.

Request body

case_study_id
string
required
The case study to grant on.
user_ids
array
required
List of student user IDs (1–500).
amount
integer
required
Number of extra attempts to grant each student.
reason
string
required
Shared reason applied to all rows (1–1000 chars).
expires_at
datetime
required
Expiry applied to all grants.
dry_run
boolean
default:"false"
If true, validates all rows without writing transactions.
idempotency_key
string
Optional. Replaying the same key returns the existing job.

Example request

curl -X POST "https://staging-be.mind.miva.university/v1/console/attempts/grant/bulk" \
  -H "Authorization: Bearer <access_token>" \
  -H "Content-Type: application/json" \
  -d '{
    "case_study_id": "6650c3d4e5f6a7b8c9d0e1f2",
    "user_ids": [
      "6650a1b2c3d4e5f6a7b8c9d0",
      "6650a2b3c4d5e6f7a8b9c0d1",
      "6650a3b4c5d6e7f8a9b0c1d2"
    ],
    "amount": 1,
    "reason": "Service outage on 2026-04-01 — granting retry",
    "expires_at": "2026-04-15T23:59:59Z",
    "dry_run": false,
    "idempotency_key": "bulk-outage-2026-04-01"
  }'

Response

{
  "success": true,
  "data": {
    "job_id": "6650f2a3b4c5d6e7f8a9b0c1",
    "status": "queued",
    "job_type": "grant",
    "total_rows": 3,
    "dry_run": false
  },
  "message": "Bulk grant job queued"
}
The job is queued immediately — poll Get Bulk Job Status to track progress and view per-row results.

Bulk Revoke Attempts

POST /v1/console/attempts/revoke/bulk
Revoke attempts from up to 500 students at once. Same contract as bulk grant, without the expires_at field. Each row is still guarded against revoking below used attempts — failing rows are reported in the job results but don’t abort the job.

Authentication

Requires ATTEMPT_MANAGEMENT.can_edit permission.

Request body

case_study_id
string
required
The case study to revoke on.
user_ids
array
required
List of student user IDs (1–500).
amount
integer
required
Number of attempts to revoke from each student.
reason
string
required
Shared reason (1–1000 chars).
dry_run
boolean
default:"false"
If true, validates without writing.
idempotency_key
string
Optional replay protection.

Example request

curl -X POST "https://staging-be.mind.miva.university/v1/console/attempts/revoke/bulk" \
  -H "Authorization: Bearer <access_token>" \
  -H "Content-Type: application/json" \
  -d '{
    "case_study_id": "6650c3d4e5f6a7b8c9d0e1f2",
    "user_ids": ["6650a1b2c3d4e5f6a7b8c9d0", "6650a2b3c4d5e6f7a8b9c0d1"],
    "amount": 1,
    "reason": "Correction after accidental mass-grant",
    "dry_run": true
  }'

Response

{
  "success": true,
  "data": {
    "job_id": "6650f3b4c5d6e7f8a9b0c1d2",
    "status": "queued",
    "job_type": "revoke",
    "total_rows": 2,
    "dry_run": true
  },
  "message": "Bulk revoke job queued"
}

Get Bulk Job Status

GET /v1/console/attempts/jobs/{job_id}
Poll the status of a queued or completed bulk job. Includes per-row results (success + error detail) once processing finishes.

Authentication

Requires ATTEMPT_MANAGEMENT.can_view permission.

Path parameters

job_id
string
required
The bulk job ID returned when the job was queued.

Example request

curl "https://staging-be.mind.miva.university/v1/console/attempts/jobs/6650f2a3b4c5d6e7f8a9b0c1" \
  -H "Authorization: Bearer <access_token>"

Response

{
  "success": true,
  "data": {
    "job_id": "6650f2a3b4c5d6e7f8a9b0c1",
    "job_type": "grant",
    "content_id": "6650c3d4e5f6a7b8c9d0e1f2",
    "status": "completed",
    "total_rows": 3,
    "processed_rows": 3,
    "succeeded_rows": 2,
    "failed_rows": 1,
    "results": [
      {
        "user_id": "6650a1b2c3d4e5f6a7b8c9d0",
        "success": true,
        "error": null
      },
      {
        "user_id": "6650a2b3c4d5e6f7a8b9c0d1",
        "success": true,
        "error": null
      },
      {
        "user_id": "6650a3b4c5d6e7f8a9b0c1d2",
        "success": false,
        "error": "Student not found"
      }
    ],
    "reason": "Service outage on 2026-04-01 — granting retry",
    "amount": 1,
    "expires_at": "2026-04-15T23:59:59Z",
    "dry_run": false,
    "started_at": "2026-04-02T09:00:01Z",
    "completed_at": "2026-04-02T09:00:04Z",
    "created_at": "2026-04-02T09:00:00Z"
  },
  "message": null
}

Job statuses

StatusMeaning
queuedIn the queue, worker hasn’t picked it up yet
processingWorker running, rows being applied incrementally
completedAll rows processed (check succeeded_rows / failed_rows for split)
failedFatal error before row processing started

Notes

  • Bulk jobs use partial success: a failing row (invalid student, revoke guard hit) is recorded with its error and the job continues processing the rest.
  • Results are updated row-by-row, so polling mid-flight shows incremental progress.
  • Idempotency keys are honoured at the job level — replaying returns the same job.

Audit Trail

Every grant and revoke (single or bulk) publishes an event that the audit service persists:
EventWhen
attempt.grantedSingle grant succeeds
attempt.revokedSingle revoke succeeds
attempt.bulk_grantBulk grant job completes
attempt.bulk_revokeBulk revoke job completes
Query these via the Audit Events endpoint, filtered by event_type or actor_user_id.