RAID API
  1. Diver
  • DiveRAID frontend public API
    • Search divers
      POST
    • Search instructors
      POST
    • Search dive centers
      POST
    • Search dive centers by country
      GET
  • DiveRAID rest API V1
    • Auth
      • Register a new user
      • Login and get a Sanctum token
      • Send password reset email
      • Reset password using token
      • Logout and revoke the current token
      • Resend email verification
      • Complete a two-factor login and get a Sanctum token
    • Profile
      • The user resource
      • Get authenticated user profile
      • Update profile
      • Delete account
      • Update password
      • Update dive center association
    • Diver
      • Shared diver resources
      • Courses
        • List active courses
        • List expired courses
        • Get course detail
        • Submit module quiz
        • Get quiz result
        • Submit course exam
        • Get exam result
        • Get skills progress
        • Sign skills (diver)
      • Free Learnings
        • List enrolled free learnings
        • List available free learning courses
        • Enroll in a free learning course
        • Get free learning detail
        • Submit free learning module quiz
        • Get free learning quiz result
      • Certifications
        • Get certification history
        • Get certification quiz result
        • Get certification exam result
        • Get certification skills
        • List diver certifications
      • Dive Logs
        • Delete dive log
        • Create dive log
        • Update dive log
        • List loggable courses
        • List dive logs (paginated)
        • Get dive log
      • Awards
        • List award cards
      • Documents
        • Upload own medical certificate or insurance
        • List diver documents
      • Forms
        • List diver forms
      • Medical
        • Get medical questionnaire structure
        • Submit medical questionnaire
      • Store
        • List courses available for purchase
        • Get order status
    • Professional
      • Students
        • List students
        • Get student progress
        • Get student quiz result
        • Get student exam result
        • Get student skills progress
        • Sign student skills
      • Certifications
        • Get certification history
        • Get certification quiz result
        • Get certification exam result
        • Get certification skills
        • List all professional certifications
        • List diver-level certifications
        • List specialty certifications
        • List professional certifications
        • List trainer certifications
        • List examiner certifications
      • Classroom
        • List classrooms
        • Get classroom progress (all students)
        • Get classroom student progress
        • Get classroom student quiz result
        • Get classroom student exam result
        • Get classroom student skills
        • Sign classroom student skills
      • Renewals
        • List renewal courses
        • Get renewal progress
        • Renewal status
        • Submit renewal module quiz
        • Get renewal quiz result
        • Submit renewal exam
        • Get renewal exam result
        • Get renewal skills progress
        • Sign renewal skills
      • Recognitions
        • List recognition courses
      • Dive Logs
        • Get student dive log
        • Sign student dive log
        • List student dive logs for a course
      • Store
    • Sync
      • Upload offline operations
      • Download full course data for offline use
      • Get sync status
    • Dive Center
    • Public
      • List all countries
      • Dive-log enum values with labels in the response language
      • Get country data
      • Get country divisions (states/provinces)
      • List time zone identifiers, optionally narrowed to one country
      • List, search, and radius-search publicly visible dive centres
      • Every publicly visible, geolocated dive centre as a map marker
      • Countries with at least one publicly visible dive centre
      • Full detail of one publicly visible dive centre
  • Schemas
    • Frontend API Schema
      • DiverSearch
    • SuccessResponse
    • DiverCertification
    • ValidationErrorResponse
    • TwoFactorChallengeResponse
    • TokenResponse
    • ErrorResponse
    • DiveLog
    • UserProfile
    • CourseLog
    • CourseLogDetail
    • QuizResult
    • ExamResult
    • SkillProgress
    • Certification
    • PaginatedDiveLogs
    • StoreItem
    • PaymentIntentResponse
    • OrderConfirmResponse
    • OrderStatusResponse
    • InstructorSearch
    • DiveCenterSearch
  1. Diver

Shared diver resources

