log_code: every certification recorded under it, every quiz attempt, every exam attempt, and every skill progress entry. Served by CertificationController::history().{log_code} is a plain string, not route-model-bound: the controller queries Certification::where('log_code', $log_code). A log_code no certification carries returns 404. Ownership is checked against the resolved set: if none of the certifications with that log_code belong to auth()->id(), 403.log_code carries more than one certification when the diver has leveled up — one certification per level reached, all sharing the same log_code. Quizzes, exams and skill logs are not level-scoped: they are shared across every level of the log_code (skills are also incremental, not repeated per level), which is why they stay flat arrays while certifications is the one field that can hold more than one row.App\Http\Controllers\Api\V1\Diver\CertificationController::history()api.v1.diver.certifications.historyAuthorization: Bearer {token}GET/api/v1/diver/certifications/{log_code}/history| Parameter | Type | Description |
|---|---|---|
{log_code} | string(32) | Certification log code, as returned by GET /api/v1/diver/certifications |
200 OKlog_code with 1 certification, 9 quizzes, 1 exam and 2 skill logs recorded. Abbreviated to 1 question per quiz/exam and 1 skill per skill log.{
"success": true,
"status": "success",
"message": "OK",
"data": {
"certifications": [
{
"resource": "Certification",
"data": {
"log_code": "339ff871bc19fdc146b7194b1b29f93a",
"course": "Divemaster Instructor",
"course_level": "Example Level 1",
"distributor": "Example Distributor",
"dive_center": "Example Dive Center",
"instructor": "Example Instructor",
"order_id": null,
"is_associated": false,
"associated_log": null,
"is_renewed": 0,
"will_expire": false,
"expire_date": null,
"is_crossover": false,
"leveling_up": false,
"leveling_up_from": null,
"leveling_id": null,
"is_upgrade": false,
"upgrade_course": null,
"previous_certification": null
}
},
{
"resource": "Certification",
"data": {
"log_code": "339ff871bc19fdc146b7194b1b29f93a",
"course": "Divemaster Instructor",
"course_level": "Example Level 2",
"distributor": "Example Distributor",
"dive_center": "Example Dive Center",
"instructor": "Example Instructor",
"order_id": null,
"is_associated": false,
"associated_log": null,
"is_renewed": 0,
"will_expire": false,
"expire_date": null,
"is_crossover": false,
"leveling_up": false,
"leveling_up_from": null,
"leveling_id": null,
"is_upgrade": false,
"upgrade_course": null,
"previous_certification": "Example Level 1 certification"
}
}
],
"quizzes": [
{
"id": 324380,
"log_code": "339ff871bc19fdc146b7194b1b29f93a",
"module_id": 391,
"total": 5,
"correct": 4,
"wrong": 1,
"score": 80,
"passed": true,
"date": "2017-08-07",
"start": "2017-08-07 02:30:33",
"end": "2017-08-07 02:34:37",
"questions": [
{
"id": 6887,
"text": "Example quiz question",
"answers": [
{ "id": 20101, "text": "Example wrong answer", "is_correct": false, "selected": false },
{ "id": 20102, "text": "Example right answer", "is_correct": true, "selected": true }
]
}
]
}
],
"exams": [
{
"id": 41827,
"log_code": "339ff871bc19fdc146b7194b1b29f93a",
"total": 20,
"correct": 19,
"wrong": 1,
"score": 95,
"passed": true,
"date": "2017-08-07",
"start": "2017-08-07 14:59:13",
"end": "2017-08-07 15:17:24",
"questions": [
{
"id": 6864,
"text": "Example exam question",
"answers": [
{ "id": 30201, "text": "Example wrong answer", "is_correct": false, "selected": true },
{ "id": 30202, "text": "Example right answer", "is_correct": true, "selected": false }
]
}
]
}
],
"skills": [
{
"id": 8123,
"log_code": "339ff871bc19fdc146b7194b1b29f93a",
"skill_type_id": 1,
"skill_type_name": "Confined Water or Pool",
"user_completed": [22, 0, 22, 100, 1],
"start": "2018-03-11 09:12:04",
"end": "2018-03-11 09:41:55",
"skills": [
{
"id": 471,
"name": "Example skill",
"description": "<p>Example skill description.</p>",
"diver_signed": true,
"instructor_signed": true
}
]
}
]
}
}| Field | Type | Nullable | Notes |
|---|---|---|---|
certifications | array | No | CertificationResource collection, _resources.md → "Certification". One entry per level the diver reached under this log_code, ordered by the level's sort then by certification_date |
quizzes | array | No | QuizResultResource collection, _resources.md → "Quiz result" |
exams | array | No | ExamResultResource collection, _resources.md → "Exam result" |
skills | array | No | SkillProgressResource collection, _resources.md → "Skill progress entry". [] when the log_code has no skill log records, populated otherwise |
401 Unauthorized{
"success": false,
"status": "error",
"message": "Unauthenticated"
}403 Forbiddenlog_code belonging to a different user.{
"success": false,
"status": "error",
"message": "Forbidden"
}404 Not Foundlog_code no certification carries. Standard envelope, unlike the earlier primary-key version of this route.{
"success": false,
"status": "error",
"message": "Resource not found"
}log_code carries one certification per level reached: quizzes and exams are shared across levels, the skills are incremental and signed per level, and a level-up reopens the same log_code and issues a new certification. certifications is therefore ordered by the level's sort, then by certification_date. The example shows two levels; the capture behind it held a single certification, which is the common case.quizzes and exams carries its full answers array, and each answer exposes is_correct alongside selected. The endpoint therefore reveals the correct answer of every quiz and exam question the diver has taken, captured 2026-09-23.Call to undefined relationship [skills] on model [App\Models\Skills\SkillType] crash documented until 23c8454ad (finding 2 of docs/plans/ApiDocsDiver/findings.md) is fixed: the skills key above is populated live for a log_code that has skill log records, captured 2026-09-23.curl --location 'https://test.diveraid.com/api/v1/diver/certifications//history' \
--header 'Authorization: Bearer <token>'{
"status": "success",
"message": "string",
"data": {
"log_code": "string",
"course": "string",
"quizzes": [
{
"id": 0,
"log_code": "string",
"module_id": 0,
"total": 0,
"correct": 0,
"wrong": 0,
"score": 0,
"passed": true,
"date": "2019-08-24",
"start": "2019-08-24T14:15:22.123Z",
"end": "2019-08-24T14:15:22.123Z",
"questions": [
{
"id": 0,
"text": "string",
"answers": [
{
"id": 0,
"text": "string",
"is_correct": true,
"selected": true
}
]
}
]
}
],
"exams": [
{
"id": 0,
"log_code": "string",
"total": 0,
"correct": 0,
"wrong": 0,
"score": 0,
"passed": true,
"date": "2019-08-24",
"start": "2019-08-24T14:15:22.123Z",
"end": "2019-08-24T14:15:22.123Z",
"questions": [
{
"id": 0,
"text": "string",
"answers": [
{
"id": 0,
"text": "string",
"is_correct": true,
"selected": true
}
]
}
]
}
],
"skills": [
{
"id": 0,
"log_code": "string",
"skill_type_id": 0,
"skill_type_name": "string",
"user_completed": true,
"start": "2019-08-24T14:15:22.123Z",
"end": "2019-08-24T14:15:22.123Z",
"skills": [
{
"id": 0,
"name": "string",
"description": "string",
"diver_signed": true,
"instructor_signed": true
}
]
}
]
}
}