# Concierge Booking Request
> Source: /api-reference/members/reservations/concierge-request
> Submit a concierge-assisted booking request for alliance properties
> Endpoint: POST /members/me/reservations/concierge-request

## Overview [#overview]

Submit a booking request for an alliance property that requires concierge assistance. The API snapshots the property and stay context the member saw, alerts the Journey concierge team via Slack, and returns a booking external ID.

This endpoint is used for alliance/partner properties where real-time online booking is not available. The concierge team follows up to complete the reservation.

<Note>
  This is not a direct booking endpoint — it queues a concierge action. The member will be contacted to confirm the booking.
</Note>

## Authentication [#authentication]

Requires a valid Clerk JWT token.

## Request Body [#request-body]

<ParamField body="propertyExternalId" type="string" required>
  Property's stable external identifier (Strapi/PMS external ID)
</ParamField>

<ParamField body="propertyName" type="string" required>
  Property name as shown to the member at time of request (max 200 chars)
</ParamField>

<ParamField body="pageTemplateType" type="string">
  `Hotel` or `STR` — whether this is a multi-room hotel or short-term rental. Used by the concierge to set expectations.
</ParamField>

<ParamField body="checkIn" type="string" required>
  Check-in date in `YYYY-MM-DD` format (ISO 8601)
</ParamField>

<ParamField body="checkOut" type="string" required>
  Check-out date in `YYYY-MM-DD` format (ISO 8601)
</ParamField>

<ParamField body="adults" type="number" required>
  Number of adult guests (1–20)
</ParamField>

<ParamField body="children" type="number" required>
  Number of children (0–20)
</ParamField>

<ParamField body="infants" type="number">
  Number of infants ages 0–2 (0–20). Forwarded to concierge for crib/setup requests. Defaults to 0.
</ParamField>

<ParamField body="pets" type="number">
  Number of pets (0–20). Defaults to 0.
</ParamField>

<ParamField body="pointsRequired" type="number">
  Points total shown to the member. When provided, the server validates this matches the server-computed cost — a mismatch returns `400 POINTS_COST_MISMATCH` so the client can refresh pricing before re-confirming.
</ParamField>

<ParamField body="cashTotal" type="number">
  Cash total in USD shown to the member (≤ 2 decimal places). Same mismatch validation as `pointsRequired` — a `400 TOTAL_COST_MISMATCH` tells the client to refresh pricing.
</ParamField>

<ParamField body="notes" type="string">
  Free-text message from the member ("anything else we should know?"). Max 500 chars.
</ParamField>

<ParamField body="voiceNoteTranscript" type="string">
  Deepgram STT transcript of a spoken voice note. Max 5000 chars. Surfaced alongside booking details in the Slack alert.
</ParamField>

<ParamField body="voiceNoteDurationSeconds" type="number">
  Duration of the voice note in seconds (non-negative integer). Rendered as H:MM:SS in the Slack alert.
</ParamField>

## Response [#response]

<ResponseField name="status" type="string" required>
  Always `"received"` on success
</ResponseField>

<ResponseField name="bookingExternalId" type="string" required>
  Stable UUID for the created booking record. Use this for any subsequent member-facing lookups. The internal numeric ID is not exposed.
</ResponseField>

<ResponseField name="totalPointCost" type="number" required>
  Server-computed, validated points cost. Treat this as canonical — may differ from `pointsRequired` if pricing shifted.
</ResponseField>

<ResponseField name="totalCostUsd" type="number" required>
  Server-computed member cash total for the stay (USD). Canonical — may differ from `cashTotal`.
</ResponseField>

<RequestExample>

```bash cURL
curl -X POST "https://api.journey.com/members/me/reservations/concierge-request" \
  -H "Authorization: Bearer YOUR_CLERK_JWT" \
  -H "Content-Type: application/json" \
  -d '{
    "propertyExternalId": "prop_ext_abc123",
    "propertyName": "The Surf Club",
    "pageTemplateType": "Hotel",
    "checkIn": "2026-07-10",
    "checkOut": "2026-07-14",
    "adults": 2,
    "children": 0,
    "pointsRequired": 12000,
    "cashTotal": 450.00,
    "notes": "Celebrating an anniversary — any room upgrades appreciated"
  }'
```

```bash With Voice Note
curl -X POST "https://api.journey.com/members/me/reservations/concierge-request" \
  -H "Authorization: Bearer YOUR_CLERK_JWT" \
  -H "Content-Type: application/json" \
  -d '{
    "propertyExternalId": "prop_ext_abc123",
    "propertyName": "The Surf Club",
    "checkIn": "2026-07-10",
    "checkOut": "2026-07-14",
    "adults": 2,
    "children": 0,
    "voiceNoteTranscript": "Hi, we are celebrating our anniversary and would love an ocean view room if possible...",
    "voiceNoteDurationSeconds": 14
  }'
```

```typescript TypeScript
const result = await fetch(
  'https://api.journey.com/members/me/reservations/concierge-request',
  {
    method: 'POST',
    headers: {
      'Authorization': `Bearer ${clerkToken}`,
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({
      propertyExternalId: 'prop_ext_abc123',
      propertyName: 'The Surf Club',
      pageTemplateType: 'Hotel',
      checkIn: '2026-07-10',
      checkOut: '2026-07-14',
      adults: 2,
      children: 0,
      pointsRequired: 12000,
      cashTotal: 450.00
    })
  }
).then(r => r.json());

console.log('Booking ID:', result.bookingExternalId);
console.log('Confirmed cost:', result.totalPointCost, 'pts + $' + result.totalCostUsd);
```

</RequestExample>

<ResponseExample>

```json 202 Success
{
  "status": "received",
  "bookingExternalId": "12345678-1234-5678-9012-123456789012",
  "totalPointCost": 12000,
  "totalCostUsd": 450.00
}
```

```json 400 Points Cost Mismatch
{
  "statusCode": 400,
  "message": "POINTS_COST_MISMATCH",
  "error": "Bad Request"
}
```

</ResponseExample>

## Error Codes [#error-codes]

| Code | Meaning |
|------|---------|
| `POINTS_COST_MISMATCH` | `pointsRequired` doesn't match server-computed cost — refresh pricing and retry |
| `TOTAL_COST_MISMATCH` | `cashTotal` doesn't match server-computed total — refresh pricing and retry |
