Auth API
TravelConnect API Docs — Authentication
1. Base rules
| Item | Rule |
|---|---|
| Base URL | Environment-dependent, e.g. https://api.example.com |
| Content-Type | application/json |
| Timezone | API 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
| Field | Type | Required | Validation | Description |
|---|---|---|---|---|
identifier | string | Yes | max 255 | Email or account code (accounts.code) |
password | string | Yes | min 8, max 255 | Matches 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
| Status | error.code | message |
|---|---|---|
| 400 | VALIDATION_ERROR | “Invalid input.” |
| 401 | INVALID_CREDENTIALS | “Invalid email/user code or password.” |
| 403 | ACCOUNT_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
| Status | error.code | message |
|---|---|---|
| 401 | UNAUTHENTICATED | “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
| Field | Type | Required | Validation | Description |
|---|---|---|---|---|
email | string | Yes | email, max 255 | Matches accounts.email |
name | string | Yes | max 100 | Matches accounts.name |
phone | string | Yes | max 20 | Matches account_profiles.phone (if profile exists) |
new_password | string | Yes | min 8, max 255 | New password |
new_password_confirmation | string | Yes | same:new_password | Confirmation |
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
| Status | error.code | message |
|---|---|---|
| 400 | VALIDATION_ERROR | “Invalid input.” |
| 403 | ACCOUNT_DISABLED | “Account is disabled.” |
| 404 | USER_NOT_FOUND | “User not found.” |
| 422 | IDENTITY_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
| Code | Meaning | Typical status |
|---|---|---|
VALIDATION_ERROR | Request validation failed | 400 |
INVALID_CREDENTIALS | Identifier/password mismatch | 401 |
UNAUTHENTICATED | Missing/invalid token | 401 |
ACCOUNT_DISABLED | Account status is not active | 403 |
USER_NOT_FOUND | User does not exist | 404 |
IDENTITY_VERIFICATION_FAILED | Identity check failed for reset password | 422 |
Account Management
TravelConnect API Docs — Account Management
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
List accounts. The UI provides text search and role tabs. No advisor-ids filter.
3.1 Query parameters
| Name | Type | Required | Validation | Description |
|---|---|---|---|---|
q | string | No | max:100 | Search by code, name, or email |
role | integer | No | in:1,2,3 | Filter by role |
page | integer | No | min:1 | Page number |
per_page | integer | No | min:1, max:100 | Items 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
| Status | error.code | message |
|---|---|---|
| 400 | VALIDATION_ERROR | Invalid input. |
| 401 | UNAUTHENTICATED | Missing or invalid token. |
| 403 | FORBIDDEN | You do not have permission to perform this action. |
4. New Account (新規アカウント登録) — /accounts/new
Create a new account.
4.1 Request body
| Name | Type | Required | Validation | Description |
|---|---|---|---|---|
code | string | Yes | max:20 | User ID (e.g., AS-001, ADV-001) |
name | string | Yes | max:255 | Display name |
email | string | Yes | email, max:255, unique:accounts.email | |
password | string | Yes | min:8, max:255 | Password |
password_confirmation | string | Yes | same:password | Confirm password |
role | integer | Yes | in:1,2,3 | Role |
department | integer | No | - | Department |
status | integer | No | in:0,1 | Status (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 a single account and role-specific relations.
5.1 Path parameters
| Name | Type | Required | Validation | Description |
|---|---|---|---|---|
id | integer | Yes | exists:accounts,id | Account 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 (アカウント編集 保存)
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.
| Name | Type | Required | Validation | Description |
|---|---|---|---|---|
code | string | Yes | max:20 | User ID |
name | string | Yes | max:255 | Name |
email | string | Yes | email, max:255, unique:accounts.email,<id> | |
role | integer | Yes | in:1,2,3 | Role |
department | integer | No | - | Department |
status | integer | No | in:0,1 | Status |
new_password | string | No | min:8, max:255 | New password |
new_password_confirmation | string | No | same:new_password | Confirm 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 (アカウント削除)
Soft delete is recommended (set deleted_at).
7.1 Success response (200)
{
"status": 200,
"message": "OK",
"data": {
"deleted": true
}
}
Changelog
- Fix: Removed
advisor_idsquery/filter and removed duplicated keys (advisor_ids+assigned_advisors). Replaced with a singlemanaged_advisorsarray for role=2 (AS), based on DB mapping.
Customer Management
Customer Management (顧客管理)
/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
| Value | Label | Notes |
|---|---|---|
0 | New | 新規 |
1 | Active | アクティブ |
2 | Dormant | 休眠 |
Endpoints
List customers
Returns customers for the list screen. Supports keyword search, filters, pagination, and sorting.
Query parameters
| Name | Type | Required | Description |
|---|---|---|---|
q | string | No | Search by code / name / email / phone. |
status | integer | No | Status filter (0/1/2). |
tag | string | No | Filter by a single tag. |
registered_from | date | No | Registered date (from). |
registered_to | date | No | Registered date (to). |
last_visit_from | date | No | Last visit date (from). |
last_visit_to | date | No | Last visit date (to). |
page | integer | No | Page number (default 1). |
per_page | integer | No | Items per page (default 20, max 100). |
sort | string | No | Sort 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
Used by the edit screen to load a customer.
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
code | string | Yes | Customer 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
Creates a new customer. code should be generated by the server.
Request body
| Field | Type | Required | Description |
|---|---|---|---|
name | string | Yes | Customer name. |
name_kana | string | Yes | Name in kana. |
email | string | Yes | Email address. |
phone | string | Yes | Phone number. |
status | integer | No | Status (default: 0 New). |
address | string | No | Address text. |
registered_at | date | No | Registration date. |
last_visit_at | date | No | Last visit date. |
tags | string[] | No | Tag list (stored as JSON). |
note | string | No | Internal memo. |
Validation rules
name: required, string, max 64name_kana: required, string, max 64email: required, email, max 255phone: required, string, max 20status: integer, in: 0,1,2registered_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
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
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
Accommodation Search (宿泊施設検索)
/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.idis integer (DB: int unsigned).
Enums
region
| Value | Label |
|---|---|
| 1 | domestic |
| 2 | international |
type
| Value | Label |
|---|---|
| 0 | hotel |
| 1 | apartment |
| 2 | aparthotel |
| 3 | resort |
| 4 | villa |
| 5 | guesthouse |
| 6 | ryokan |
meal_plan
| Value | Label |
|---|---|
| 0 | none |
| 1 | breakfast |
| 2 | dinner |
| 3 | breakfast_and_dinner |
cancel_policy
| Value | Label |
|---|---|
| 0 | free_cancellation |
| 1 | prepay_required |
Endpoints
All requests must include: Authorization: Bearer <access_token>
Search accommodations (list)
Search accommodations with filters and pagination.
Query parameters
| Name | Type | Required | Description |
|---|---|---|---|
q | string | No | Search keyword for name/location/area. |
destination | string | No | Alias for destination input (same as q). |
check_in | date (YYYY-MM-DD) | No | Check-in date. |
check_out | date (YYYY-MM-DD) | No | Check-out date. |
adults | integer | No | Number of adults. |
children | integer | No | Number of children. |
child_ages[] | integer[] | No | Age list for children (optional). |
rooms | integer | No | Number of rooms. |
price_min | number | No | Minimum price per night (JPY). |
price_max | number | No | Maximum price per night (JPY). |
distance_area[] | string[] | No | Distance filter options (e.g., 中心部 / 周辺エリア / 郊外). |
cancel_policy | integer | No | 0=free_cancellation, 1=prepay_required. |
stars_min | integer | No | Minimum stars (1-5). |
type[] | integer[] | No | Accommodation types (0-6). |
amenities[] | string[] | No | Amenity filter (matches accommodation_amenities.amenity). |
meal_plan | integer | No | 0=none, 1=breakfast, 2=dinner, 3=both. |
sort_by | string | No | recommended | price-low | price-high | rating | cashback |
page | integer | No | Page number (default: 1). |
per_page | integer | No | Items per page (default: 10, max: 100). |
Validation
| Field | Rules | Message (EN) |
|---|---|---|
check_in | nullable|date | Check-in must be a valid date. |
check_out | nullable|date|after:check_in | Check-out must be a date after check-in. |
adults | nullable|integer|min:1|max:20 | Adults must be between 1 and 20. |
children | nullable|integer|min:0|max:20 | Children must be between 0 and 20. |
child_ages | nullable|array | Child ages must be an array. |
child_ages.* | integer|min:0|max:17 | Each child age must be between 0 and 17. |
rooms | nullable|integer|min:1|max:10 | Rooms must be between 1 and 10. |
price_min | nullable|numeric|min:0 | Minimum price must be a positive number. |
price_max | nullable|numeric|gte:price_min | Maximum price must be greater than or equal to minimum price. |
cancel_policy | nullable|integer|in:0,1 | The selected cancel policy is invalid. |
stars_min | nullable|integer|min:1|max:5 | Stars must be between 1 and 5. |
type | nullable|array | Type must be an array. |
type.* | integer|in:0,1,2,3,4,5,6 | The selected type is invalid. |
meal_plan | nullable|integer|in:0,1,2,3 | The selected meal plan is invalid. |
sort_by | nullable|string|in:recommended,price-low,price-high,rating,cashback | The selected sort option is invalid. |
page | nullable|integer|min:1 | Page must be at least 1. |
per_page | nullable|integer|min:1|max:100 | Per 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 full details of an accommodation.
Validation
| Field | Rules | Message (EN) |
|---|---|---|
id | required|integer | Accommodation 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
Mark an accommodation as favorite for the current account.
Validation
| Field | Rules | Message (EN) |
|---|---|---|
id | required|integer | Accommodation 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
Remove an accommodation from favorites for the current account.
Validation
| Field | Rules | Message (EN) |
|---|---|---|
id | required|integer | Accommodation 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
List favorite accommodations for the current account.
Query parameters
| Name | Type | Required | Description |
|---|---|---|---|
page | integer | No | Page number (default: 1). |
per_page | integer | No | Items per page (default: 10, max: 100). |
Validation
| Field | Rules | Message (EN) |
|---|---|---|
page | nullable|integer|min:1 | Page must be at least 1. |
per_page | nullable|integer|min:1|max:100 | Per 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
Form Management
Form Management (フォーム管理)
/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}/formsto fetchcopy_urland response status.
Enums
form.type
| Value | Label | Notes |
|---|---|---|
1 | Travel requirements | 旅行要望フォーム |
2 | Service review | サービスレビュー |
response.status
| Value | Label | Notes |
|---|---|---|
0 | Not answered | 未回答 |
1 | Answered | 回答済み |
Endpoints
List forms
Admin endpoint. Returns form templates for Form Management screen.
Query parameters
| Name | Type | Required | Description |
|---|---|---|---|
q | string | No | Keyword search by code/name. |
type | integer | No | Filter by form type (see enums). |
is_active | boolean | No | Filter active forms only. |
page | integer | No | Page number (default: 1). |
per_page | integer | No | Items per page (default: 20, max: 100). |
sort | string | No | Sort 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
Admin endpoint. Returns a form definition and its fields.
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
formCode | string | Yes | Form 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
Admin endpoint. Returns responses for a given form (used by /forms/{formCode}/responses UI).
Query parameters
| Name | Type | Required | Description |
|---|---|---|---|
q | string | No | Keyword search by project code or customer name/email. |
project_code | string | No | Filter by project code. |
status | integer | No | 0 not answered / 1 answered. |
submitted_from | date | No | Filter by submitted date range (from). |
submitted_to | date | No | Filter by submitted date range (to). |
page | integer | No | Page number. |
per_page | integer | No | Items 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
Admin endpoint. Returns one response with normalized answers and metadata.
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
formCode | string | Yes | Form code. |
responseId | integer | Yes | Response 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
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
Public endpoint (no Bearer token). Requires a valid token query parameter. Returns fields for rendering the public form page.
Query parameters
| Name | Type | Required | Description |
|---|---|---|---|
token | string | Yes | Share 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
Public endpoint. Requires token. Stores answers and marks the response as Answered.
Query parameters
| Name | Type | Required | Description |
|---|---|---|---|
token | string | Yes | Share token embedded in copy URL. |
Request body
| Field | Type | Required | Description |
|---|---|---|---|
answers | object | Yes | Key-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 (案件管理)
/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):
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)
Used by the Project List screen to render 4 summary cards: 案件数 / 未提案 / 手配済み / 今月の売上.
AuthAuthorization: Bearer <access_token>
Query parameters
| Name | Type | Required | Description |
|---|---|---|---|
q | string | No | Optional search keyword (same meaning as list API). |
group | string | No | Optional status-group tab filter: all, consultation, proposal, approved, booked. |
status[] | integer[] | No | Optional multi-status filter. Example: status[]=3&status[]=4. |
assigned_advisor_code | string | No | Scope stats to an assigned advisor (accounts.code). |
Validation
| Field | Rules | Message (EN) |
|---|---|---|
group | nullable|string|in:all,consultation,proposal,approved,booked | The selected group is invalid. |
status | nullable|array | Status must be an array. |
status.* | integer | The selected status is invalid. |
assigned_advisor_code | nullable|string|max:50 | Assigned 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
Project List screen (/projects). Returns a paginated list of projects with customer, assigned advisor, and summary fields.
AuthAuthorization: Bearer <access_token>
Query parameters
| Name | Type | Required | Description |
|---|---|---|---|
q | string | No | Free text search by project code or customer name/email/phone. |
status | integer | No | Project status code (0–10). |
assigned_advisor_code | string | No | Assigned advisor account code (accounts.code). |
page | integer | No | Page number (default: 1). |
per_page | integer | No | Items per page (default: 20, max: 100). |
Request body
None
Validation
| Field | Rules | Message (EN) |
|---|---|---|
per_page | integer|min:1|max:100 | Per page must be between 1 and 100. |
status | integer|in:0,1,2,3,4,5,6,7,8,9,10 | The selected status is invalid. |
assigned_advisor_code | string|max:50 | Assigned 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)
Project Edit screen (/projects/{code}/edit). The UI has 3 tabs: Basic Information, Proposals, and Form Management.
AuthAuthorization: Bearer <access_token>
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
code | string | Yes | Project code (projects.code), e.g. PRJ-001. |
Request body
None
Validation
| Field | Rules | Message (EN) |
|---|---|---|
code | string|exists:projects,code | The 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)
Used in the Proposals tab on the Project Edit screen to show proposal cards.
AuthAuthorization: Bearer <access_token>
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
code | string | Yes | Project code (projects.code). |
Query parameters
| Name | Type | Required | Description |
|---|---|---|---|
status | integer | No | Proposal status code (0–5). |
page | integer | No | Page number (default: 1). |
per_page | integer | No | Items per page (default: 20, max: 100). |
Request body
None
Validation
| Field | Rules | Message (EN) |
|---|---|---|
status | integer|in:0,1,2,3,4,5 | The selected proposal status is invalid. |
per_page | integer|min:1|max:100 | Per 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)
Used by the “Create new proposal” button from the Proposals tab.
AuthAuthorization: Bearer <access_token>
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
code | string | Yes | Project code (projects.code). |
Request body
{
"title": "Okinawa 3D2N Proposal",
"description": "Hotel + activities package.",
"total_price": 298000
}
Validation
| Field | Rules | Message (EN) |
|---|---|---|
title | required|string|max:255 | Title field is required. |
description | nullable|string | Description must be a string. |
total_price | nullable|numeric|min:0 | Total 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)
Used in the Form Management tab to show copyable URLs and whether each form has been answered.
AuthAuthorization: Bearer <access_token>
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
code | string | Yes | Project code (projects.code). |
Request body
None
Validation
| Field | Rules | Message (EN) |
|---|---|---|
code | required|string|exists:projects,code | The 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
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
| Field | Rules | Message (EN) |
|---|---|---|
customer_code | nullable|string|exists:customers,code | The selected customer does not exist. |
customer | nullable|object | The customer field must be a valid object. |
customer.name | required_without:customer_code|string|max:100 | Customer name is required when customer_code is not provided. |
customer.email | required_without:customer_code|email|max:255 | Customer email is required when customer_code is not provided. |
customer.phone | required_without:customer_code|string|max:20 | Customer phone is required when customer_code is not provided. |
travel_start_date | required|date_format:Y-m-d | Travel start date does not match the format Y-m-d. |
travel_end_date | required|date_format:Y-m-d|after_or_equal:travel_start_date | Travel end date must be after or equal to travel_start_date. |
number_of_people | required|integer|min:1|max:255 | Number of people must be between 1 and 255. |
destination | nullable|string|max:200 | Destination must not be greater than 200 characters. |
budget_min | nullable|numeric|min:0 | Budget min must be at least 0. |
budget_max | nullable|numeric|gte:budget_min | Budget max must be greater than or equal to budget_min. |
status | required|integer|in:0,1,2,3,4,5,6,7,8,9,10 | The selected status is invalid. |
priority | required|integer|in:0,1,2 | The selected priority is invalid. |
assigned_advisor_code | nullable|string|max:50 | Assigned advisor code must not be greater than 50 characters. |
zoom_available | required|integer|in:0,1 | The selected zoom availability is invalid. |
zoom_meeting_date | nullable|date | Zoom meeting date is not a valid date. |
tags | nullable|array | Tags must be an array. |
tags.* | string|max:50 | Each tag must not be greater than 50 characters. |
companions | nullable|array | Companions must be an array. |
companions.*.name | required|string|max:100 | Each companion name is required. |
companions.*.age | required|integer|min:0|max:150 | Each companion age must be between 0 and 150. |
companions.*.relationship | required|integer|in:0,1,2,3,4,5,6 | Each companion relationship is invalid. |
companions.*.special_requests | nullable|string | Special 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)
Updates the main project fields for /projects/{code}/edit.
AuthAuthorization: Bearer <access_token>
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
code | string | Yes | Project 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
| Field | Rules | Message (EN) |
|---|---|---|
code | required|string|exists:projects,code | The selected project does not exist. |
travel_start_date | required|date_format:Y-m-d | Travel start date does not match the format Y-m-d. |
travel_end_date | required|date_format:Y-m-d|after_or_equal:travel_start_date | Travel end date must be after or equal to travel_start_date. |
number_of_people | required|integer|min:1|max:255 | Number of people must be between 1 and 255. |
destination | nullable|string|max:200 | Destination must not be greater than 200 characters. |
budget_min | nullable|numeric|min:0 | Budget min must be at least 0. |
budget_max | nullable|numeric|gte:budget_min | Budget max must be greater than or equal to budget_min. |
status | required|integer|in:0,1,2,3,4,5,6,7,8,9,10 | The selected status is invalid. |
priority | required|integer|in:0,1,2 | The selected priority is invalid. |
assigned_advisor_code | nullable|string|max:50 | Assigned advisor code must not be greater than 50 characters. |
notes | nullable|string | Notes must be a string. |
zoom_available | required|integer|in:0,1 | The selected zoom availability is invalid. |
zoom_meeting_date | nullable|date | Zoom meeting date is not a valid date. |
tags | nullable|array | Tags must be an array. |
tags.* | string|max:50 | Each tag must not be greater than 50 characters. |
companions | nullable|array | Companions must be an array. |
companions.*.id | nullable|integer | Companion id must be an integer. |
companions.*.name | required|string|max:100 | Each companion name is required. |
companions.*.age | required|integer|min:0|max:150 | Each companion age must be between 0 and 150. |
companions.*.relationship | required|integer|in:0,1,2,3,4,5,6 | Each 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)
Replaces all tags for a project (syncs project_tags).
AuthAuthorization: Bearer <access_token>
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
code | string | Yes | Project code (projects.code). |
Request body
{
"tags": [
"family",
"beach"
]
}
Validation
| Field | Rules | Message (EN) |
|---|---|---|
tags | required|array | Tags field is required. |
tags.* | string|max:50 | Each 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)
Replaces companion list for a project (creates/updates/deletes companions).
AuthAuthorization: Bearer <access_token>
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
code | string | Yes | Project 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
| Field | Rules | Message (EN) |
|---|---|---|
companions | required|array | Companions field is required. |
companions.*.id | nullable|integer | Companion id must be an integer. |
companions.*.name | required|string|max:100 | Each companion name is required. |
companions.*.age | required|integer|min:0|max:150 | Each companion age must be between 0 and 150. |
companions.*.relationship | required|integer|in:0,1,2,3,4,5,6 | Each companion relationship is invalid. |
companions.*.special_requests | nullable|string | Special 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)
Creates or updates Zoom meeting for the project (zoom_meetings).
AuthAuthorization: Bearer <access_token>
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
code | string | Yes | Project code (projects.code). |
Request body
{
"meeting_date": "2026-01-16T16:00:00+09:00"
}
Validation
| Field | Rules | Message (EN) |
|---|---|---|
meeting_date | required|date | Meeting 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)
Deletes the Zoom meeting record for the project (if any).
AuthAuthorization: Bearer <access_token>
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
code | string | Yes | Project code (projects.code). |
Request body
None
Validation
| Field | Rules | Message (EN) |
|---|---|---|
code | required|string|exists:projects,code | The 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)
| Code | Label (EN) |
|---|---|
0 | Advisor selection pending |
1 | Scheduling not started |
2 | Scheduling in progress |
3 | Schedule confirmed |
4 | Proposal in progress |
5 | Booking arranged |
6 | Change processing |
7 | Trip completed |
8 | Review requested |
9 | Review submitted |
10 | Closed |
Priority (projects.priority)
| Code | Label (EN) |
|---|---|
0 | High |
1 | Medium |
2 | Low |
Zoom availability (projects.zoom_available)
| Code | Label (EN) |
|---|---|
0 | Not available |
1 | Available |
Form type (forms.type)
| Code | Label (EN) |
|---|---|
0 | Travel request |
1 | Service review |
Form response status
| Code | Label (EN) |
|---|---|
0 | Not answered |
1 | Answered |
Companion relationship (companions.relationship)
| Code | Label (EN) |
|---|---|
0 | Spouse |
1 | Child |
2 | Parent |
3 | Sibling |
4 | Friend |
5 | Colleague |
6 | Other |
Notes
codein the path refers toprojects.code(e.g.,PRJ-001).- Assigned advisor is returned as
assigned_advisorobject (no separateassigned_advisor_idfield in list/detail responses). - Customer is returned as
customerobject (no separatecustomer_idfield in list/detail responses). status,priority, andzoom_availableare returned as objects, e.g.{"status": 1, "label": "..."}.number_of_peopleis used instead ofadults/children./api/projects/{code}/formsreturns shareable URLs and whether each form was submitted (answered).
Proposal Management
Proposal Management (提案管理)
/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 numericid. - Relations:
proposals→projects(DB column name iscase_id) and child tablesproposal_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/proposalswithqandstatusfilters.
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/accommodationsto search, then writes toaccommodations[]in proposal.
- Initial create:
- 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 embeddedcustomerandcompanions. - GET
/api/proposals/{code}— load the proposal draft (title/description/status/total_price) when editing.
- GET
- 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}
- Create: POST
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}withaccommodationsarray (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}withitinerary_items. - Alternative: save as
activitiesarray snapshot (if you add a DB table later).
- Recommended: save as itinerary items (type = Activity) via PUT
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}withitinerary_itemsarray.
itinerary_items fields (based on proposal_itinerary)
| Field | Type | Required | Description |
|---|---|---|---|
activity_date | date | Yes | Which day this item belongs to. |
start_time | time | No | Start time (optional). |
end_time | time | No | End time (optional). |
title | string | No | Item title. |
description | string | No | Details/notes. |
location | string | No | Location text. |
type | integer | Yes | 0 transport / 1 accommodation / 2 meal (recommend adding 3 activity). |
sort_order | integer | No | Order 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}withtotal_price. - Preview: GET
/api/proposals/{code}/previewreturns a preview URL or HTML.
Request body (update total price)
{
"total_price": 2450000
}
Data models
Proposal
| Field | Type | Description |
|---|---|---|
id | integer | Internal numeric ID. |
code | string | Public identifier (e.g., PROP-001). |
project | object | Project (案件) summary object. |
customer | object | Customer summary object (from project). |
assigned_advisor | object | Assigned advisor (from project). |
title | string | Proposal title. |
total_price | number | Total price (DECIMAL). |
status | object | Status object: {status:int,label:string}. |
description | string|null | Notes / description. |
accommodations | array | Selected accommodations (optional, when requested). |
itinerary | array | Itinerary items (optional, when requested). |
created_at | string | Timestamp. |
updated_at | string | Timestamp. |
ProposalAccommodation
| Field | Type | Description |
|---|---|---|
id | integer | Internal ID. |
accommodation | object | Accommodation summary object. |
check_in_date | string | YYYY-MM-DD |
check_out_date | string | YYYY-MM-DD |
room_count | integer|null | Rooms requested. |
price_per_night | number|null | Optional (for preview). |
total_price | number|null | Optional (for preview). |
notes | string|null | Notes. |
ProposalItineraryItem
| Field | Type | Description |
|---|---|---|
id | integer | Internal ID. |
activity_date | string | YYYY-MM-DD |
start_time | string|null | HH:MM:SS |
end_time | string|null | HH:MM:SS |
title | string|null | Title. |
description | string|null | Description. |
location | string|null | Location. |
type | object | Type object: {type:int,label:string}. |
sort_order | integer | Ordering in the day. |
Enums
proposal.status
| Value | Label | Notes |
|---|---|---|
0 | Draft | 下書き |
1 | Tentative booking | 仮予約 |
2 | Proposing | 提案中 |
3 | Paid | 決済完了 |
4 | Cancelled | キャンセル |
5 | Rejected | 不採用 (UI may show as ボツ) |
proposal_itinerary.type
| Value | Label | Notes |
|---|---|---|
0 | Transport | 移動 |
1 | Accommodation | 宿泊 |
2 | Meal | 食事 |
Endpoints
List proposals
Returns proposals with project/customer/advisor summary info, plus tab summary counts.
Query parameters
| Name | Type | Required | Description |
|---|---|---|---|
q | string | No | Keyword search by proposal code/title/customer name. |
status | integer | No | Filter by proposal status value. |
project_code | string | No | Filter by project (案件) code, e.g., CASE-002. |
customer_code | string | No | Filter by customer code. |
assigned_advisor_code | string | No | Filter by assigned advisor code. |
page | integer | No | Page number (default: 1). |
per_page | integer | No | Items per page (default: 15, max: 100). |
sort | string | No | Sort 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
Creates a proposal for a given project (案件). Can optionally copy from an existing proposal.
Request body
| Field | Type | Required | Description |
|---|---|---|---|
project_code | string | Yes | Project (案件) code. |
title | string | Yes | Max 255 characters. |
description | string | No | Optional notes. |
status | integer | No | Default: 0 (Draft). |
total_price | number | No | Total price (DECIMAL). |
copy_from_code | string | No | If provided, system copies accommodations/itinerary from that proposal. |
accommodations | array | No | List of accommodations to attach (see below). |
itinerary | array | No | List 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 ifaccommodationspresent, 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 aftercheck_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
Returns a proposal. By default, includes accommodations and itinerary arrays.
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
code | string | Yes | Proposal 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
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
Updates proposal status following the UI flow. For example, Draft → Tentative, Tentative → Proposing, Paid → Cancelled, etc.
Request body
| Field | Type | Required | Description |
|---|---|---|---|
status | integer | Yes | New status value (0..5). |
note | string | No | Optional note for auditing. |
Allowed transitions (recommended)
| From | To | Manual? | Notes |
|---|---|---|---|
| Draft (0) | Tentative (1), Proposing (2), Rejected (5) | Yes | Operator/advisor can move forward or reject. |
| Tentative (1) | Proposing (2), Rejected (5) | Yes | Confirm before sending proposal. |
| Proposing (2) | Paid (3) | No | Should switch automatically after payment confirmation. |
| Paid (3) | Cancelled (4) | Yes | Cancel 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."
]
}
}
}
Search activities
Search activities for the Activities tab. Activities may come from an external provider; store chosen items in the itinerary or snapshot.
Query parameters
| Name | Type | Required | Description |
|---|---|---|---|
q | string | No | Keyword search (name/location/category). |
destination | string | No | Destination filter (e.g., Tokyo, Hakone). |
category | string | No | Activity category (e.g., sightseeing, experience). |
price_min | number | No | Minimum price. |
price_max | number | No | Maximum price. |
page | integer | No | Page number. |
per_page | integer | No | Items 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
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
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"
}
}