TravelConnect API Documentation — All Modules

Auth API

TravelConnect API Docs - Auth (Login/Logout/Reset Password)

TravelConnect API Docs — Authentication

Endpoints: Login, Logout, Reset Password • Generated: 2026-01-09 10:12
This document is based on the provided DB design (accounts + account_profiles) and the FE flows. Responses intentionally do not include a meta key.

1. Base rules

ItemRule
Base URLEnvironment-dependent, e.g. https://api.example.com
Content-Typeapplication/json
TimezoneAPI timestamps in UTC

2. Authentication

Protected endpoints require a Bearer token:

Authorization: Bearer <access_token>

Logout revokes the current token.

3. Response envelope (no meta)

3.1 Success

{
  "status": 200,
  "message": "OK",
  "data": { ... }
}

3.2 Error

{
  "status": 422,
  "message": "Invalid input.",
  "data": {
    "code": "VALIDATION_ERROR",
    "message": "Invalid input.",
    "fields": {
      "password": [
        "Password is required."
      ]
    }
  }
}

fields is present only for validation errors.

4. API: Login POST /api/auth/login

4.1 Request

FieldTypeRequiredValidationDescription
identifierstringYesmax 255Email or account code (accounts.code)
passwordstringYesmin 8, max 255Matches accounts.password (hashed)

4.2 Validation messages (English)

  • identifier.required — “Email or user code is required.”
  • identifier.string — “Email or user code must be a string.”
  • identifier.max — “Email or user code must not exceed 255 characters.”
  • password.required — “Password is required.”
  • password.string — “Password must be a string.”
  • password.min — “Password must be at least 8 characters.”
  • password.max — “Password must not exceed 255 characters.”

4.3 Example request

POST /api/auth/login
Content-Type: application/json

{
  "identifier": "advisor001@example.com",
  "password": "********"
}

4.4 Success response (200)

{
  "status": 200,
  "message": "OK",
  "data": {
    "access_token": "plain_text_token_here",
    "token_type": "Bearer",
    "user": {
      "id": 1,
      "code": "ACC-001",
      "name": "John Doe",
      "email": "advisor001@example.com",
      "status": 1,
      "role": 3
    }
  }
}

4.5 Error responses

Statuserror.codemessage
400VALIDATION_ERROR“Invalid input.”
401INVALID_CREDENTIALS“Invalid email/user code or password.”
403ACCOUNT_DISABLED“Account is disabled.”

5. API: Logout POST /api/auth/logout

5.1 Request

Requires Bearer token.

POST /api/auth/logout
Authorization: Bearer <access_token>

5.2 Success response (200)

{
  "status": 200,
  "message": "OK",
  "data": {
    "revoked": true
  }
}

5.3 Error responses

Statuserror.codemessage
401UNAUTHENTICATED“Missing or invalid token.”

6. API: Reset Password POST /api/auth/password/reset

Identity verification uses the provided fields and existing DB: accounts.email, accounts.name, and (if present) account_profiles.phone.

6.1 Request

FieldTypeRequiredValidationDescription
emailstringYesemail, max 255Matches accounts.email
namestringYesmax 100Matches accounts.name
phonestringYesmax 20Matches account_profiles.phone (if profile exists)
new_passwordstringYesmin 8, max 255New password
new_password_confirmationstringYessame:new_passwordConfirmation

6.2 Validation messages (English)

  • email.required — “Email is required.”
  • email.email — “Email format is invalid.”
  • email.max — “Email must not exceed 255 characters.”
  • name.required — “Name is required.”
  • name.string — “Name must be a string.”
  • name.max — “Name must not exceed 100 characters.”
  • phone.required — “Phone number is required.”
  • phone.string — “Phone number must be a string.”
  • phone.max — “Phone number must not exceed 20 characters.”
  • new_password.required — “New password is required.”
  • new_password.string — “New password must be a string.”
  • new_password.min — “New password must be at least 8 characters.”
  • new_password.max — “New password must not exceed 255 characters.”
  • new_password_confirmation.required — “Password confirmation is required.”
  • new_password_confirmation.same — “Password confirmation does not match.”

6.3 Example request

POST /api/auth/password/reset
Content-Type: application/json

{
  "email": "advisor001@example.com",
  "name": "John Doe",
  "phone": "090-1234-5678",
  "new_password": "NewStrongPassw0rd!",
  "new_password_confirmation": "NewStrongPassw0rd!"
}

6.4 Success response (200)

{
  "status": 200,
  "message": "OK",
  "data": {
    "reset": true
  }
}

6.5 Error responses

Statuserror.codemessage
400VALIDATION_ERROR“Invalid input.”
403ACCOUNT_DISABLED“Account is disabled.”
404USER_NOT_FOUND“User not found.”
422IDENTITY_VERIFICATION_FAILED“Identity verification failed.”

Recommended behavior: return the same 422 message for any mismatch (name/phone) to avoid leaking which field is incorrect.

7. Error codes

CodeMeaningTypical status
VALIDATION_ERRORRequest validation failed400
INVALID_CREDENTIALSIdentifier/password mismatch401
UNAUTHENTICATEDMissing/invalid token401
ACCOUNT_DISABLEDAccount status is not active403
USER_NOT_FOUNDUser does not exist404
IDENTITY_VERIFICATION_FAILEDIdentity check failed for reset password422

Account Management

TravelConnect API Docs - Account Management

TravelConnect API Docs — Account Management

Generated 2026-01-10 08:43 UTC. Screen mapping: /accounts, /accounts/new, /accounts/{id}/edit

1. Common

1.1 Authentication

All endpoints require an access token.

Authorization: Bearer <access_token>

1.2 Response format

{
  "status": 200,
  "message": "OK",
  "data": { ... }
}

On error:

{
  "status": 422,
  "message": "Invalid input.",
  "data": {
    "code": "VALIDATION_ERROR",
    "message": "Invalid input.",
    "details": {
      "field": [
        "Message..."
      ]
    }
  }
}

2. Enums

2.1 roles

  • 1: Operator (事業者(マスター))
  • 2: Advisor Success (アドバイザーサクセス / AS)
  • 3: Advisor (アドバイザー)

2.2 status

  • 0: Inactive (無効)
  • 1: Active (有効)

3. Account List (アカウント管理) — /accounts

GET /api/accounts

List accounts. The UI provides text search and role tabs. No advisor-ids filter.

3.1 Query parameters

NameTypeRequiredValidationDescription
qstringNomax:100Search by code, name, or email
roleintegerNoin:1,2,3Filter by role
pageintegerNomin:1Page number
per_pageintegerNomin:1, max:100Items per page

3.2 Validation messages (English)

  • q.max — “Search query may not be greater than 100 characters.”
  • role.in — “Role is invalid.”
  • page.min — “Page must be at least 1.”
  • per_page.min — “Per page must be at least 1.”
  • per_page.max — “Per page may not be greater than 100.”

3.3 Example request

GET /api/accounts?page=1&per_page=20&q=sato&role=2
Authorization: Bearer <access_token>

3.4 Success response (200)

{
  "status": 200,
  "message": "OK",
  "data": {
    "items": [
      {
        "id": 10,
        "code": "AS-001",
        "name": "Hanako Sato",
        "email": "sato@example.com",
        "role": 2,
        "status": 1,
        "department": 1,
          { "id": 21, "code": "ADV-001", "name": "Taro Tanaka" },
          { "id": 22, "code": "ADV-002", "name": "Yuki Suzuki" }
        ],
        "created_at": "2026-01-01T00:00:00Z",
        "updated_at": "2026-01-01T00:00:00Z"
      },
      {
        "id": 21,
        "code": "ADV-001",
        "name": "Taro Tanaka",
        "email": "tanaka@example.com",
        "role": 3,
        "status": 1,
        "department": 1,
        "created_at": "2026-01-01T00:00:00Z",
        "updated_at": "2026-01-01T00:00:00Z"
      }
    ],
    "pagination": {
      "page": 1,
      "per_page": 20,
      "total": 124,
      "total_pages": 7
    }
  }
}

3.5 Error responses

Statuserror.codemessage
400VALIDATION_ERRORInvalid input.
401UNAUTHENTICATEDMissing or invalid token.
403FORBIDDENYou do not have permission to perform this action.

4. New Account (新規アカウント登録) — /accounts/new

POST /api/accounts

Create a new account.

4.1 Request body

NameTypeRequiredValidationDescription
codestringYesmax:20User ID (e.g., AS-001, ADV-001)
namestringYesmax:255Display name
emailstringYesemail, max:255, unique:accounts.emailEmail
passwordstringYesmin:8, max:255Password
password_confirmationstringYessame:passwordConfirm password
roleintegerYesin:1,2,3Role
departmentintegerNo-Department
statusintegerNoin:0,1Status (default: 1)

4.2 Validation messages (English)

  • code.required — “User ID is required.”
  • code.max — “User ID may not be greater than 20 characters.”
  • name.required — “Name is required.”
  • email.email — “Email must be a valid email address.”
  • email.unique — “Email has already been taken.”
  • password.min — “Password must be at least 8 characters.”
  • password_confirmation.same — “Password confirmation does not match.”
  • role.in — “Role is invalid.”

4.3 Success response (201)

{
  "status": 201,
  "message": "Created",
  "data": {
    "account": {
      "id": 10,
      "code": "AS-001",
      "name": "Hanako Sato",
      "email": "sato@example.com",
      "role": 2,
      "status": 1,
      "department": 1,
        { "id": 21, "code": "ADV-001", "name": "Taro Tanaka" }
      ],
      "created_at": "2026-01-01T00:00:00Z",
      "updated_at": "2026-01-01T00:00:00Z"
    }
  }
}

5. Account Edit (アカウント編集) — /accounts/{id}/edit

GET /api/accounts/<id>

Get a single account and role-specific relations.

5.1 Path parameters

NameTypeRequiredValidationDescription
idintegerYesexists:accounts,idAccount ID

5.2 Success response (200)

