MeetyyAPI
Documentation / API Reference / Onboarding

Onboarding API

Everything a client needs to take a freshly registered user through onboarding β€” the role choice, the questionnaire payload, and (for mentors) the verification document step.

Onboarding is what makes someone a mentor: there is no separate mentor application in this flow. Submitting role: "mentor" assigns the mentor role and creates the mentor profile that the verification documents hang off.

  • Base URL: /api/v1
  • Auth: the question set is public; submitting and reading a submission require a Sanctum bearer token β€” Authorization: Bearer {token} β€” and a completed 2FA challenge (2fa.completed)
  • One submission per user per role. A user may onboard as both mentee and mentor; the mentor role is additive and never removes the mentee role.

Response envelope

Success

{ "status": "success", "message": "…", "data": { } }

Error

{ "status": "error", "message": "…", "data": { } }

Status codes

Code Meaning
200 OK (questions, show)
201 Onboarding submitted, verification document uploaded
401 Missing/invalid token
404 No onboarding record for that role
422 Validation error, invalid role, or already onboarded for that role
500 Server error

The flow

1. POST /api/v1/register                       β†’ user + token
2. POST /api/v1/verify-email
3. GET  /api/v1/onboarding/questions/{role}     (optional β€” the client may use its own steps)
4. POST /api/v1/onboarding                      role=mentee | mentor
       └─ role=mentor  β†’  mentor role assigned + mentor profile created (pending)
5. POST /api/v1/mentor/verification-documents   mentors only β€” KYC upload
6. PUT  /api/v1/mentor/profile                  mentors only β€” legal name, languages, expertise…

Step 5 is not required to finish onboarding. is_onboarded (returned on GET /api/v1/me and in the auth payloads) flips to true at step 4, so the client can drop the user into the app right away; the mentor profile stays pending until an admin approves it, and mentors can upload β€” or re-upload β€” documents at any point after step 4.


1. Onboarding questions

GET /api/v1/onboarding/questions/{role}

Public. role ∈ mentor · mentee. Returns the canonical question set. Labels and options differ per role (a mentor is asked what they can mentor in, a mentee what they want to learn).

200 Response

{
  "status": "success",
  "message": "Onboarding questions retrieved.",
  "data": {
    "role": "mentor",
    "questions": [
      {
        "key": "experience_level",
        "type": "single_select",
        "label": "What is your experience level?",
        "options": ["beginner", "intermediate", "advanced", "expert"],
        "required": true
      }
    ]
  }
}

Question types: single_select Β· multi_select Β· number Β· text Β· textarea.

Key Type Notes
goals multi_select Options differ by role
experience_level single_select beginner Β· intermediate Β· advanced Β· expert
preferred_communication single_select video Β· audio Β· chat
availability single_select weekdays Β· weekends Β· both Β· flexible
weekly_hours number 1–40
topics_of_interest multi_select Options differ by role
how_did_you_hear text Optional
expectations textarea Optional

The current onboarding UI hides these questionnaire steps and collects the profile, social and interests steps instead. The endpoint and its validation rules are kept intact so a step can be switched back on without an API change β€” see the note on responses below.

Errors: 422 "Invalid role. Must be mentor or mentee." Β· 500 "Failed to retrieve onboarding questions."


1b. Catalogues the wizard renders

Both are public β€” the steps render before there is anything user-specific to render against.

GET /api/v1/interests                β†’ { "data": { "groups": [ { "label", "interests": [ { id, slug, name } ] } ] } }
GET /api/v1/public/expertise-areas   β†’ { "data": { "expertise_areas": [ … ] } }

GET /interests is the suggested vocabulary for responses.interests / user.interests; send the name back, not the slug, and note it is not a closed set β€” an interest outside the catalogue still saves. Full reference: auth-api.md endpoint 39b.

GET /public/expertise-areas is an alias of GET /api/v1/mentor/available-expertise-areas, which carries no role gate and never did β€” a mentee may call either. The alias exists so the step, which runs for both roles, need not call a mentor-prefixed URL.


2. Submit onboarding

POST /api/v1/onboarding

Bearer (2fa.completed). Marks onboarding complete for the given role.

Field Type Required Notes
role enum yes mentor Β· mentee
responses object yes Answer map β€” may hold any subset of the keys below
responses.interests string[] no 3–10 items, each ≀ 100 β€” the "What are you interested in?" step. Also mirrored onto user.interests
responses.goals string[] no β‰₯ 1 item, each ≀ 255
responses.experience_level enum no beginner Β· intermediate Β· advanced Β· expert
responses.preferred_communication enum no video Β· audio Β· chat
responses.availability enum no weekdays Β· weekends Β· both Β· flexible
responses.weekly_hours int no 1–40
responses.topics_of_interest string[] no 1–10 items, each ≀ 100
responses.how_did_you_hear string no ≀ 255
responses.expectations string no ≀ 1000
responses.certifications object[] no ≀ 10 β€” same shape as PUT /profile
responses.education object[] no ≀ 20 β€” same shape as PUT /profile
responses.experiences object[] no ≀ 20 β€” same shape as PUT /profile
responses.expertise_areas object[] no ≀ 10 β€” { id, skills[] }, catalogue ids

