8.9 KiB
Contact Create Flow (Frontend Guide)
This document explains the full flow to create a contact, send an automatic invite email, and let the invited user set their first password.
1) Prerequisites
- Admin user is already logged in and has an
access token. - Admin user has already configured a Security PIN.
- Frontend knows the target
role_id(fetch fromGET /api/v1/roles/get-all).
2) Endpoints Used
POST /api/v1/auth/pin/verifyto get a PINaction_token.POST /api/v1/contacts/createto create the contact (auto invite enabled).POST /api/v1/auth/set-passwordto set the first password from invite token.
3) Detailed Flow
Step A - Verify PIN for contact create action
Action ID for contact create:
2a59e8b4-5a72-4be3-b8fc-4f49941181c3
Request:
{
"data": {
"type": "auth_pin_verify",
"attributes": {
"pin": "123456",
"action": "2a59e8b4-5a72-4be3-b8fc-4f49941181c3"
}
}
}
Success response:
{
"data": {
"type": "auth_pin_verify",
"attributes": {
"verified": true,
"action_token": "<PIN_ACTION_TOKEN>",
"action_token_ttl_ms": 300000
}
}
}
Store action_token, then pass it to contact create in header:
X-PIN-Verification-Token: <PIN_ACTION_TOKEN>
Step B - Create Contact (auto invite)
Endpoint:
POST /api/v1/contacts/create
Required headers:
Authorization: Bearer <ACCESS_TOKEN>X-PIN-Verification-Token: <PIN_ACTION_TOKEN>Content-Type: application/json
Minimum required body:
data.type = contact_<role>_createattributes.role_idattributes.emailattributes.first_nameattributes.last_name
Allowed create types:
contact_pilot_createcontact_doctor_createcontact_air_rescuer_createcontact_technician_createcontact_flight_assistant_createcontact_staff_create
Rules:
data.typemust match the role resolved fromattributes.role_id.- Only one role-specific profile object is allowed, and it must match
data.type.
Note: backend now automatically sends an invite email after successful contact creation.
If PIN token is missing/invalid, API returns 202 with PIN verification required error.
Step C - User clicks invite link from email
Invite email contains a link like:
https://frontend-domain/set-password?token=<INVITE_TOKEN>
This token already carries user identity (email + user id claims) and is single-use.
Step D - Frontend submits set password
Endpoint:
POST /api/v1/auth/set-password
Request:
{
"data": {
"type": "auth_set_password",
"attributes": {
"token": "<INVITE_TOKEN>",
"password": "StrongP@ssw0rd123"
}
}
}
If successful, user can directly log in using POST /api/v1/auth/login.
4) Contact Create Payloads by Role
Pilot
{
"data": {
"type": "contact_pilot_create",
"attributes": {
"role_id": "<ROLE_ID_PILOT>",
"email": "pilot1@example.com",
"username": "pilot1",
"first_name": "Marco",
"last_name": "Melandri",
"mobile_phone": "+628123456789",
"timezone": "Asia/Jakarta",
"is_active": true,
"pilot": {
"pilot_category": "regular",
"short_name": "Marco",
"license_no": "PIL-001",
"license_issued_at": "2025-01-01T00:00:00Z",
"license_expired_at": "2028-01-01T00:00:00Z",
"technician_license_no": "TECH-001",
"has_ifr_qualification": true,
"is_chief_pilot": false,
"is_dul": false,
"total_flight_minutes": 12000,
"responsible_pilot_minutes": 5000,
"second_pilot_minutes": 3000,
"double_command_minutes": 1000,
"flight_instructor_minutes": 500,
"night_flight_minutes": 1200,
"ifr_flight_minutes": 800,
"location": "Jakarta",
"postcode": "12950",
"street_line": "Jl. Sudirman No. 1",
"weight_kg": 72.5,
"photo_file_id": "/uploads/contacts/pilot1.jpg"
}
}
}
}
Doctor
{
"data": {
"type": "contact_doctor_create",
"attributes": {
"role_id": "<ROLE_ID_DOCTOR>",
"email": "doctor1@example.com",
"username": "doctor1",
"first_name": "Alicia",
"last_name": "Pratama",
"mobile_phone": "+628123450001",
"timezone": "Asia/Jakarta",
"is_active": true,
"doctor": {
"short_name": "Dr. Alicia",
"medical_license_no": "MED-DOCTOR-001",
"license_issued_at": "2024-01-01T00:00:00Z",
"license_expired_at": "2029-01-01T00:00:00Z",
"specialization": "Emergency Medicine",
"sub_specialization": "Trauma",
"is_chief_physician": false,
"location": "Jakarta",
"postcode": "12950",
"street_line": "Jl. Jenderal Sudirman No. 10",
"photo_file_id": "/uploads/contacts/doctor1.jpg"
}
}
}
}
Air Rescuer
{
"data": {
"type": "contact_air_rescuer_create",
"attributes": {
"role_id": "<ROLE_ID_AIR_RESCUER>",
"email": "airrescuer1@example.com",
"username": "airrescuer1",
"first_name": "Raka",
"last_name": "Wijaya",
"mobile_phone": "+628123450002",
"timezone": "Asia/Jakarta",
"is_active": true,
"air_rescuer": {
"short_name": "Raka",
"is_chief_physician_in_charge": false,
"responsible_flight_rescuer": false,
"location": "Bandung",
"postcode": "40115",
"street_line": "Jl. Merdeka No. 20",
"photo_file_id": "/uploads/contacts/airrescuer1.jpg"
}
}
}
}
Technician
{
"data": {
"type": "contact_technician_create",
"attributes": {
"role_id": "<ROLE_ID_TECHNICIAN>",
"email": "tech1@example.com",
"username": "tech1",
"first_name": "Bima",
"last_name": "Nugraha",
"mobile_phone": "+628123450003",
"timezone": "Asia/Jakarta",
"is_active": true,
"technician": {
"short_name": "Bima",
"license_no": "TECH-7788",
"is_chief_technician": false,
"is_responsible_complaints": true,
"location": "Surabaya",
"postcode": "60231",
"street_line": "Jl. Diponegoro No. 30",
"photo_file_id": "/uploads/contacts/tech1.jpg"
}
}
}
}
Flight Assistant
{
"data": {
"type": "contact_flight_assistant_create",
"attributes": {
"role_id": "<ROLE_ID_FLIGHT_ASSISTANT>",
"email": "fa1@example.com",
"username": "fa1",
"first_name": "Nadia",
"last_name": "Putri",
"mobile_phone": "+628123450004",
"timezone": "Asia/Jakarta",
"is_active": true,
"flight_assistant": {
"short_name": "Nadia",
"location": "Yogyakarta",
"postcode": "55281",
"street_line": "Jl. Kaliurang No. 15",
"photo_file_id": "/uploads/contacts/fa1.jpg"
}
}
}
}
Staff
{
"data": {
"type": "contact_staff_create",
"attributes": {
"role_id": "<ROLE_ID_STAFF>",
"email": "staff1@example.com",
"username": "staff1",
"first_name": "Dina",
"last_name": "Maharani",
"mobile_phone": "+628123450005",
"timezone": "Asia/Jakarta",
"is_active": true,
"staff": {
"short_name": "Dina",
"role_label": "Operations Staff",
"location": "Semarang",
"postcode": "50132",
"street_line": "Jl. Pandanaran No. 8",
"photo_file_id": "/uploads/contacts/staff1.jpg"
}
}
}
}
5) Example Create Contact Response
{
"data": {
"type": "contact",
"id": "019ce67e-9289-731d-ae4c-0156902aabbc",
"attributes": {
"role_code": "doctor",
"role_name": "doctor",
"email": "doctor1@example.com",
"first_name": "Alicia",
"last_name": "Pratama",
"is_active": true,
"doctor": {
"short_name": "Dr. Alicia",
"medical_license_no": "MED-DOCTOR-001"
},
"invite_sent": true,
"invite_error": ""
}
}
}
If invite sending fails (for example SES/SQS config issue):
- contact create is still successful,
invite_sent = false,invite_errorcontains the error message.
6) Error Cases Frontend Should Handle
202 PIN verification requiredon contact create:- call
/auth/pin/verifyagain (Step A), then retry create withX-PIN-Verification-Token.
- call
422 Validation error:- check payload fields (invalid role_id, invalid email, etc).
400on create:- typically business/constraint conflict.
400on set-password:- token invalid/expired/used or password does not meet rules.
7) Frontend Integration Summary
- Admin logs in.
- Fetch
role_idfrom roles list. - Verify PIN (
/auth/pin/verifywith contact-create action ID). - Create contact (
/contacts/create+X-PIN-Verification-Token). - Wait for invited user to open invite email.
- Set-password page reads
tokenfrom query param. - Submit
/auth/set-passwordwith{ token, password }. - Redirect user to login page.