{
  "status": 200,
  "message": "OK",
  "data": {
    "account": {
      "id": 10,
      "code": "ADV-001",
      "name": "Taro Tanaka",
      "email": "tanaka@example.com",
      "role": 3,
      "status": 1,
      "department": 1,
      "advisor_success": {
        "id": 3,
        "code": "AS-001",
        "name": "Hanako Sato"
      },
      "created_at": "2026-01-01T00:00:00Z",
      "updated_at": "2026-01-01T00:00:00Z"
    }
  }
}

If role=2, the response includes managed_advisors instead of advisor_success.

6. Update Account (アカウント編集 保存)

PUT /api/accounts/<id>

Update account fields. Password change is optional.

6.1 Request body

Same as create, but password is optional. Use new_password + new_password_confirmation when changing password.

NameTypeRequiredValidationDescription
codestringYesmax:20User ID
namestringYesmax:255Name
emailstringYesemail, max:255, unique:accounts.email,<id>Email
roleintegerYesin:1,2,3Role
departmentintegerNo-Department
statusintegerNoin:0,1Status
new_passwordstringNomin:8, max:255New password
new_password_confirmationstringNosame:new_passwordConfirm new password

6.2 Success response (200)

{
  "status": 200,
  "message": "OK",
  "data": {
    "account": {
      "id": 10,
      "code": "AS-001",
      "name": "Hanako Sato",
      "email": "sato@example.com",
      "role": 2,
      "status": 1,
      "department": 1,
        { "id": 21, "code": "ADV-001", "name": "Taro Tanaka" }
      ],
      "updated_at": "2026-01-02T00:00:00Z"
    }
  }
}

7. Delete Account (アカウント削除)

DELETE /api/accounts/<id>

Soft delete is recommended (set deleted_at).

7.1 Success response (200)

{
  "status": 200,
  "message": "OK",
  "data": {
    "deleted": true
  }
}

Changelog

  • Fix: Removed advisor_ids query/filter and removed duplicated keys (advisor_ids + assigned_advisors). Replaced with a single managed_advisors array for role=2 (AS), based on DB mapping.

Customer Management

TravelConnect API Docs — Customer Management

Customer Management (顧客管理)

Base URL: /api • All responses are wrapped as {"status": int, "message": string, "data": any} (no meta key).
Auth: Authorization: Bearer <access_token>Template CSS source: travelconnect_form_management_api_docs_v1_status_message_data.html

Overview

  • UI module: /customers (顧客管理)
  • Goal: manage customer profiles used in Project/Proposal flows.
  • Identifier: customers are referenced by code (example: CUS-001).
  • Tags: stored as JSON in DB; exposed as tags: string[] in API.
  • Delete: recommended as soft delete (deleted_at).

Screens & routes

Customer list (/customers)

  • Search + filter customers, paginate results.
  • Uses GET /api/customers.

New customer (/customers/new)

  • Create a customer profile.
  • Uses POST /api/customers.

Edit customer (/customers/{code}/edit)

  • Load: GET /api/customers/{code}
  • Save: PUT /api/customers/{code}
  • Delete action: DELETE /api/customers/{code}

Enums

customer.status

ValueLabelNotes
0New新規
1Activeアクティブ
2Dormant休眠

Endpoints

List customers

GET/api/customers

Returns customers for the list screen. Supports keyword search, filters, pagination, and sorting.

Query parameters

NameTypeRequiredDescription
qstringNoSearch by code / name / email / phone.
statusintegerNoStatus filter (0/1/2).
tagstringNoFilter by a single tag.
registered_fromdateNoRegistered date (from).
registered_todateNoRegistered date (to).
last_visit_fromdateNoLast visit date (from).
last_visit_todateNoLast visit date (to).
pageintegerNoPage number (default 1).
per_pageintegerNoItems per page (default 20, max 100).
sortstringNoSort key. Example: -updated_at, name.

Success response

{
  "status": 200,
  "message": "OK",
  "data": {
    "items": [
      {
        "id": 12,
        "code": "CUS-001",
        "name": "Hanako Yamada",
        "name_kana": "ヤマダ ハナコ",
        "email": "hanako.yamada@email.com",
        "phone": "090-1234-5678",
        "status": {
          "status": 1,
          "label": "Active"
        },
        "address": "Tokyo, Japan",
        "registered_at": "2024-03-01",
        "last_visit_at": "2024-03-20",
        "tags": [
          "family",
          "vip"
        ],
        "note": "Prefers kid-friendly activities.",
        "created_at": "2024-03-01 10:00:00",
        "updated_at": "2024-03-20 12:30:00"
      }
    ],
    "pagination": {
      "page": 1,
      "per_page": 20,
      "total": 1,
      "total_pages": 1
    }
  }
}

Get customer detail

GET/api/customers/{code}

Used by the edit screen to load a customer.

Path parameters

NameTypeRequiredDescription
codestringYesCustomer code (e.g., CUS-001).

Success response

{
  "status": 200,
  "message": "OK",
  "data": {
    "id": 12,
    "code": "CUS-001",
    "name": "Hanako Yamada",
    "name_kana": "ヤマダ ハナコ",
    "email": "hanako.yamada@email.com",
    "phone": "090-1234-5678",
    "status": {
      "status": 1,
      "label": "Active"
    },
    "address": "Tokyo, Japan",
    "registered_at": "2024-03-01",
    "last_visit_at": "2024-03-20",
    "tags": [
      "family",
      "vip"
    ],
    "note": "Prefers kid-friendly activities.",
    "created_at": "2024-03-01 10:00:00",
    "updated_at": "2024-03-20 12:30:00"
  }
}

Not found

{
  "status": 404,
  "message": "Not found",
  "data": {
    "error": "Customer not found."
  }
}

Create customer

POST/api/customers

Creates a new customer. code should be generated by the server.

Request body

FieldTypeRequiredDescription
namestringYesCustomer name.
name_kanastringYesName in kana.
emailstringYesEmail address.
phonestringYesPhone number.
statusintegerNoStatus (default: 0 New).
addressstringNoAddress text.
registered_atdateNoRegistration date.
last_visit_atdateNoLast visit date.
tagsstring[]NoTag list (stored as JSON).
notestringNoInternal memo.

Validation rules

  • name: required, string, max 64
  • name_kana: required, string, max 64
  • email: required, email, max 255
  • phone: required, string, max 20
  • status: integer, in: 0,1,2
  • registered_at, last_visit_at: date (YYYY-MM-DD)
  • tags: array of strings

Request example

{
  "name": "Hanako Yamada",
  "name_kana": "ヤマダ ハナコ",
  "email": "hanako.yamada@email.com",
  "phone": "090-1234-5678",
  "status": 0,
  "address": "Tokyo, Japan",
  "registered_at": "2024-03-01",
  "last_visit_at": null,
  "tags": [
    "family",
    "vip"
  ],
  "note": "Prefers kid-friendly activities."
}

Success response

{
  "status": 201,
  "message": "Created",
  "data": {
    "code": "CUS-001",
    "id": 12
  }
}

Validation error

{
  "status": 422,
  "message": "Validation failed",
  "data": {
    "errors": {
      "name": [
        "The name field is required."
      ],
      "email": [
        "The email must be a valid email address."
      ],
      "phone": [
        "The phone field is required."
      ]
    }
  }
}

Update customer

PUT/api/customers/{code}

Updates an existing customer.

Request example

{
  "name": "Hanako Yamada",
  "name_kana": "ヤマダ ハナコ",
  "email": "hanako.yamada@email.com",
  "phone": "090-1234-5678",
  "status": 1,
  "address": "Tokyo, Japan",
  "registered_at": "2024-03-01",
  "last_visit_at": "2024-03-20",
  "tags": [
    "family",
    "vip",
    "repeat"
  ],
  "note": "Updated note."
}

Success response

{
  "status": 200,
  "message": "OK",
  "data": {
    "code": "CUS-001"
  }
}

Delete customer

DELETE/api/customers/{code}

Deletes a customer (recommended: soft delete).

Success response

{
  "status": 200,
  "message": "Deleted",
  "data": {
    "code": "CUS-001",
    "deleted_at": "2026-01-13 10:00:00"
  }
}

Accommodation Search

TravelConnect API Docs — Accommodation Search

Accommodation Search (宿泊施設検索)

Route: /accommodations • Module API specification (based on FE + DB design)

Overview

  • UI screen: /accommodations (宿泊施設検索)
  • Goal: search accommodations with filters (dates, guests, budget, distance, meal plan, cancellation, stars, type, amenities) and manage favorites.
  • Auth: all endpoints require Authorization: Bearer <access_token>.
  • IDs: accommodations.id is integer (DB: int unsigned).

Enums

region

ValueLabel
1domestic
2international

type

ValueLabel
0hotel
1apartment
2aparthotel
3resort
4villa
5guesthouse
6ryokan

meal_plan

ValueLabel
0none
1breakfast
2dinner
3breakfast_and_dinner

cancel_policy

ValueLabel
0free_cancellation
1prepay_required

Endpoints

All requests must include: Authorization: Bearer <access_token>

Search accommodations (list)

GET/api/accommodations

Search accommodations with filters and pagination.

Query parameters

NameTypeRequiredDescription
qstringNoSearch keyword for name/location/area.
destinationstringNoAlias for destination input (same as q).
check_indate (YYYY-MM-DD)NoCheck-in date.
check_outdate (YYYY-MM-DD)NoCheck-out date.
adultsintegerNoNumber of adults.
childrenintegerNoNumber of children.
child_ages[]integer[]NoAge list for children (optional).
roomsintegerNoNumber of rooms.
price_minnumberNoMinimum price per night (JPY).
price_maxnumberNoMaximum price per night (JPY).
distance_area[]string[]NoDistance filter options (e.g., 中心部 / 周辺エリア / 郊外).
cancel_policyintegerNo0=free_cancellation, 1=prepay_required.
stars_minintegerNoMinimum stars (1-5).
type[]integer[]NoAccommodation types (0-6).
amenities[]string[]NoAmenity filter (matches accommodation_amenities.amenity).
meal_planintegerNo0=none, 1=breakfast, 2=dinner, 3=both.
sort_bystringNorecommended | price-low | price-high | rating | cashback
pageintegerNoPage number (default: 1).
per_pageintegerNoItems per page (default: 10, max: 100).