Shapes reused across more than one endpoint under /api/v1/diver/*. Documented once here; endpoint files link to the relevant section instead of repeating the field list.
All six were captured against the capture account (a seeded account with real course, certification, quiz, exam and dive log history), then redacted: real names and business names below are fabricated, the structure and field values (scores, dates, booleans) are real.

Course log#

Produced by App\Http\Resources\Api\V1\CourseLogResource. Returned as a collection by GET /api/v1/diver/courses and GET /api/v1/diver/free-learnings (see findings.md — the courses endpoint itself is currently broken; this shape was captured live through free-learnings, which renders the same resource class against the same course_logs-style data).
{
  "log_code": "9be9084934f36b03bbbb70c38acb9d50",
  "course": {
    "id": 165,
    "name": "Ice",
    "group": "recreational",
    "need_quiz": true,
    "need_exam": true,
    "need_skills": true
  },
  "instructor": null,
  "dive_center": null,
  "purchase_date": "2022-08-08",
  "expire_date": null,
  "quiz_lock": false,
  "admin_lock": false,
  "exam_enable": false,
  "is_completed": false
}
When instructor / dive_center are assigned, captured live elsewhere:
"instructor": { "id": 421, "full_name": "Example Instructor" },
"dive_center": { "id": 1281, "name": "Example Dive Center" }
FieldTypeNullableNotes
log_codestring(32)NoOpaque code, not a primary key. See _endpoint-template.md conventions
course.idintegerYesnull only if the course record was deleted
course.namestringYes
course.groupstringYesEnum value (not label), e.g. recreational, trainer
course.need_quizbooleanNo
course.need_exambooleanNo
course.need_skillsbooleanNo
instructorobjectYesnull when no instructor is assigned to the log
instructor.idintegerNoPresent only inside a non-null instructor
instructor.full_namestringNoPresent only inside a non-null instructor
dive_centerobjectYesnull when no dive centre is assigned
dive_center.idintegerNoPresent only inside a non-null dive_center
dive_center.namestringYesReads from dive_center->company->name; null if the centre has no company record
purchase_datestringYesY-m-d
expire_datestringYesY-m-d
quiz_lockbooleanNoSet by the anti-cheat check in CourseController::submitQuiz()
admin_lockbooleanNoSet by an administrator, or by the same anti-cheat check
exam_enablebooleanNo
is_completedbooleanNo

Detail variant (CourseLogDetailResource)#

Produced by App\Http\Resources\Api\V1\CourseLogDetailResource. Intended for GET /api/v1/diver/courses/{log_code} and GET /api/v1/diver/free-learnings/{log_code}.
Could not be captured live: both endpoints currently 500 on every call. See finding 3 in findings.md — skills_completed collides between a hasMany relation and a boolean accessor, and the resource's property access always resolves to the boolean, so ->sum() throws. The shape below is reconstructed field-by-field from the resource source, not from a live response; treat the field list as accurate but the example as illustrative, not captured.
{
  "log_code": "a6b952760c2b672d2f45da6989b1a8c1",
  "course": {
    "id": 12,
    "name": "Open Water",
    "group": "recreational",
    "need_quiz": true,
    "need_exam": true,
    "need_skills": true
  },
  "instructor": { "id": 421, "full_name": "Example Instructor" },
  "dive_center": { "id": 1281, "name": "Example Dive Center" },
  "purchase_date": "2023-01-10",
  "expire_date": null,
  "quiz_lock": false,
  "admin_lock": false,
  "exam_enable": true,
  "modules": [
    {
      "id": 41,
      "name": "Module 1",
      "quiz_questions": 10,
      "quiz_minimum": 8,
      "quiz_status": {
        "passed": true,
        "score": 100,
        "date": "2023-01-12"
      }
    }
  ],
  "exam_status": {
    "passed": true,
    "score": 96.5,
    "date": "2023-01-20"
  },
  "skills_count": {
    "total": 12,
    "signed": 12
  },
  "is_completed": true
}
FieldTypeNullableNotes
log_codestring(32)No
course.*Same shape as the course log's course object above
instructorobjectYesSame shape as the course log's instructor
dive_centerobjectYesSame shape as the course log's dive_center
purchase_datestringYesY-m-d
expire_datestringYesY-m-d
quiz_lockbooleanNo
admin_lockbooleanNo
exam_enablebooleanNo
modulesarrayNoOne entry per module in the course's version, [] when the course has no modules
modules[].idintegerNo
modules[].namestringNo
modules[].quiz_questionsintegerNoConfigured question count for the module's quiz
modules[].quiz_minimumintegerNoPassing threshold
modules[].quiz_statusobjectYesnull when the module's quiz has never been attempted
modules[].quiz_status.passedbooleanNoPresent only inside a non-null quiz_status
modules[].quiz_status.scorenumberNoPresent only inside a non-null quiz_status
modules[].quiz_status.datestringNoY-m-d, present only inside a non-null quiz_status
exam_statusobjectYesnull when the exam has never been attempted
exam_status.passed / .score / .dateSame shape as modules[].quiz_status
skills_count.totalintegerNoSum of skill counts across every skill log for this course log
skills_count.signedintegerNoSum of diver-signed skill counts
is_completedbooleanNo

Certification#

Produced by App\Http\Resources\Backend\User\CertificationResource, a backend resource used only by API controllers. Wraps every certification in an extra resource / data envelope in addition to the standard API envelope. Returned singly (as certification) by GET /api/v1/diver/certifications/{certification}/history, and by the /api/v1/professional/certifications endpoints.
GET /api/v1/diver/certifications uses App\Http\Resources\Api\V1\DiverCertificationResource instead: the same keys with the same values, plus certification_date, course_group and is_specialty, a previous_certification that resolves to the previous course name, and null-safe distributor and leveling_up_from. See certifications/GET_list.md.
{
  "resource": "Certification",
  "data": {
    "log_code": "b737d1ed6e52d5d0daa7794889f9d61c",
    "course": "Advanced Deco Trimix Instructor",
    "course_level": null,
    "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": true,
    "expire_date": "2027-10-20",
    "is_crossover": false,
    "leveling_up": false,
    "leveling_up_from": null,
    "leveling_id": null,
    "is_upgrade": false,
    "upgrade_course": null,
    "previous_certification": null
  }
}
Captured live, one entry from a 62-certification collection for the capture account. resource is a fixed literal, always "Certification".
FieldTypeNullableNotes
resourcestringNoAlways the literal "Certification"
data.log_codestring(32)No
data.coursestringNoCourse name, plain text, not an object
data.course_levelstringYesnull for courses with no level system
data.distributorstringNoReads distributor->name directly: a certification without a distributor makes this resource fail. The GET /diver/certifications resource returns null instead
data.dive_centerstringNodive_center->name ?? 'N/A' — the literal string N/A is a sentinel, not null
data.instructorstringNoinstructor->full_name ?? 'N/A' — same N/A sentinel
data.order_idintegerYesnull for certifications not tied to a store order (legacy/migrated records)
data.is_associatedbooleanNo
data.associated_logYesNot observed populated in this dataset
data.is_renewedintegerNo0/1, not cast to boolean — document as returned
data.will_expirebooleanNo
data.expire_datestringYesY-m-d, present only when will_expire is true in every observed record, but not enforced by the resource — treat as always-present-but-nullable
data.is_crossoverbooleanNo
data.leveling_upbooleanNo
data.leveling_up_fromstringYesCourse name of the certification leveling_id points to
data.leveling_idintegerYes
data.is_upgradebooleanNo
data.upgrade_coursestringYes
data.previous_certificationstringYes

Dive log#

Produced by App\Http\Resources\Api\V1\DiveLogResource. Returned by GET /api/v1/diver/dive-logs (paginated) and GET /api/v1/diver/dive-logs/{diveLog}.
{
  "id": 384110,
  "uuid": "6560b3f5-6d71-4f25-a769-6d593c024844",
  "number": 400,
  "date": "2022-09-05 00:00:00",
  "dive_type": "shore",
  "purpose_type": "fun_dive",
  "location": "Example Site",
  "country": null,
  "lat": null,
  "lon": null,
  "dive_config": "open_circuit",
  "gas": "air",
  "gas_mix": { "oxygen": 21, "nitrogen": 79 },
  "max_depth": 20,
  "average_depth": 12,
  "depth_unit": "meters",
  "dive_time": 52,
  "abt": null,
  "rnt": 0,
  "tbt": null,
  "tts": 0,
  "pressure_start": null,
  "pressure_end": null,
  "pressure_unit": "bar",
  "water_temperature": 13,
  "weather_temperature": 13,
  "temperature_unit": "celsius",
  "weather": "sunny",
  "water": "calm",
  "current": "no_current",
  "visibility": "poor",
  "weights": null,
  "weights_unit": "kilogram",
  "buddy": "Example Buddy",
  "verified_by": "EB",
  "wetsuit": "Sidemount",
  "sac_rate": null,
  "safety_stop": false,
  "has_decompression": false,
  "decompression_note": null,
  "mod": null,
  "end": null,
  "group_in": null,
  "group_out": null,
  "comments": "Example dive notes.",
  "log_code": null,
  "course_id": null,
  "skill_log_id": null,
  "skill_id": null,
  "instructor_id": null,
  "is_signed": false,
  "signed_at": null,
  "cylinder_count": 1,
  "bottom_cylinder_volume": 15,
  "dive_condition_notes": "Good"
}
Captured live from the capture account's 400-entry log. All *_type, *_config, gas, weather, water, current, visibility, *_unit fields are enum values (snake_case), not labels — unlike the user resource, which returns labels. Free-text fields (buddy, verified_by, location, comments, dive_condition_notes) are diver-entered and were redacted for this document; they are not sanitised or length-limited server-side beyond the column definition.
gas_mix is stored internally as {o, n, h} and translated to {oxygen, nitrogen, helium} by App\Support\DiveLog\GasMix on the way out (and back on the way in), so every client sees one shape whichever way the dive log was created.
The values the *_type, *_config, gas, weather, water, current and visibility fields can take, and which a client may send, are listed in dive-logs/POST_create.md → "Enum values".
FieldTypeNullable
idintegerNo
uuidstringNo
numberintegerNo
datestring (Y-m-d H:i:s)Yes
dive_typestring (enum value)Yes
purpose_typestring (enum value)Yes
locationstringYes
countrystringYes
lat / lonnumberYes
dive_configstring (enum value)Yes
gasstring (enum value)Yes
gas_mixobject ({oxygen, nitrogen, helium}), null when the dive has no recorded mix. Always these three API keysYes
max_depth / average_depthnumberYes
depth_unitstring (enum value)Yes
dive_timeintegerYes
abt / rnt / tbt / ttsnumberYes
pressure_start / pressure_endnumberYes
pressure_unitstring (enum value)Yes
water_temperature / weather_temperaturenumberYes
temperature_unitstring (enum value)Yes
weather / water / current / visibilitystring (enum value)Yes
weightsnumberYes
weights_unitstring (enum value)Yes
buddy / verified_bystringYes
wetsuitstringYes
sac_ratenumberYes
safety_stop / has_decompressionbooleanNo
decompression_notestringYes
mod / endnumberYes
group_in / group_outstring (one letter A to Z)Yes
commentsstringYes
log_codestring(32)Yes
course_id / skill_log_id / skill_id / instructor_idintegerYes
is_signedbooleanNo
signed_atstring (Y-m-d H:i:s)Yes
cylinder_countintegerYes
bottom_cylinder_volumenumberYes
dive_condition_notesstringYes

Pagination envelope (GET /api/v1/diver/dive-logs only)#

{
  "success": true,
  "status": "success",
  "message": "OK",
  "data": {
    "data": [ "...dive log objects above..." ],
    "meta": {
      "current_page": 1,
      "last_page": 20,
      "per_page": 20,
      "total": 400
    }
  }
}
Collection is under data.data, page metadata under data.meta. There is no links key. per_page is hardcoded to 20 server-side and not yet accepted as a query parameter — see docs/plans/ApiDocsDiver/plan.md's out-of-scope note.

Quiz result#

Produced by App\Http\Resources\Api\V1\QuizResultResource. Returned by POST /api/v1/diver/courses/{log_code}/modules/{module}/quiz (on submission), GET /api/v1/diver/courses/{log_code}/quiz/{quiz}, the free-learnings equivalents, and GET /api/v1/diver/certifications/{certification}/quiz/{quiz}.
{
  "id": 61222,
  "log_code": "6fc53117a24f6bbc76fa8f4bbcaa02dd",
  "module_id": 223,
  "total": 10,
  "correct": 10,
  "wrong": 0,
  "score": 100,
  "passed": true,
  "date": "2014-11-11",
  "start": "2014-11-11 05:54:36",
  "end": "2014-11-11 06:04:19",
  "questions": [
    {
      "id": 319,
      "text": "Who discovered Buoyancy?",
      "answers": [
        { "id": 1261, "text": "Boyle", "is_correct": false, "selected": false },
        { "id": 1263, "text": "Archimedes", "is_correct": true, "selected": true }
      ]
    }
  ]
}
Captured live: a 10-question quiz result with the full question and answer set, abbreviated above to one question of four answers.
FieldTypeNullableNotes
idintegerNo
log_codestring(32)No
module_idintegerNo
totalintegerNoQuestion count used for scoring, not necessarily count(questions)
correct / wrongintegerNo
scorenumberNoPercentage, rounded to 2 decimals server-side
passedbooleanNo
datestringNoY-m-d
start / endstringNoY-m-d H:i:s, client-supplied at submission time, not server timestamps
questionsarrayNoEvery question attempted, with every answer choice and the correct one revealed
questions[].id / .textNo
questions[].answers[].id / .textNo
questions[].answers[].is_correctbooleanNoReveals the correct answer regardless of what the diver selected
questions[].answers[].selectedbooleanNoWhat this diver chose

Exam result#

Produced by App\Http\Resources\Api\V1\ExamResultResource. Identical shape to the quiz result minus module_id (an exam is not scoped to a module). Returned by POST /api/v1/diver/courses/{log_code}/exam (on submission), GET /api/v1/diver/courses/{log_code}/exam/{exam}, and GET /api/v1/diver/certifications/{certification}/exam/{exam}.
{
  "id": 7063,
  "log_code": "f7efa55017629461ccd0b8bf303bfb91",
  "total": 45,
  "correct": 45,
  "wrong": 0,
  "score": 100,
  "passed": true,
  "date": "2014-11-28",
  "start": "2014-11-28 03:56:06",
  "end": "2014-11-28 04:09:25",
  "questions": [
    {
      "id": 1418,
      "text": "Can you hold your breath at any time while diving or hovering?",
      "answers": [
        { "id": 5641, "text": "Yes", "is_correct": false, "selected": false },
        { "id": 5642, "text": "No, never hold your breath while diving", "is_correct": true, "selected": true }
      ]
    }
  ]
}
Captured live: a 45-question exam result, abbreviated to one question of four answers. Field table: see Quiz result above, minus module_id.

Skill progress entry#

Produced by App\Http\Resources\Api\V1\SkillProgressResource. Intended for GET /api/v1/diver/courses/{log_code}/skills, GET /api/v1/diver/certifications/{certification}/skills, GET /api/v1/diver/certifications/{certification}/history, and the response of POST /api/v1/diver/courses/{log_code}/skills/sign.
Could not be captured live at all. Every code path that reaches this resource for a log with real skill records 500s first — see finding 2 in findings.md: SkillLog::with('skillType.skills') references a relation, skills, that does not exist on SkillType (the real relation is skill, singular). The resource itself also reads $this->skillType?->skills, so even a request that avoided the eager-load bug would fail inside the resource. The shape below is reconstructed from SkillProgressResource source only; no example is a captured live response — do not treat the example values as observed.
{
  "id": 7757,
  "log_code": "f7efa55017629461ccd0b8bf303bfb91",
  "skill_type_id": 14,
  "skill_type_name": "Example skill group",
  "user_completed": null,
  "start": "2023-01-15 10:00:00",
  "end": null,
  "skills": [
    {
      "id": 88,
      "name": "Example skill",
      "description": "Example skill description.",
      "diver_signed": false,
      "instructor_signed": false
    }
  ]
}
FieldTypeNullableNotes
idintegerNoTO VERIFY: not captured live
log_codestring(32)NoTO VERIFY: not captured live
skill_type_idintegerNoTO VERIFY: not captured live
skill_type_namestringYesnull if the skill type relation fails to load
user_completedYesType and semantics TO VERIFY — not clarified by the resource source alone
start / endstringYesY-m-d H:i:s, TO VERIFY: not captured live
skillsarrayNoEmpty when the skill type has no skills defined, or when skillType failed to load (currently always, given finding 2)
skills[].id / .name / .descriptionNoTO VERIFY: not captured live
skills[].diver_signedbooleanNoFrom the log's user_skills JSON map, keyed by skill id
skills[].instructor_signedbooleanNoFrom the log's instructor_skills JSON map

Source: docs/api/diver/_resources.md
Modified at 2026-10-08 12:41:30
Previous
Update dive center association
Next
List active courses
Built with