Every answer is optional individually β€” responses itself is required and must be an object β€” but anything that is sent still has to be well-formed. This is what lets the UI ship a different step order without breaking the contract. The required: true flags returned by GET /onboarding/questions/{role} describe the intended UI; they are not enforced here.

A key with no rule is dropped, not passed through. responses is validated as an array with validated children, so Laravel strips any child that has no rule of its own. That is how the four credential collections were silently discarded before they were added to the table above β€” an unlisted key never reaches the service, and no error is raised. Anything new the wizard starts sending inside responses needs a rule here first.

Request

{
  "role": "mentor",
  "responses": {
    "interests": ["Web Development", "DevOps", "Leadership"]
  }
}

201 Response

{
  "status": "success",
  "message": "Onboarding completed successfully.",
  "data": {
    "onboarding": {
      "id": "9b6f…",
      "role": "mentor",
      "is_completed": true,
      "responses": { "interests": ["Web Development", "DevOps", "Leadership"] }
    }
  }
}

Side effect for both roles β€” inside the same transaction as the questionnaire:

  • The four credential collections β€” certifications, education, experiences and expertise_areas β€” are written to the user's own rows through the same service PUT /api/v1/profile uses, and keep its semantics exactly: a key that is present is the complete list (rows with an id updated, rows without one created, anything omitted deleted), and a key that is absent leaves that collection untouched. So a questionnaire-only submission never wipes what a per-step save already stored. Ids are scoped to the caller's own rows; naming someone else's is a 422. The full field-by-field rules are in profile-api-migration.md.
  • When responses.interests is present it is also written to users.interests, so the rest of the API can read a person's interests without going through their questionnaire. The answer map keeps its own copy; this is a second home, not a move. A user who onboards as both a mentor and a mentee has one list on the user, so the later submission wins. Interests stay editable afterwards through PUT /api/v1/profile.

Side effects for role: "mentor" β€” both inside the same transaction as the questionnaire:

  1. The mentor role is assigned (the existing mentee role is kept).
  2. A mentor profile is created with approval_status: "pending" and the user's timezone, if the user does not already have one. Its legal identity fields (legal_name, legal_birth_year, legal_profession, languages) are left empty on purpose β€” the mentor fills them in later via PUT /api/v1/mentor/profile. A user who already has a profile (e.g. from the legacy POST /api/v1/mentor/register application) keeps it untouched.

The profile is what unlocks POST /api/v1/mentor/verification-documents; before onboarding as a mentor, that endpoint answers 404 "Mentor profile not found."

Errors: 422 validation, or "You have already completed onboarding for this role." Β· 500 "Failed to submit onboarding."


3. Show onboarding

GET /api/v1/onboarding/{role}

Bearer (2fa.completed). role ∈ mentor · mentee. Returns the stored submission so the client can resume or display it.

200 Response

{
  "status": "success",
  "message": "Onboarding retrieved.",
  "data": {
    "onboarding": { "id": "9b6f…", "role": "mentor", "is_completed": true, "responses": { } }
  }
}

Errors: 422 invalid role Β· 404 "No onboarding found for this role." Β· 500 "Failed to retrieve onboarding."


4. Mentor verification documents

Part of the mentor onboarding journey, but served by the mentor module. Full reference: Mentor Verification Document API.

POST /api/v1/mentor/verification-documents      multipart/form-data

Bearer. Requires a mentor profile β€” that is, mentor onboarding must have been submitted first.

Field Type Required Notes
document_type enum yes id_card Β· passport Β· driving_license
documents file[] yes 1–5 files, pdf/jpg/jpeg/png, ≀ 5 MB each

Send several files under one type when a document has more than one page β€” the front and back of an ID card, for instance:

POST /api/v1/mentor/verification-documents
Content-Type: multipart/form-data

document_type=id_card
documents[][email protected]
documents[][email protected]

201 Response

{
  "status": "success",
  "message": "Verification documents uploaded successfully.",
  "data": {
    "verification_documents": [
      {
        "id": "9b6f…",
        "documentType": "id_card",
        "documentTypeLabel": "National ID Card",
        "originalName": "id-front.jpg",
        "status": "pending",
        "statusLabel": "Pending Review",
        "adminNotes": null,
        "downloadUrl": "https://api.example.com/api/v1/mentor/verification-documents/9b6f…/download"
      }
    ]
  }
}

Uploads start as pending; an admin approves or rejects each one and the mentor is notified. A pending or rejected document can be replaced (POST …/verification-documents/{id}) or deleted; an approved type is locked. Files live on a private disk and are only readable through the download endpoint.

Errors: 401 unauthenticated Β· 404 "Mentor profile not found." (onboarding not submitted) Β· 422 validation, or the type is already approved Β· 500 "Failed to upload verification document."


Related