Validation

FieldRulesMessage (EN)
check_innullable|dateCheck-in must be a valid date.
check_outnullable|date|after:check_inCheck-out must be a date after check-in.
adultsnullable|integer|min:1|max:20Adults must be between 1 and 20.
childrennullable|integer|min:0|max:20Children must be between 0 and 20.
child_agesnullable|arrayChild ages must be an array.
child_ages.*integer|min:0|max:17Each child age must be between 0 and 17.
roomsnullable|integer|min:1|max:10Rooms must be between 1 and 10.
price_minnullable|numeric|min:0Minimum price must be a positive number.
price_maxnullable|numeric|gte:price_minMaximum price must be greater than or equal to minimum price.
cancel_policynullable|integer|in:0,1The selected cancel policy is invalid.
stars_minnullable|integer|min:1|max:5Stars must be between 1 and 5.
typenullable|arrayType must be an array.
type.*integer|in:0,1,2,3,4,5,6The selected type is invalid.
meal_plannullable|integer|in:0,1,2,3The selected meal plan is invalid.
sort_bynullable|string|in:recommended,price-low,price-high,rating,cashbackThe selected sort option is invalid.
pagenullable|integer|min:1Page must be at least 1.
per_pagenullable|integer|min:1|max:100Per page must be between 1 and 100.

Success response

{
  "status": 200,
  "message": "OK",
  "data": {
    "items": [
      {
        "id": 101,
        "name": "Tokyo Bay Resort",
        "location": "Tokyo, Japan",
        "area": "中心部",
        "region": {
          "value": 1,
          "label": "domestic"
        },
        "price_per_night": 18000.0,
        "currency": "JPY",
        "rating": 4.6,
        "review_count": 1284,
        "stars": 4,
        "type": {
          "value": 3,
          "label": "resort"
        },
        "image_url": "/tokyo-bay-resort.jpg",
        "max_guests": 4,
        "meal_plan": {
          "value": 1,
          "label": "breakfast"
        },
        "cancel_policy": {
          "value": 0,
          "label": "free_cancellation"
        },
        "distance_from_center": 1.8,
        "cashback": 900.0,
        "description": "Family-friendly resort near the bay.",
        "amenities": [
          "WiFi",
          "Parking",
          "Pool"
        ],
        "is_favorite": true
      }
    ],
    "pagination": {
      "page": 1,
      "per_page": 10,
      "total": 120,
      "total_pages": 12
    }
  }
}

Common errors

  • 401 Unauthorized
  • 422 Validation Error

Get accommodation detail

GET/api/accommodations/{id}

Get full details of an accommodation.

Validation

FieldRulesMessage (EN)
idrequired|integerAccommodation id must be an integer.

Success response

{
  "status": 200,
  "message": "OK",
  "data": {
    "accommodation": {
      "id": 101,
      "name": "Tokyo Bay Resort",
      "location": "Tokyo, Japan",
      "area": "中心部",
      "region": {
        "value": 1,
        "label": "domestic"
      },
      "price_per_night": 18000.0,
      "currency": "JPY",
      "rating": 4.6,
      "review_count": 1284,
      "stars": 4,
      "type": {
        "value": 3,
        "label": "resort"
      },
      "image_url": "/tokyo-bay-resort.jpg",
      "max_guests": 4,
      "meal_plan": {
        "value": 1,
        "label": "breakfast"
      },
      "cancel_policy": {
        "value": 0,
        "label": "free_cancellation"
      },
      "distance_from_center": 1.8,
      "cashback": 900.0,
      "description": "Family-friendly resort near the bay.",
      "amenities": [
        "WiFi",
        "Parking",
        "Pool"
      ],
      "is_favorite": true
    }
  }
}

Common errors

  • 401 Unauthorized
  • 422 Validation Error

Add to favorites

POST/api/accommodations/{id}/favorite

Mark an accommodation as favorite for the current account.

Validation

FieldRulesMessage (EN)
idrequired|integerAccommodation id must be an integer.

Success response

{
  "status": 200,
  "message": "OK",
  "data": {
    "is_favorite": true
  }
}

Common errors

  • 401 Unauthorized
  • 422 Validation Error

Remove from favorites

DELETE/api/accommodations/{id}/favorite

Remove an accommodation from favorites for the current account.

Validation

FieldRulesMessage (EN)
idrequired|integerAccommodation id must be an integer.

Success response

{
  "status": 400,
  "message": "Bad request.",
  "data": {
    "is_favorite": false
  }
}

Common errors

  • 401 Unauthorized
  • 422 Validation Error

List favorites

GET/api/accommodations/favorites

List favorite accommodations for the current account.

Query parameters

NameTypeRequiredDescription
pageintegerNoPage number (default: 1).
per_pageintegerNoItems per page (default: 10, max: 100).

Validation

FieldRulesMessage (EN)
pagenullable|integer|min:1Page must be at least 1.
per_pagenullable|integer|min:1|max:100Per page must be between 1 and 100.

Success response

{
  "status": 200,
  "message": "OK",
  "data": {
    "items": [
      {
        "id": 101,
        "name": "Tokyo Bay Resort",
        "location": "Tokyo, Japan",
        "area": "中心部",
        "region": {
          "value": 1,
          "label": "domestic"
        },
        "price_per_night": 18000.0,
        "currency": "JPY",
        "rating": 4.6,
        "review_count": 1284,
        "stars": 4,
        "type": {
          "value": 3,
          "label": "resort"
        },
        "image_url": "/tokyo-bay-resort.jpg",
        "max_guests": 4,
        "meal_plan": {
          "value": 1,
          "label": "breakfast"
        },
        "cancel_policy": {
          "value": 0,
          "label": "free_cancellation"
        },
        "distance_from_center": 1.8,
        "cashback": 900.0,
        "description": "Family-friendly resort near the bay.",
        "amenities": [
          "WiFi",
          "Parking",
          "Pool"
        ],
        "is_favorite": true
      }
    ],
    "pagination": {
      "page": 1,
      "per_page": 10,
      "total": 5,
      "total_pages": 1
    }
  }
}

Common errors

  • 401 Unauthorized
  • 422 Validation Error
Generated from FE routes and DB design sheets (accommodations, accommodation_amenities, accommodation_favorites).

Form Management

TravelConnect API Docs — Form Management

Form Management (フォーム管理)

Base URL: /api • All responses are wrapped as {"status": int, "message": string, "data": any} (no meta key).
Admin Auth: Authorization: Bearer <access_token> • Public form endpoints use token.
Template CSS source: accommodation_search_api_docs.html

Overview

  • UI module: /forms (フォーム管理)
  • Purpose: manage form templates and review submitted responses. Forms are also displayed inside the Project edit screen under the フォーム管理 tab.
  • Key concept: a Form is a template (FORM-xxx), and a Response is a submission for a specific project/customer.
  • Response wrapper: every endpoint returns {"status": int, "message": string, "data": any}.

Screens & routes

Form list (/forms)

  • Shows available form templates (e.g., Travel Requirements / Service Review).
  • Uses GET /api/forms.

Responses list (/forms/{formCode}/responses)

  • Shows responses for a given form (filters: project, customer, status, date).
  • Uses GET /api/forms/{formCode}/responses.

Project edit tab: フォーム管理 (/projects/{projectCode}/edit)

  • Displays the main forms with a URLをコピー action and a status chip (Answered / Not answered).
  • Uses GET /api/projects/{projectCode}/forms to fetch copy_url and response status.

Enums

form.type

ValueLabelNotes
1Travel requirements旅行要望フォーム
2Service reviewサービスレビュー

response.status

ValueLabelNotes
0Not answered未回答
1Answered回答済み

Endpoints

List forms

GET/api/forms

Admin endpoint. Returns form templates for Form Management screen.

Query parameters

NameTypeRequiredDescription
qstringNoKeyword search by code/name.
typeintegerNoFilter by form type (see enums).
is_activebooleanNoFilter active forms only.
pageintegerNoPage number (default: 1).
per_pageintegerNoItems per page (default: 20, max: 100).
sortstringNoSort key. Example: -updated_at.

Success response

{
  "status": 200,
  "message": "OK",
  "data": {
    "items": [
      {
        "id": 1,
        "code": "FORM-001",
        "name": "Travel Requirements Form",
        "name_ja": "旅行要望フォーム",
        "description": "Collect travel preferences before proposal creation.",
        "type": {
          "type": 1,
          "label": "Travel requirements"
        },
        "is_active": true,
        "created_at": "2024-01-10 09:00:00",
        "updated_at": "2024-01-20 18:00:00"
      },
      {
        "id": 2,
        "code": "FORM-002",
        "name": "Service Review Form",
        "name_ja": "サービスレビュー",
        "description": "Collect feedback after the trip / meeting.",
        "type": {
          "type": 2,
          "label": "Service review"
        },
        "is_active": true,
        "created_at": "2024-01-10 09:00:00",
        "updated_at": "2024-01-20 18:00:00"
      }
    ],
    "pagination": {
      "page": 1,
      "per_page": 20,
      "total": 2,
      "total_pages": 1
    }
  }
}

Get form detail

GET/api/forms/{formCode}

Admin endpoint. Returns a form definition and its fields.

Path parameters

NameTypeRequiredDescription
formCodestringYesForm code, e.g., FORM-001.

Success response

