Download OpenAPI specification:
Exam preparation and private Arena competitions.
The MirrorMingo Exam API is the public, developer-facing surface for MirrorMingo's verified exam-prep platform. It exposes:
All content is curriculum-grounded and verified against official exam-board sources. The Exam API does not generate past questions. Arena accepts partner-supplied questions, which are not automatically verified by MirrorMingo.
MirrorMingo School Intelligence is a separate school-subscription product. It is not exposed through this third-party developer API.
Every request must include a MirrorMingo API key as a bearer token:
Authorization: Bearer mirrormingo_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
Create and manage keys from the Developer dashboard. Keys are shown only once at creation time — store them securely and never embed them in client-side code.
The API is versioned in the path (/v1). Breaking changes ship under a new
major version; additive changes (new fields, new endpoints) may appear
within v1 without notice, so clients should ignore unknown fields.
Requests are rate limited per API key. The current allowance is returned on
every response via the RateLimit-* headers. On exhaustion the API responds
with 429 Too Many Requests and a Retry-After header.
Returns the catalog of exams MirrorMingo supports, optionally filtered by country. Each entry carries enough metadata to render a picker.
| country | string Full country name to prioritise locally relevant exams (e.g. |
| country_code | string = 2 characters ISO 3166-1 alpha-2 country code (e.g. |
{- "exams": [
- {
- "exam_key": "jamb",
- "name": "Joint Admissions and Matriculation Board",
- "category": "university_admission",
- "country": "Nigeria",
- "duration_min": 120
}
]
}Full specification for one exam — sections, timing, question types and scoring.
| exam_key required | string non-empty The exam identifier (e.g. |
{- "exam_key": "string",
- "name": "string",
- "category": "string",
- "country": "string",
- "duration_min": 0,
- "sections": [
- {
- "name": "string",
- "duration_min": 0,
- "questions": 0,
- "question_types": [
- "string"
], - "skills": [
- "string"
]
}
], - "scoring": { }
}Official exam-board sites and verified past-question sources for the exam.
| exam_key required | string non-empty The exam identifier (e.g. |
{- "exam_key": "string",
}A suggested week-by-week study path for the exam.
| exam_key required | string non-empty The exam identifier (e.g. |
{- "exam_key": "string",
- "study_weeks": 0,
- "study_hours_total": 0,
- "study_path": [
- { }
]
}Lists the verified past-question papers available for an exam, grouped
by year. Use a paper_id from this response to open a mock attempt.
| exam_key required | string non-empty The exam identifier (e.g. |
{- "exam_key": "string",
- "total_papers": 0,
- "years": [
- {
- "year": 2023,
- "papers": [
- {
- "paper_id": "jamb-mathematics-2023",
- "title": "JAMB Mathematics 2023",
- "subject": "Mathematics",
- "year": 0,
- "question_count": 0
}
]
}
]
}Opens a verified paper into a new timed mock-exam attempt and returns the
first page of questions. The returned attempt_id is used to page through
questions and to submit answers.
| exam_key required | string non-empty The exam identifier (e.g. |
| paper_id required | string [ 6 .. 160 ] characters A |
| question_offset | integer >= 0 Default: 0 |
| question_limit | integer [ 1 .. 20 ] Default: 9 |
| learner_id | string <= 160 characters Stable learner reference from your app. Use the same value when paging, submitting and listing history. |
{- "paper_id": "string",
- "question_offset": 0,
- "question_limit": 9,
- "learner_id": "string"
}{- "attempt_id": "string",
- "paper_id": "string",
- "question_count": 0,
- "questions": [
- {
- "answer_id": "12:4",
- "question_number": 0,
- "prompt": "string",
- "question_text": "string",
- "options": {
- "A": "12",
- "B": "14",
- "C": "16",
- "D": "18"
}, - "page": 0,
- "has_visual": true
}
]
}Loads one page of questions for a paper, for paging through long papers.
| exam_key required | string non-empty The exam identifier (e.g. |
| paper_id required | string [ 6 .. 160 ] characters |
| question_offset | integer >= 0 Default: 0 |
| question_limit | integer [ 1 .. 20 ] Default: 9 |
| learner_id | string <= 160 characters Same learner reference used when opening the attempt, if any. |
{- "paper_id": "string",
- "question_offset": 0,
- "question_limit": 9,
- "learner_id": "string"
}{- "questions": [
- {
- "answer_id": "12:4",
- "question_number": 0,
- "prompt": "string",
- "question_text": "string",
- "options": {
- "A": "12",
- "B": "14",
- "C": "16",
- "D": "18"
}, - "page": 0,
- "has_visual": true
}
], - "total": 0
}Submits learner answers for an attempt and returns scored feedback.
| exam_key required | string non-empty The exam identifier (e.g. |
| attempt_id required | string [ 6 .. 160 ] characters |
required | object Map of |
| status | string Default: "submitted" Enum: "submitted" "expired" "abandoned" |
| learner_id | string <= 160 characters Same learner reference used when opening the attempt, if any. |
{- "attempt_id": "string",
- "answers": {
- "12:1": "C",
- "12:2": "A",
- "12:3": "D"
}, - "status": "submitted",
- "learner_id": "string"
}{- "success": true,
- "feedback": { }
}Lists prior mock-exam attempts for the authenticated key, newest first.
| exam_key required | string non-empty The exam identifier (e.g. |
| limit | integer [ 1 .. 50 ] Default: 10 |
| offset | integer >= 0 Default: 0 |
| learner_id | string <= 160 characters Stable learner reference from your app. MirrorMingo stores only a hash-derived internal namespace. |
{- "attempts": [
- {
- "attempt_id": "string",
- "paper_id": "string",
- "score": 0,
- "submitted_at": "2019-08-24T14:15:22Z"
}
], - "total": 0
}Create Competition. See the Arena integration guide for lifecycle and retry semantics.
| request_id required | string (Request Id) [ 1 .. 100 ] characters |
| title required | string (Title) [ 1 .. 200 ] characters |
required | Array of objects (Questions) [ 1 .. 120 ] items |
| max_players | integer (Max Players) [ 1 .. 6 ] Default: 6 |
| seconds_per_question | integer (Seconds Per Question) [ 10 .. 300 ] Default: 30 |
Opens At (string) or Opens At (null) (Opens At) | |
Closes At (string) or Closes At (null) (Closes At) |
{- "request_id": "string",
- "title": "string",
- "questions": [
- {
- "id": "string",
- "prompt": "string",
- "options": [
- "string",
- "string"
], - "correct_index": 5,
- "explanation": ""
}
], - "max_players": 6,
- "seconds_per_question": 30,
- "opens_at": "2019-08-24T14:15:22Z",
- "closes_at": "2019-08-24T14:15:22Z"
}{- "id": "string",
- "title": "string",
- "version": 0,
- "ruleset": "turns-v1",
- "phase": "waiting",
- "server_time": 0,
- "deadline": 0,
- "current_index": 0,
- "question_count": 0,
- "max_players": 0,
- "seconds_per_question": 0,
- "players": [
- {
- "id": "string",
- "display_name": "string",
- "score": 0,
- "answered": 0,
- "elapsed_seconds": 0,
- "rank": 0
}
], - "turn_player_id": "string",
- "question": {
- "id": "string",
- "prompt": "string",
- "options": [
- "string"
]
}, - "result": {
- "question_id": "string",
- "participant_id": "string",
- "picked_index": 0,
- "correct_index": 0,
- "explanation": "string",
- "points": 0
}
}Get Competition. See the Arena integration guide for lifecycle and retry semantics.
| match_id required | string (Match Id) |
{- "id": "string",
- "title": "string",
- "version": 0,
- "ruleset": "turns-v1",
- "phase": "waiting",
- "server_time": 0,
- "deadline": 0,
- "current_index": 0,
- "question_count": 0,
- "max_players": 0,
- "seconds_per_question": 0,
- "players": [
- {
- "id": "string",
- "display_name": "string",
- "score": 0,
- "answered": 0,
- "elapsed_seconds": 0,
- "rank": 0
}
], - "turn_player_id": "string",
- "question": {
- "id": "string",
- "prompt": "string",
- "options": [
- "string"
]
}, - "result": {
- "question_id": "string",
- "participant_id": "string",
- "picked_index": 0,
- "correct_index": 0,
- "explanation": "string",
- "points": 0
}
}Create Session. See the Arena integration guide for lifecycle and retry semantics.
| match_id required | string (Match Id) |
| role | string (Role) Default: "participant" Enum: "participant" "spectator" |
| learner_id | string (Learner Id) <= 160 characters Default: "" |
| display_name | string (Display Name) <= 100 characters Default: "" |
| ttl_seconds | integer (Ttl Seconds) [ 60 .. 86400 ] Default: 3600 |
{- "role": "participant",
- "learner_id": "",
- "display_name": "",
- "ttl_seconds": 3600
}{- "token": "string",
- "match_id": "string",
- "participant_id": "string",
- "role": "participant",
- "expires_at": "string",
- "websocket_path": "string"
}Start Competition. See the Arena integration guide for lifecycle and retry semantics.
| match_id required | string (Match Id) |
{- "id": "string",
- "title": "string",
- "version": 0,
- "ruleset": "turns-v1",
- "phase": "waiting",
- "server_time": 0,
- "deadline": 0,
- "current_index": 0,
- "question_count": 0,
- "max_players": 0,
- "seconds_per_question": 0,
- "players": [
- {
- "id": "string",
- "display_name": "string",
- "score": 0,
- "answered": 0,
- "elapsed_seconds": 0,
- "rank": 0
}
], - "turn_player_id": "string",
- "question": {
- "id": "string",
- "prompt": "string",
- "options": [
- "string"
]
}, - "result": {
- "question_id": "string",
- "participant_id": "string",
- "picked_index": 0,
- "correct_index": 0,
- "explanation": "string",
- "points": 0
}
}Cancel Competition. See the Arena integration guide for lifecycle and retry semantics.
| match_id required | string (Match Id) |
{- "id": "string",
- "title": "string",
- "version": 0,
- "ruleset": "turns-v1",
- "phase": "waiting",
- "server_time": 0,
- "deadline": 0,
- "current_index": 0,
- "question_count": 0,
- "max_players": 0,
- "seconds_per_question": 0,
- "players": [
- {
- "id": "string",
- "display_name": "string",
- "score": 0,
- "answered": 0,
- "elapsed_seconds": 0,
- "rank": 0
}
], - "turn_player_id": "string",
- "question": {
- "id": "string",
- "prompt": "string",
- "options": [
- "string"
]
}, - "result": {
- "question_id": "string",
- "participant_id": "string",
- "picked_index": 0,
- "correct_index": 0,
- "explanation": "string",
- "points": 0
}
}Play State. See the Arena integration guide for lifecycle and retry semantics.
| match_id required | string (Match Id) |
{- "id": "string",
- "title": "string",
- "version": 0,
- "ruleset": "turns-v1",
- "phase": "waiting",
- "server_time": 0,
- "deadline": 0,
- "current_index": 0,
- "question_count": 0,
- "max_players": 0,
- "seconds_per_question": 0,
- "players": [
- {
- "id": "string",
- "display_name": "string",
- "score": 0,
- "answered": 0,
- "elapsed_seconds": 0,
- "rank": 0
}
], - "turn_player_id": "string",
- "question": {
- "id": "string",
- "prompt": "string",
- "options": [
- "string"
]
}, - "result": {
- "question_id": "string",
- "participant_id": "string",
- "picked_index": 0,
- "correct_index": 0,
- "explanation": "string",
- "points": 0
}
}Answer. See the Arena integration guide for lifecycle and retry semantics.
| match_id required | string (Match Id) |
| request_id required | string (Request Id) [ 1 .. 100 ] characters |
| question_id required | string (Question Id) [ 1 .. 100 ] characters |
| picked_index required | integer (Picked Index) [ 0 .. 5 ] |
{- "request_id": "string",
- "question_id": "string",
- "picked_index": 5
}{- "id": "string",
- "title": "string",
- "version": 0,
- "ruleset": "turns-v1",
- "phase": "waiting",
- "server_time": 0,
- "deadline": 0,
- "current_index": 0,
- "question_count": 0,
- "max_players": 0,
- "seconds_per_question": 0,
- "players": [
- {
- "id": "string",
- "display_name": "string",
- "score": 0,
- "answered": 0,
- "elapsed_seconds": 0,
- "rank": 0
}
], - "turn_player_id": "string",
- "question": {
- "id": "string",
- "prompt": "string",
- "options": [
- "string"
]
}, - "result": {
- "question_id": "string",
- "participant_id": "string",
- "picked_index": 0,
- "correct_index": 0,
- "explanation": "string",
- "points": 0
}
}Events. See the Arena integration guide for lifecycle and retry semantics.
| after | integer (After) >= 0 Default: 0 |
| limit | integer (Limit) [ 1 .. 100 ] Default: 50 |
{- "events": [
- {
- "sequence": 0,
- "event_id": "string",
- "type": "string",
- "match_id": "string",
- "data": { }
}
], - "cursor": 0,
- "has_more": true
}