{
  "status": 200,
  "message": "OK",
  "data": {
    "id": 1,
    "code": "FORM-001",
    "name": "Travel Requirements Form",
    "name_ja": "旅行要望フォーム",
    "description": "Collect travel preferences before proposal creation.",
    "type": {
      "type": 1,
      "label": "Travel requirements"
    },
    "is_active": true,
    "fields": [
      {
        "key": "destination",
        "label": "Destination",
        "type": "text",
        "required": true,
        "options": null,
        "sort_order": 1
      },
      {
        "key": "travel_dates",
        "label": "Travel dates",
        "type": "date_range",
        "required": true,
        "options": null,
        "sort_order": 2
      },
      {
        "key": "number_of_people",
        "label": "Number of people",
        "type": "number",
        "required": true,
        "options": null,
        "sort_order": 3
      },
      {
        "key": "budget",
        "label": "Budget",
        "type": "select",
        "required": false,
        "options": [
          "<100,000 JPY",
          "100,000–300,000 JPY",
          "300,000–500,000 JPY",
          "500,000+ JPY"
        ],
        "sort_order": 4
      },
      {
        "key": "notes",
        "label": "Notes",
        "type": "textarea",
        "required": false,
        "options": null,
        "sort_order": 99
      }
    ],
    "created_at": "2024-01-10 09:00:00",
    "updated_at": "2024-01-20 18:00:00"
  }
}

List form responses

GET/api/forms/{formCode}/responses

Admin endpoint. Returns responses for a given form (used by /forms/{formCode}/responses UI).

Query parameters

NameTypeRequiredDescription
qstringNoKeyword search by project code or customer name/email.
project_codestringNoFilter by project code.
statusintegerNo0 not answered / 1 answered.
submitted_fromdateNoFilter by submitted date range (from).
submitted_todateNoFilter by submitted date range (to).
pageintegerNoPage number.
per_pageintegerNoItems per page (max 100).

Success response

{
  "status": 200,
  "message": "OK",
  "data": {
    "items": [
      {
        "id": 101,
        "form_code": "FORM-001",
        "project": {
          "id": 2,
          "code": "CASE-002",
          "destination": "Tokyo / Hakone"
        },
        "customer": {
          "id": 12,
          "code": "CUS-001",
          "name": "Hanako Yamada",
          "email": "hanako.yamada@email.com"
        },
        "status": {
          "status": 1,
          "label": "Answered"
        },
        "submitted_at": "2024-03-01 10:15:00",
        "updated_at": "2024-03-01 10:15:00"
      }
    ],
    "pagination": {
      "page": 1,
      "per_page": 20,
      "total": 1,
      "total_pages": 1
    }
  }
}

Get response detail

GET/api/forms/{formCode}/responses/{responseId}

Admin endpoint. Returns one response with normalized answers and metadata.

Path parameters

NameTypeRequiredDescription
formCodestringYesForm code.
responseIdintegerYesResponse ID.

Success response

{
  "status": 200,
  "message": "OK",
  "data": {
    "id": 101,
    "form": {
      "code": "FORM-001",
      "name": "Travel Requirements Form"
    },
    "project": {
      "id": 2,
      "code": "CASE-002",
      "destination": "Tokyo / Hakone"
    },
    "customer": {
      "id": 12,
      "code": "CUS-001",
      "name": "Hanako Yamada",
      "email": "hanako.yamada@email.com",
      "phone": "090-1234-5678"
    },
    "status": {
      "status": 1,
      "label": "Answered"
    },
    "answers": [
      {
        "key": "destination",
        "label": "Destination",
        "value": "Tokyo / Hakone"
      },
      {
        "key": "travel_dates",
        "label": "Travel dates",
        "value": {
          "start": "2024-03-15",
          "end": "2024-03-17"
        }
      },
      {
        "key": "number_of_people",
        "label": "Number of people",
        "value": 3
      },
      {
        "key": "budget",
        "label": "Budget",
        "value": "100,000–300,000 JPY"
      },
      {
        "key": "notes",
        "label": "Notes",
        "value": "Prefer kid-friendly activities."
      }
    ],
    "submitted_at": "2024-03-01 10:15:00",
    "created_at": "2024-03-01 10:15:00",
    "updated_at": "2024-03-01 10:15:00"
  }
}

Project form links & status

GET/api/projects/{projectCode}/forms

Admin endpoint. Used by Project edit screen (フォーム管理 tab) to display copy URLs and answer status.

Success response

{
  "status": 200,
  "message": "OK",
  "data": {
    "project": {
      "id": 2,
      "code": "CASE-002"
    },
    "items": [
      {
        "form_code": "FORM-001",
        "title": "Travel Requirements Form",
        "title_ja": "旅行要望フォーム",
        "description": "Collect travel preferences before proposal creation.",
        "status": {
          "status": 1,
          "label": "Answered"
        },
        "copy_url": "https://app.example.com/public/forms/FORM-001?token=abc123",
        "response_id": 101
      },
      {
        "form_code": "FORM-002",
        "title": "Service Review Form",
        "title_ja": "サービスレビュー",
        "description": "Feedback after the Zoom meeting / trip.",
        "status": {
          "status": 0,
          "label": "Not answered"
        },
        "copy_url": "https://app.example.com/public/forms/FORM-002?token=def456",
        "response_id": null
      }
    ]
  }
}

Public: get form definition

GET/api/public/forms/{formCode}

Public endpoint (no Bearer token). Requires a valid token query parameter. Returns fields for rendering the public form page.

Query parameters

NameTypeRequiredDescription
tokenstringYesShare token embedded in copy URL.

Success response

{
  "status": 200,
  "message": "OK",
  "data": {
    "form": {
      "code": "FORM-001",
      "name": "Travel Requirements Form",
      "description": "Collect travel preferences before proposal creation."
    },
    "project": {
      "code": "CASE-002"
    },
    "fields": [
      {
        "key": "destination",
        "label": "Destination",
        "type": "text",
        "required": true,
        "options": null,
        "sort_order": 1
      },
      {
        "key": "travel_dates",
        "label": "Travel dates",
        "type": "date_range",
        "required": true,
        "options": null,
        "sort_order": 2
      },
      {
        "key": "number_of_people",
        "label": "Number of people",
        "type": "number",
        "required": true,
        "options": null,
        "sort_order": 3
      },
      {
        "key": "budget",
        "label": "Budget",
        "type": "select",
        "required": false,
        "options": [
          "<100,000 JPY",
          "100,000–300,000 JPY",
          "300,000–500,000 JPY",
          "500,000+ JPY"
        ],
        "sort_order": 4
      },
      {
        "key": "notes",
        "label": "Notes",
        "type": "textarea",
        "required": false,
        "options": null,
        "sort_order": 99
      }
    ]
  }
}

Public: submit response

POST/api/public/forms/{formCode}/responses

Public endpoint. Requires token. Stores answers and marks the response as Answered.

Query parameters

NameTypeRequiredDescription
tokenstringYesShare token embedded in copy URL.

Request body

FieldTypeRequiredDescription
answersobjectYesKey-value object; keys must match form field keys.

Validation rules

  • token: required, string, must be a valid share token.
  • answers: required, object.
  • For required fields in the form definition, validate answers.<field_key> accordingly.
  • For number/date/date_range fields, validate type and format.

Request example

{
  "answers": {
    "destination": "Tokyo / Hakone",
    "travel_dates": {
      "start": "2024-03-15",
      "end": "2024-03-17"
    },
    "number_of_people": 3,
    "budget": "100,000–300,000 JPY",
    "notes": "Prefer kid-friendly activities."
  }
}

Success response

{
  "status": 201,
  "message": "Created",
  "data": {
    "id": 101,
    "form_code": "FORM-001",
    "status": {
      "status": 1,
      "label": "Answered"
    },
    "submitted_at": "2024-03-01 10:15:00"
  }
}

Validation error

{
  "status": 422,
  "message": "Validation failed",
  "data": {
    "errors": {
      "answers.destination": [
        "The destination field is required."
      ],
      "answers.number_of_people": [
        "The number_of_people must be an integer."
      ]
    }
  }
}

Project Management

TravelConnect API Docs — Project Management

TravelConnect API Docs — Project Management (案件管理)

Base URL: /api • Responses contain status, message, and data only (no meta key).

Authentication

All endpoints require an access token (Login API).

Authorization: Bearer <access_token>

Screens & tabs

Project List screen (/projects)

The list screen includes 4 summary cards (案件数 / 未提案 / 手配済み / 今月の売上). Use GET /api/projects/stats to fetch these numbers.

Project Edit screen has 3 tabs (same as the UI screenshot):

基本情報 (Basic Information) 提案書 (Proposals) フォーム管理 (Form Management)

Which APIs are used per tab

  • Basic Information: GET /api/projects/{code}, PUT /api/projects/{code}, PUT /api/projects/{code}/tags, PUT /api/projects/{code}/companions, PUT /api/projects/{code}/zoom-meeting, DELETE /api/projects/{code}/zoom-meeting
  • Proposals: GET /api/projects/{code}/proposals, POST /api/projects/{code}/proposals
  • Form Management: GET /api/projects/{code}/forms

UI route example: /cases/CASE-002/edit may be renamed to /projects/PRJ-002/edit depending on your final naming.

Get list screen statistics (cards)

GET/api/projects/stats

Used by the Project List screen to render 4 summary cards: 案件数 / 未提案 / 手配済み / 今月の売上.

AuthAuthorization: Bearer <access_token>

Query parameters

NameTypeRequiredDescription
qstringNoOptional search keyword (same meaning as list API).
groupstringNoOptional status-group tab filter: all, consultation, proposal, approved, booked.
status[]integer[]NoOptional multi-status filter. Example: status[]=3&status[]=4.
assigned_advisor_codestringNoScope stats to an assigned advisor (accounts.code).

Validation

FieldRulesMessage (EN)
groupnullable|string|in:all,consultation,proposal,approved,bookedThe selected group is invalid.
statusnullable|arrayStatus must be an array.
status.*integerThe selected status is invalid.
assigned_advisor_codenullable|string|max:50Assigned advisor code must not be greater than 50 characters.

Success response

{
  "status": 200,
  "message": "OK",
  "data": {
    "cards": {
      "total_projects": {
        "value": 9,
        "change_text": "+2 this week"
      },
      "unproposed": {
        "value": 6,
        "change_text": "-1 since yesterday"
      },
      "arranged": {
        "value": 1,
        "change_text": "+1 this month"
      },
      "sales_current_month": {
        "value": 2450000,
        "currency": "JPY",
        "formatted": "¥2,450,000",
        "change_text": "+15% vs last month"
      }
    }
  }
}

Common errors

  • 401 Unauthorized
  • 422 Validation Error

List projects

GET/api/projects

Project List screen (/projects). Returns a paginated list of projects with customer, assigned advisor, and summary fields.

AuthAuthorization: Bearer <access_token>

Query parameters

NameTypeRequiredDescription
qstringNoFree text search by project code or customer name/email/phone.
statusintegerNoProject status code (0–10).
assigned_advisor_codestringNoAssigned advisor account code (accounts.code).
pageintegerNoPage number (default: 1).
per_pageintegerNoItems per page (default: 20, max: 100).

Request body

None

Validation

FieldRulesMessage (EN)
per_pageinteger|min:1|max:100Per page must be between 1 and 100.
statusinteger|in:0,1,2,3,4,5,6,7,8,9,10The selected status is invalid.
assigned_advisor_codestring|max:50Assigned advisor code must not be greater than 50 characters.

Success response

{
  "status": 200,
  "message": "OK",
  "data": {
    "items": [
      {
        "id": 123,
        "code": "PRJ-001",
        "customer": {
          "id": 45,
          "code": "CUST-001",
          "name": "Hanako Yamada",
          "email": "yamada.hanako@example.com",
          "phone": "090-1234-5678"
        },
        "destination": "Okinawa",
        "travel_start_date": "2026-02-10",
        "travel_end_date": "2026-02-13",
        "number_of_people": 3,
        "budget_min": 200000,
        "budget_max": 300000,
        "status": {
          "status": 1,
          "label": "Scheduling not started"
        },
        "priority": {
          "priority": 0,
          "label": "High"
        },
        "assigned_advisor": {
          "id": 7,
          "code": "ADV-001",
          "name": "Taro Tanaka",
          "email": "tanaka@example.com"
        },
        "zoom_available": {
          "zoom_available": 1,
          "label": "Available"
        },
        "zoom_meeting": {
          "meeting_date": "2026-01-15T10:00:00+09:00"
        },
        "tags": [
          "family",
          "activity"
        ],
        "created_at": "2026-01-10T09:30:00+09:00",
        "updated_at": "2026-01-10T11:05:00+09:00"
      }
    ],
    "pagination": {
      "page": 1,
      "per_page": 20,
      "total": 128,
      "total_pages": 7
    }
  }
}

Common errors

  • 401 Unauthorized
  • 422 Validation Error

Get project detail (edit screen)

GET/api/projects/{code}

Project Edit screen (/projects/{code}/edit). The UI has 3 tabs: Basic Information, Proposals, and Form Management.

AuthAuthorization: Bearer <access_token>

Path parameters

NameTypeRequiredDescription
codestringYesProject code (projects.code), e.g. PRJ-001.

Request body

None

Validation

FieldRulesMessage (EN)
codestring|exists:projects,codeThe selected project does not exist.

Success response

{
  "status": 200,
  "message": "OK",
  "data": {
    "id": 123,
    "code": "PRJ-001",
    "customer": {
      "id": 45,
      "code": "CUST-001",
      "name": "Hanako Yamada",
      "name_kana": "ヤマダ ハナコ",
      "email": "yamada.hanako@example.com",
      "phone": "090-1234-5678"
    },
    "assigned_advisor": {
      "id": 7,
      "code": "ADV-001",
      "name": "Taro Tanaka",
      "email": "tanaka@example.com"
    },
    "travel_start_date": "2026-02-10",
    "travel_end_date": "2026-02-13",
    "destination": "Okinawa",
    "number_of_people": 3,
    "budget_min": 200000,
    "budget_max": 300000,
    "status": {
      "status": 1,
      "label": "Scheduling not started"
    },
    "priority": {
      "priority": 0,
      "label": "High"
    },
    "notes": "Family trip with kids-friendly hotel.",
    "zoom_available": {
      "zoom_available": 1,
      "label": "Available"
    },
    "zoom_meeting": {
      "meeting_date": "2026-01-15T10:00:00+09:00"
    },
    "tags": [
      "family",
      "activity"
    ],
    "companions": [
      {
        "id": 1,
        "name": "Taro Yamada",
        "age": 35,
        "relationship": 0,
        "relationship_label": "Spouse",
        "special_requests": ""
      },
      {
        "id": 2,
        "name": "Yui Yamada",
        "age": 8,
        "relationship": 1,
        "relationship_label": "Child",
        "special_requests": "Need child seat."
      }
    ],
    "created_at": "2026-01-10T09:30:00+09:00",
    "updated_at": "2026-01-10T11:05:00+09:00"
  }
}

Common errors

  • 401 Unauthorized
  • 404 Not Found
  • 422 Validation Error

List proposals for a project (Proposals tab)

GET/api/projects/{code}/proposals

Used in the Proposals tab on the Project Edit screen to show proposal cards.

AuthAuthorization: Bearer <access_token>

Path parameters

NameTypeRequiredDescription
codestringYesProject code (projects.code).

Query parameters

NameTypeRequiredDescription
statusintegerNoProposal status code (0–5).
pageintegerNoPage number (default: 1).
per_pageintegerNoItems per page (default: 20, max: 100).

Request body

None

Validation

FieldRulesMessage (EN)
statusinteger|in:0,1,2,3,4,5The selected proposal status is invalid.
per_pageinteger|min:1|max:100Per page must be between 1 and 100.

Success response

{
  "status": 200,
  "message": "OK",
  "data": {
    "items": [
      {
        "id": 901,
        "code": "PROP-001",
        "title": "Okinawa 3D2N Proposal",
        "total_price": 298000,
        "status": {
          "status": 2,
          "label": "In proposal"
        },
        "updated_at": "2026-01-12T12:30:00+09:00"
      }
    ],
    "pagination": {
      "page": 1,
      "per_page": 20,
      "total": 1,
      "total_pages": 1
    }
  }
}

Common errors

  • 401 Unauthorized
  • 404 Not Found
  • 422 Validation Error

Create a new proposal (Proposals tab)

POST/api/projects/{code}/proposals

Used by the “Create new proposal” button from the Proposals tab.

AuthAuthorization: Bearer <access_token>

Path parameters

NameTypeRequiredDescription
codestringYesProject code (projects.code).

Request body

{
  "title": "Okinawa 3D2N Proposal",
  "description": "Hotel + activities package.",
  "total_price": 298000
}

Validation

FieldRulesMessage (EN)
titlerequired|string|max:255Title field is required.
descriptionnullable|stringDescription must be a string.
total_pricenullable|numeric|min:0Total price must be at least 0.

Success response

{
  "status": 200,
  "message": "OK",
  "data": {
    "code": "PROP-001",
    "created_at": "2026-01-12T12:30:00+09:00"
  }
}

Common errors

  • 401 Unauthorized
  • 404 Not Found
  • 422 Validation Error

List form links & response status (Form Management tab)

GET/api/projects/{code}/forms

Used in the Form Management tab to show copyable URLs and whether each form has been answered.

AuthAuthorization: Bearer <access_token>

Path parameters

NameTypeRequiredDescription
codestringYesProject code (projects.code).

Request body

None

Validation

FieldRulesMessage (EN)
coderequired|string|exists:projects,codeThe selected project does not exist.

Success response

{
  "status": 200,
  "message": "OK",
  "data": [
    {
      "type": 0,
      "label": "Travel request",
      "share_url": "https://example.com/forms/travel-request/FRMRESP-001",
      "response_status": {
        "status": 1,
        "label": "Answered"
      },
      "submitted_at": "2026-01-11T09:15:00+09:00"
    },
    {
      "type": 1,
      "label": "Service review",
      "share_url": "https://example.com/forms/service-review/FRMRESP-002",
      "response_status": {
        "status": 0,
        "label": "Not answered"
      },
      "submitted_at": null
    }
  ]
}

Common errors

  • 401 Unauthorized
  • 404 Not Found

Create a new project

POST/api/projects

Creates a new project (/projects/new). Either provide customer_code, or provide customer payload to create/match.

AuthAuthorization: Bearer <access_token>

Request body

{
  "customer_code": "CUST-001",
  "customer": {
    "name": "Hanako Yamada",
    "name_kana": "ヤマダ ハナコ",
    "email": "yamada.hanako@example.com",
    "phone": "090-1234-5678",
    "address": "Tokyo, Japan"
  },
  "assigned_advisor_code": "ADV-001",
  "travel_start_date": "2026-02-10",
  "travel_end_date": "2026-02-13",
  "destination": "Okinawa",
  "number_of_people": 3,
  "budget_min": 200000,
  "budget_max": 300000,
  "status": 0,
  "priority": 1,
  "notes": "Family trip with kids-friendly hotel.",
  "zoom_available": 1,
  "zoom_meeting_date": "2026-01-15T10:00:00+09:00",
  "tags": [
    "family",
    "activity"
  ],
  "companions": [
    {
      "name": "Taro Yamada",
      "age": 35,
      "relationship": 0,
      "special_requests": ""
    },
    {
      "name": "Yui Yamada",
      "age": 8,
      "relationship": 1,
      "special_requests": "Need child seat."
    }
  ]
}

Validation

FieldRulesMessage (EN)
customer_codenullable|string|exists:customers,codeThe selected customer does not exist.
customernullable|objectThe customer field must be a valid object.
customer.namerequired_without:customer_code|string|max:100Customer name is required when customer_code is not provided.
customer.emailrequired_without:customer_code|email|max:255Customer email is required when customer_code is not provided.
customer.phonerequired_without:customer_code|string|max:20Customer phone is required when customer_code is not provided.
travel_start_daterequired|date_format:Y-m-dTravel start date does not match the format Y-m-d.
travel_end_daterequired|date_format:Y-m-d|after_or_equal:travel_start_dateTravel end date must be after or equal to travel_start_date.
number_of_peoplerequired|integer|min:1|max:255Number of people must be between 1 and 255.
destinationnullable|string|max:200Destination must not be greater than 200 characters.
budget_minnullable|numeric|min:0Budget min must be at least 0.
budget_maxnullable|numeric|gte:budget_minBudget max must be greater than or equal to budget_min.
statusrequired|integer|in:0,1,2,3,4,5,6,7,8,9,10The selected status is invalid.
priorityrequired|integer|in:0,1,2The selected priority is invalid.
assigned_advisor_codenullable|string|max:50Assigned advisor code must not be greater than 50 characters.
zoom_availablerequired|integer|in:0,1The selected zoom availability is invalid.
zoom_meeting_datenullable|dateZoom meeting date is not a valid date.
tagsnullable|arrayTags must be an array.
tags.*string|max:50Each tag must not be greater than 50 characters.
companionsnullable|arrayCompanions must be an array.
companions.*.namerequired|string|max:100Each companion name is required.
companions.*.agerequired|integer|min:0|max:150Each companion age must be between 0 and 150.
companions.*.relationshiprequired|integer|in:0,1,2,3,4,5,6Each companion relationship is invalid.
companions.*.special_requestsnullable|stringSpecial requests must be a string.

Success response

{
  "status": 200,
  "message": "OK",
  "data": {
    "code": "PRJ-001",
    "created_at": "2026-01-10T09:30:00+09:00"
  }
}

Common errors

  • 401 Unauthorized
  • 409 Conflict
  • 422 Validation Error

Update a project (Basic Information tab)

PUT/api/projects/{code}

Updates the main project fields for /projects/{code}/edit.

AuthAuthorization: Bearer <access_token>

Path parameters

NameTypeRequiredDescription
codestringYesProject code (projects.code).

Request body

{
  "assigned_advisor_code": "ADV-002",
  "travel_start_date": "2026-02-10",
  "travel_end_date": "2026-02-13",
  "destination": "Okinawa",
  "number_of_people": 3,
  "budget_min": 200000,
  "budget_max": 320000,
  "status": 2,
  "priority": 0,
  "notes": "Updated notes.",
  "zoom_available": 1,
  "zoom_meeting_date": "2026-01-16T16:00:00+09:00",
  "tags": [
    "family",
    "beach"
  ],
  "companions": [
    {
      "id": 1,
      "name": "Taro Yamada",
      "age": 35,
      "relationship": 0,
      "special_requests": ""
    },
    {
      "id": 2,
      "name": "Yui Yamada",
      "age": 8,
      "relationship": 1,
      "special_requests": "Need child seat."
    }
  ]
}

Validation

FieldRulesMessage (EN)
coderequired|string|exists:projects,codeThe selected project does not exist.
travel_start_daterequired|date_format:Y-m-dTravel start date does not match the format Y-m-d.
travel_end_daterequired|date_format:Y-m-d|after_or_equal:travel_start_dateTravel end date must be after or equal to travel_start_date.
number_of_peoplerequired|integer|min:1|max:255Number of people must be between 1 and 255.
destinationnullable|string|max:200Destination must not be greater than 200 characters.
budget_minnullable|numeric|min:0Budget min must be at least 0.
budget_maxnullable|numeric|gte:budget_minBudget max must be greater than or equal to budget_min.
statusrequired|integer|in:0,1,2,3,4,5,6,7,8,9,10The selected status is invalid.
priorityrequired|integer|in:0,1,2The selected priority is invalid.
assigned_advisor_codenullable|string|max:50Assigned advisor code must not be greater than 50 characters.
notesnullable|stringNotes must be a string.
zoom_availablerequired|integer|in:0,1The selected zoom availability is invalid.
zoom_meeting_datenullable|dateZoom meeting date is not a valid date.
tagsnullable|arrayTags must be an array.
tags.*string|max:50Each tag must not be greater than 50 characters.
companionsnullable|arrayCompanions must be an array.
companions.*.idnullable|integerCompanion id must be an integer.
companions.*.namerequired|string|max:100Each companion name is required.
companions.*.agerequired|integer|min:0|max:150Each companion age must be between 0 and 150.
companions.*.relationshiprequired|integer|in:0,1,2,3,4,5,6Each companion relationship is invalid.

Success response

{
  "status": 200,
  "message": "OK",
  "data": {
    "code": "PRJ-001",
    "updated_at": "2026-01-10T12:10:00+09:00"
  }
}

Common errors

  • 401 Unauthorized
  • 404 Not Found
  • 422 Validation Error

Replace project tags (Basic Information tab)

PUT/api/projects/{code}/tags

Replaces all tags for a project (syncs project_tags).

AuthAuthorization: Bearer <access_token>

Path parameters

NameTypeRequiredDescription
codestringYesProject code (projects.code).

Request body

{
  "tags": [
    "family",
    "beach"
  ]
}

Validation

FieldRulesMessage (EN)
tagsrequired|arrayTags field is required.
tags.*string|max:50Each tag must not be greater than 50 characters.

Success response

{
  "status": 200,
  "message": "OK",
  "data": {
    "code": "PRJ-001",
    "tags": [
      "family",
      "beach"
    ]
  }
}

Common errors

  • 401 Unauthorized
  • 404 Not Found
  • 422 Validation Error

Replace companions (Basic Information tab)

PUT/api/projects/{code}/companions

Replaces companion list for a project (creates/updates/deletes companions).

AuthAuthorization: Bearer <access_token>

Path parameters

NameTypeRequiredDescription
codestringYesProject code (projects.code).

Request body

{
  "companions": [
    {
      "id": 1,
      "name": "Taro Yamada",
      "age": 35,
      "relationship": 0,
      "special_requests": ""
    },
    {
      "name": "Yui Yamada",
      "age": 8,
      "relationship": 1,
      "special_requests": "Need child seat."
    }
  ]
}

Validation

FieldRulesMessage (EN)
companionsrequired|arrayCompanions field is required.
companions.*.idnullable|integerCompanion id must be an integer.
companions.*.namerequired|string|max:100Each companion name is required.
companions.*.agerequired|integer|min:0|max:150Each companion age must be between 0 and 150.
companions.*.relationshiprequired|integer|in:0,1,2,3,4,5,6Each companion relationship is invalid.
companions.*.special_requestsnullable|stringSpecial requests must be a string.

Success response

{
  "status": 200,
  "message": "OK",
  "data": {
    "code": "PRJ-001",
    "companions_count": 2
  }
}

Common errors

  • 401 Unauthorized
  • 404 Not Found
  • 422 Validation Error

Set Zoom meeting date (Basic Information tab)

PUT/api/projects/{code}/zoom-meeting

Creates or updates Zoom meeting for the project (zoom_meetings).

AuthAuthorization: Bearer <access_token>

Path parameters

NameTypeRequiredDescription
codestringYesProject code (projects.code).

Request body

{
  "meeting_date": "2026-01-16T16:00:00+09:00"
}

Validation

FieldRulesMessage (EN)
meeting_daterequired|dateMeeting date field is required.

Success response

{
  "status": 200,
  "message": "OK",
  "data": {
    "code": "PRJ-001",
    "zoom_meeting": {
      "meeting_date": "2026-01-16T16:00:00+09:00"
    }
  }
}

Common errors

  • 401 Unauthorized
  • 404 Not Found
  • 422 Validation Error

Delete Zoom meeting (Basic Information tab)

DELETE/api/projects/{code}/zoom-meeting

Deletes the Zoom meeting record for the project (if any).

AuthAuthorization: Bearer <access_token>

Path parameters

NameTypeRequiredDescription
codestringYesProject code (projects.code).

Request body

None

Validation

FieldRulesMessage (EN)
coderequired|string|exists:projects,codeThe selected project does not exist.

Success response

{
  "status": 200,
  "message": "OK",
  "data": {
    "code": "PRJ-001",
    "zoom_meeting": null
  }
}

Common errors

  • 401 Unauthorized
  • 404 Not Found

Reference (Enums)

Project status (projects.status)

CodeLabel (EN)
0Advisor selection pending
1Scheduling not started
2Scheduling in progress
3Schedule confirmed
4Proposal in progress
5Booking arranged
6Change processing
7Trip completed
8Review requested
9Review submitted
10Closed

Priority (projects.priority)

CodeLabel (EN)
0High
1Medium
2Low

Zoom availability (projects.zoom_available)

CodeLabel (EN)
0Not available
1Available

Form type (forms.type)

CodeLabel (EN)
0Travel request
1Service review

Form response status

CodeLabel (EN)
0Not answered
1Answered

Companion relationship (companions.relationship)

CodeLabel (EN)
0Spouse
1Child
2Parent
3Sibling
4Friend
5Colleague
6Other

Notes

  • code in the path refers to projects.code (e.g., PRJ-001).
  • Assigned advisor is returned as assigned_advisor object (no separate assigned_advisor_id field in list/detail responses).
  • Customer is returned as customer object (no separate customer_id field in list/detail responses).
  • status, priority, and zoom_available are returned as objects, e.g. {"status": 1, "label": "..."}.
  • number_of_people is used instead of adults/children.
  • /api/projects/{code}/forms returns shareable URLs and whether each form was submitted (answered).

Proposal Management

TravelConnect API Docs — Proposal Management

Proposal Management (提案管理)

Base URL: /api • All responses are wrapped as {"status": int, "message": string, "data": any} (no meta key).
Auth: Authorization: Bearer <access_token>

Overview

  • UI module: /proposals (提案管理)
  • Purpose: manage proposals linked to a project (案件) and track their lifecycle (Draft → Tentative → Proposing → Paid → Cancelled / Rejected).
  • Identifier: API uses proposal.code (e.g., PROP-001) in URLs. Internally, DB also has numeric id.
  • Relations: proposalsprojects (DB column name is case_id) and child tables proposal_accommodations, proposal_itinerary.
  • Dependencies (existing modules): project data is typically fetched from GET /api/projects/{code}; accommodation search is handled by /api/accommodations.

Screens & flows

Proposal List screen (/proposals)

  • Tabs: All Draft & Tentative Proposing Paid Cancelled / Rejected
  • Uses: GET /api/proposals with q and status filters.

New Proposal screen (/proposals/new?caseId=CASE-xxx)

  • Can be opened from multiple entry points:
    • /proposals/new?caseId=CASE-xxx (recommended: create from Project/案件 screen)
    • /proposals/new (standalone draft — UI may show a notice when not linked to a project)
  • Tabs (create/edit): 基本情報 宿泊施設 アクティビティ 旅程表 予算・プレビュー
  • Top actions: 下書き保存 (save as draft) / 保存 (save)
  • API usage:
    • Initial create: POST /api/proposals
    • Subsequent saves (each tab): PUT /api/proposals/{code}
    • Accommodation tab typically uses /api/accommodations to search, then writes to accommodations[] in proposal.
  • Optionally supports copy: /proposals/new?copyFrom=PROP-xxx (copy accommodations/itinerary from an existing proposal).

Proposal Detail screen (/proposals/{code})

  • Tabs: 基本情報 宿泊施設 アクティビティ 旅程表 予算・プレビュー
  • Uses: GET /api/proposals/{code}, PUT /api/proposals/{code}, PUT /api/proposals/{code}/status.

Create/Edit Tabs – APIs & parameters

The create/edit UI is organized into 5 tabs (基本情報, 宿泊施設, アクティビティ, 旅程表, 予算・プレビュー). This section explains which APIs are used by each tab and which parameters are saved. All responses follow { status, message, data }.

1) 基本情報 (Basic info)

  • Load
    • GET /api/projects/{project_code} — load the project (案件) and embedded customer and companions.
    • GET /api/proposals/{code} — load the proposal draft (title/description/status/total_price) when editing.
  • Editable fields (stored in proposals)
    • title (required) — proposal title (提案タイトル).
    • description (optional) — free text notes for the proposal.
    • status.value — draft/proposing/etc. (see Enums).
    • total_price (optional) — may be updated in the Budget tab.
  • Save
    • Create: POST /api/proposals
    • Update: PUT /api/proposals/{code}
Request body example (create)
{
  "project_code": "CASE-002",
  "title": "Tokyo / Hakone 3 days trip proposal",
  "description": "Draft proposal for the customer.",
  "status": 0
}
Request body example (update basic info)
{
  "title": "Tokyo / Hakone 3 days trip proposal (rev.1)",
  "description": "Updated notes...",
  "status": 0
}

If your UI allows editing project fields (destination, travel dates, number of people, budget range), update them via PUT /api/projects/{project_code} (Project/案件管理 module), because the Proposal table does not store those fields.

2) 宿泊施設 (Accommodations)

  • Search accommodations: GET /api/accommodations (same filters as Accommodation Search module).
  • Save selected accommodations: PUT /api/proposals/{code} with accommodations array (recommended: full replace).
Request body (update accommodations)
{
  "accommodations": [
    {
      "accommodation_id": 123,
      "check_in_date": "2026-03-15",
      "check_out_date": "2026-03-17",
      "room_count": 1,
      "price_per_night": 25000,
      "total_price": 50000,
      "notes": "Near station, breakfast included."
    }
  ]
}

Persisted to proposal_accommodations. Use server-side validation to ensure dates are within the project travel period.

3) アクティビティ (Activities)

  • Search activities: GET /api/activities (provider/external search; not stored as a master table in the provided DB design).
  • Save selected activities:
    • Recommended: save as itinerary items (type = Activity) via PUT /api/proposals/{code} with itinerary_items.
    • Alternative: save as activities array snapshot (if you add a DB table later).
Request body (add activities as itinerary items)
{
  "itinerary_items": [
    {
      "activity_date": "2026-03-15",
      "start_time": "10:00",
      "end_time": "12:00",
      "title": "Tokyo Skytree",
      "location": "Tokyo",
      "type": 3,
      "description": "Category: sightseeing, Duration: 120 min, Price: 2100 JPY"
    }
  ]
}

DB note: the current proposal_itinerary.type enum in the DB design includes 0: transport, 1: accommodation, 2: meal. The UI has a dedicated Activities tab, so it is recommended to extend the enum to include 3: activity.

4) 旅程表 (Itinerary)

  • Load: GET /api/proposals/{code} returns itinerary items.
  • Save: PUT /api/proposals/{code} with itinerary_items array.
itinerary_items fields (based on proposal_itinerary)
FieldTypeRequiredDescription
activity_datedateYesWhich day this item belongs to.
start_timetimeNoStart time (optional).
end_timetimeNoEnd time (optional).
titlestringNoItem title.
descriptionstringNoDetails/notes.
locationstringNoLocation text.
typeintegerYes0 transport / 1 accommodation / 2 meal (recommend adding 3 activity).
sort_orderintegerNoOrder within the same day (default 0).

5) 予算・プレビュー (Budget & Preview)

  • Budget summary: GET /api/proposals/{code}/budget (optional; server-side calculation).
  • Persist total: PUT /api/proposals/{code} with total_price.
  • Preview: GET /api/proposals/{code}/preview returns a preview URL or HTML.
Request body (update total price)
{
  "total_price": 2450000
}

Data models

Proposal

FieldTypeDescription
idintegerInternal numeric ID.
codestringPublic identifier (e.g., PROP-001).
projectobjectProject (案件) summary object.
customerobjectCustomer summary object (from project).
assigned_advisorobjectAssigned advisor (from project).
titlestringProposal title.
total_pricenumberTotal price (DECIMAL).
statusobjectStatus object: {status:int,label:string}.
descriptionstring|nullNotes / description.
accommodationsarraySelected accommodations (optional, when requested).
itineraryarrayItinerary items (optional, when requested).
created_atstringTimestamp.
updated_atstringTimestamp.

ProposalAccommodation

FieldTypeDescription
idintegerInternal ID.
accommodationobjectAccommodation summary object.
check_in_datestringYYYY-MM-DD
check_out_datestringYYYY-MM-DD
room_countinteger|nullRooms requested.
price_per_nightnumber|nullOptional (for preview).
total_pricenumber|nullOptional (for preview).
notesstring|nullNotes.

ProposalItineraryItem

FieldTypeDescription
idintegerInternal ID.
activity_datestringYYYY-MM-DD
start_timestring|nullHH:MM:SS
end_timestring|nullHH:MM:SS
titlestring|nullTitle.
descriptionstring|nullDescription.
locationstring|nullLocation.
typeobjectType object: {type:int,label:string}.
sort_orderintegerOrdering in the day.

Enums

proposal.status

ValueLabelNotes
0Draft下書き
1Tentative booking仮予約
2Proposing提案中
3Paid決済完了
4Cancelledキャンセル
5Rejected不採用 (UI may show as ボツ)

proposal_itinerary.type

ValueLabelNotes
0Transport移動
1Accommodation宿泊
2Meal食事

Endpoints

List proposals

GET/api/proposals

Returns proposals with project/customer/advisor summary info, plus tab summary counts.

Query parameters

NameTypeRequiredDescription
qstringNoKeyword search by proposal code/title/customer name.
statusintegerNoFilter by proposal status value.
project_codestringNoFilter by project (案件) code, e.g., CASE-002.
customer_codestringNoFilter by customer code.
assigned_advisor_codestringNoFilter by assigned advisor code.
pageintegerNoPage number (default: 1).
per_pageintegerNoItems per page (default: 15, max: 100).
sortstringNoSort key. Example: -updated_at (desc), code (asc).

Response (success)

{
  "status": 200,
  "message": "OK",
  "data": {
    "items": [
      {
        "id": 1,
        "code": "PROP-001",
        "project": {
          "id": 2,
          "code": "CASE-002",
          "destination": "Okinawa",
          "travel_start_date": "2024-05-10",
          "travel_end_date": "2024-05-13",
          "number_of_people": 2
        },
        "customer": {
          "id": 12,
          "code": "CUS-001",
          "name": "Hanako Yamada",
          "email": "hanako.yamada@email.com",
          "phone": "090-1234-5678"
        },
        "assigned_advisor": {
          "id": 7,
          "code": "ADV-001",
          "name": "Taro Tanaka",
          "email": "tanaka@example.com"
        },
        "title": "Okinawa resort plan (draft)",
        "total_price": 150000.0,
        "status": {
          "status": 0,
          "label": "Draft"
        },
        "description": "First draft proposal.",
        "created_at": "2024-01-15 10:00:00",
        "updated_at": "2024-01-20 09:12:00"
      }
    ],
    "pagination": {
      "page": 1,
      "per_page": 15,
      "total": 1,
      "total_pages": 1
    },
    "summary": {
      "all": 13,
      "draft": 6,
      "active": 6,
      "completed": 1,
      "archived": 0
    }
  }
}

Create proposal

POST/api/proposals

Creates a proposal for a given project (案件). Can optionally copy from an existing proposal.

Request body

FieldTypeRequiredDescription
project_codestringYesProject (案件) code.
titlestringYesMax 255 characters.
descriptionstringNoOptional notes.
statusintegerNoDefault: 0 (Draft).
total_pricenumberNoTotal price (DECIMAL).
copy_from_codestringNoIf provided, system copies accommodations/itinerary from that proposal.
accommodationsarrayNoList of accommodations to attach (see below).
itineraryarrayNoList of itinerary items to attach (see below).

Validation rules

  • project_code: required, string, max 20, must exist in projects.
  • title: required, string, max 255.
  • status: optional, integer, in [0..5].
  • accommodations.*.accommodation_id: required if accommodations present, integer, must exist.
  • accommodations.*.check_in_date: required if present, date (YYYY-MM-DD).
  • accommodations.*.check_out_date: required if present, date (YYYY-MM-DD), must be after check_in_date.
  • itinerary.*.activity_date: required if present, date.
  • itinerary.*.type: required if present, integer, in [0..2].

Request example

{
  "project_code": "CASE-002",
  "title": "Okinawa resort plan",
  "description": "Draft proposal for the customer.",
  "status": 0,
  "total_price": 150000.0,
  "accommodations": [
    {
      "accommodation_id": 101,
      "check_in_date": "2024-05-10",
      "check_out_date": "2024-05-12",
      "room_count": 1,
      "price_per_night": 18000.0,
      "total_price": 36000.0,
      "notes": "Ocean view preferred."
    }
  ],
  "itinerary": [
    {
      "activity_date": "2024-05-10",
      "start_time": "10:00:00",
      "end_time": "12:00:00",
      "title": "Arrive at airport",
      "description": "Pick up the rental car.",
      "location": "Naha Airport",
      "type": 0,
      "sort_order": 1
    }
  ]
}

Response (success)

{
  "status": 201,
  "message": "Created",
  "data": {
    "id": 8,
    "code": "PROP-007",
    "project": {
      "id": 2,
      "code": "CASE-002",
      "destination": "Okinawa",
      "travel_start_date": "2024-05-10",
      "travel_end_date": "2024-05-13",
      "number_of_people": 2
    },
    "customer": {
      "id": 12,
      "code": "CUS-001",
      "name": "Hanako Yamada",
      "email": "hanako.yamada@email.com",
      "phone": "090-1234-5678"
    },
    "assigned_advisor": {
      "id": 7,
      "code": "ADV-001",
      "name": "Taro Tanaka",
      "email": "tanaka@example.com"
    },
    "title": "Okinawa resort plan",
    "total_price": 150000.0,
    "status": {
      "status": 0,
      "label": "Draft"
    },
    "description": "Draft proposal for the customer.",
    "accommodations": [
      {
        "id": 21,
        "accommodation": {
          "id": 101,
          "name": "Hakone Onsen Resort",
          "location": "Hakone",
          "price_per_night": 18000.0,
          "currency": "JPY"
        },
        "check_in_date": "2024-05-10",
        "check_out_date": "2024-05-12",
        "room_count": 1,
        "price_per_night": 18000.0,
        "total_price": 36000.0,
        "notes": "Ocean view preferred."
      }
    ],
    "itinerary": [
      {
        "id": 55,
        "activity_date": "2024-05-10",
        "start_time": "10:00:00",
        "end_time": "12:00:00",
        "title": "Arrive at airport",
        "description": "Pick up the rental car.",
        "location": "Naha Airport",
        "type": {
          "type": 0,
          "label": "Transport"
        },
        "sort_order": 1
      }
    ],
    "created_at": "2024-01-25 14:30:00",
    "updated_at": "2024-01-25 14:30:00"
  }
}

Response (validation error)

{
  "status": 422,
  "message": "Validation failed",
  "data": {
    "errors": {
      "project_code": [
        "The project_code field is required."
      ],
      "title": [
        "The title field is required."
      ]
    }
  }
}

Get proposal detail

GET/api/proposals/{code}

Returns a proposal. By default, includes accommodations and itinerary arrays.

Path parameters

NameTypeRequiredDescription
codestringYesProposal code (e.g., PROP-001).

Response (success)

{
  "status": 200,
  "message": "OK",
  "data": {
    "id": 8,
    "code": "PROP-007",
    "project": {
      "id": 2,
      "code": "CASE-002",
      "destination": "Okinawa",
      "travel_start_date": "2024-05-10",
      "travel_end_date": "2024-05-13",
      "number_of_people": 2
    },
    "customer": {
      "id": 12,
      "code": "CUS-001",
      "name": "Hanako Yamada",
      "email": "hanako.yamada@email.com",
      "phone": "090-1234-5678"
    },
    "assigned_advisor": {
      "id": 7,
      "code": "ADV-001",
      "name": "Taro Tanaka",
      "email": "tanaka@example.com"
    },
    "title": "Okinawa resort plan",
    "total_price": 150000.0,
    "status": {
      "status": 0,
      "label": "Draft"
    },
    "description": "Draft proposal for the customer.",
    "accommodations": [
      {
        "id": 21,
        "accommodation": {
          "id": 101,
          "name": "Hakone Onsen Resort",
          "location": "Hakone",
          "price_per_night": 18000.0,
          "currency": "JPY"
        },
        "check_in_date": "2024-05-10",
        "check_out_date": "2024-05-12",
        "room_count": 1,
        "price_per_night": 18000.0,
        "total_price": 36000.0,
        "notes": "Ocean view preferred."
      }
    ],
    "itinerary": [
      {
        "id": 55,
        "activity_date": "2024-05-10",
        "start_time": "10:00:00",
        "end_time": "12:00:00",
        "title": "Arrive at airport",
        "description": "Pick up the rental car.",
        "location": "Naha Airport",
        "type": {
          "type": 0,
          "label": "Transport"
        },
        "sort_order": 1
      }
    ],
    "created_at": "2024-01-25 14:30:00",
    "updated_at": "2024-01-25 14:30:00"
  }
}

Update proposal

PUT/api/proposals/{code}

Updates proposal fields and nested accommodations/itinerary. This endpoint is used by the Save button.

Request body

Same as POST /api/proposals, but fields are optional. Provide arrays to replace current lists.

Validation rules

  • title: optional, string, max 255.
  • total_price: optional, numeric.
  • accommodations: optional, array; if provided, replaces all existing accommodations for the proposal.
  • itinerary: optional, array; if provided, replaces all existing itinerary items for the proposal.

Response (success)

{
  "status": 200,
  "message": "OK",
  "data": {
    "id": 8,
    "code": "PROP-007",
    "project": {
      "id": 2,
      "code": "CASE-002",
      "destination": "Okinawa",
      "travel_start_date": "2024-05-10",
      "travel_end_date": "2024-05-13",
      "number_of_people": 2
    },
    "customer": {
      "id": 12,
      "code": "CUS-001",
      "name": "Hanako Yamada",
      "email": "hanako.yamada@email.com",
      "phone": "090-1234-5678"
    },
    "assigned_advisor": {
      "id": 7,
      "code": "ADV-001",
      "name": "Taro Tanaka",
      "email": "tanaka@example.com"
    },
    "title": "Okinawa resort plan",
    "total_price": 150000.0,
    "status": {
      "status": 0,
      "label": "Draft"
    },
    "description": "Draft proposal for the customer.",
    "accommodations": [
      {
        "id": 21,
        "accommodation": {
          "id": 101,
          "name": "Hakone Onsen Resort",
          "location": "Hakone",
          "price_per_night": 18000.0,
          "currency": "JPY"
        },
        "check_in_date": "2024-05-10",
        "check_out_date": "2024-05-12",
        "room_count": 1,
        "price_per_night": 18000.0,
        "total_price": 36000.0,
        "notes": "Ocean view preferred."
      }
    ],
    "itinerary": [
      {
        "id": 55,
        "activity_date": "2024-05-10",
        "start_time": "10:00:00",
        "end_time": "12:00:00",
        "title": "Arrive at airport",
        "description": "Pick up the rental car.",
        "location": "Naha Airport",
        "type": {
          "type": 0,
          "label": "Transport"
        },
        "sort_order": 1
      }
    ],
    "created_at": "2024-01-25 14:30:00",
    "updated_at": "2024-01-25 14:30:00"
  }
}

Update proposal status

PUT/api/proposals/{code}/status

Updates proposal status following the UI flow. For example, Draft → Tentative, Tentative → Proposing, Paid → Cancelled, etc.

Request body

FieldTypeRequiredDescription
statusintegerYesNew status value (0..5).
notestringNoOptional note for auditing.

Allowed transitions (recommended)

FromToManual?Notes
Draft (0)Tentative (1), Proposing (2), Rejected (5)YesOperator/advisor can move forward or reject.
Tentative (1)Proposing (2), Rejected (5)YesConfirm before sending proposal.
Proposing (2)Paid (3)NoShould switch automatically after payment confirmation.
Paid (3)Cancelled (4)YesCancel when necessary.
Cancelled (4)--Terminal.
Rejected (5)--Terminal.

Request example

{
  "status": 1,
  "note": "Customer asked to hold rooms tentatively."
}

Response (success)

{
  "status": 200,
  "message": "OK",
  "data": {
    "code": "PROP-007",
    "status": {
      "status": 1,
      "label": "Tentative booking"
    },
    "updated_at": "2024-01-26 09:00:00"
  }
}

Response (validation error)

{
  "status": 422,
  "message": "Validation failed",
  "data": {
    "errors": {
      "project_code": [
        "The project_code field is required."
      ],
      "title": [
        "The title field is required."
      ]
    }
  }
}
GET/api/activities

Search activities for the Activities tab. Activities may come from an external provider; store chosen items in the itinerary or snapshot.

Query parameters

NameTypeRequiredDescription
qstringNoKeyword search (name/location/category).
destinationstringNoDestination filter (e.g., Tokyo, Hakone).
categorystringNoActivity category (e.g., sightseeing, experience).
price_minnumberNoMinimum price.
price_maxnumberNoMaximum price.
pageintegerNoPage number.
per_pageintegerNoItems per page (max 100).

Success response

{
  "status": 200,
  "message": "OK",
  "data": {
    "items": [
      {
        "id": 1,
        "name": "Tokyo Skytree",
        "category": "sightseeing",
        "duration_minutes": 120,
        "price": 2100,
        "currency": "JPY",
        "location": "Tokyo"
      }
    ],
    "page": 1,
    "per_page": 20,
    "total": 1
  }
}

Get budget summary

GET/api/proposals/{code}/budget

Returns a computed cost breakdown from selected accommodations and itinerary items. This endpoint is optional; the UI can also compute totals client-side.

Success response

{
  "status": 200,
  "message": "OK",
  "data": {
    "currency": "JPY",
    "accommodation_total": 50000,
    "activity_total": 4200,
    "transport_total": 15000,
    "meal_total": 8000,
    "grand_total": 77200
  }
}

Get proposal preview

GET/api/proposals/{code}/preview

Generates or returns a preview of the proposal (for 予算・プレビュー tab). You can return an HTML string or a temporary URL.

Success response

{
  "status": 200,
  "message": "OK",
  "data": {
    "preview_url": "https://example.com/storage/previews/PROP-001.html",
    "expires_at": "2026-03-01T12:00:00Z"
  }
}