> ## Documentation Index
> Fetch the complete documentation index at: https://docs.expys.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Interests and intake

> Let a member start a conversation about an experience, share their dates, and commit later - with the whole thread carried across.

A member does not have to spend points to start talking to a concierge. An **interest**
registers that they want an experience, opens a concierge conversation, and gives them
somewhere to put their dates - with **no points debited and no inventory held**.

When they commit, the redemption inherits that same conversation and everything they
told us.

```
POST /v1/interests              → interest + conversation, nothing debited
PUT  /v1/interests/{id}/intake  → amend, and send the details we don't store
POST /v1/redemptions            → { offer, interest } - inherits both
```

<Note>
  An interest holds no stock. A member who takes a week to decide can still lose the
  last unit - which is honest, where a silent hold would let one undecided member freeze
  inventory indefinitely. Check `availability` on the offer to show what is left.
</Note>

## Registering an interest

`POST /v1/interests` takes the offer and, optionally, everything the member already
knows about their plans.

```bash theme={null}
curl -X POST https://api.expys.com/v1/interests \
  -H "Authorization: Bearer YOUR_MEMBER_TOKEN" \
  -H "Idempotency-Key: 0f3a9c2e-4b1d-4e6a-9c7b-1f2e3d4c5b6a" \
  -H "Content-Type: application/json" \
  -d '{
        "offer": "off_123",
        "preferredDates": ["2026-11-06", "2026-11-04"],
        "blackoutDates": ["2026-12-25"],
        "adults": 2,
        "children": 1,
        "occasion": "ANNIVERSARY",
        "contactMethod": "APP_CHAT",
        "contactWindow": "EVENING"
      }'
```

The response carries a `conversationId`. That thread is live immediately - pass it to
`sendMessage` or the SSE stream and the member is talking to a concierge.

<ResponseField name="conversationId" type="string | null" required>
  The concierge conversation. The redemption that follows inherits it, so this id stays
  valid for the whole journey.
</ResponseField>

<ResponseField name="intake" type="Intake | null" required>
  What the member told us, or `null` if they told us nothing yet.
</ResponseField>

### Dates are calendar dates

`preferredDates` and `blackoutDates` are `YYYY-MM-DD` strings, **not** datetimes. A
datetime is rejected rather than parsed: it moves a date across a day boundary for
anyone west of UTC, in the direction that loses the member their first choice.

Preferred dates are stored **in the order you send them** - first choice first. Up to
five preferred and twenty blackout dates.

## Two kinds of intake, handled differently

<Warning>
  **Dietary requirements, allergies and accessibility needs are never stored.**

  They are health data. We forward them straight to the concierge team and keep only a
  timestamp saying they were sent. You can submit them; you cannot read them back,
  because we will not be holding them.
</Warning>

| Field                                     | Stored | Returned                               |
| ----------------------------------------- | ------ | -------------------------------------- |
| `preferredDates`, `blackoutDates`         | yes    | yes                                    |
| `adults`, `children`                      | yes    | yes                                    |
| `occasion`                                | yes    | yes                                    |
| `contactMethod`, `contactWindow`, `phone` | yes    | yes                                    |
| `dietary`, `allergies`, `accessibility`   | **no** | **no** - only `sensitiveDetailsSentAt` |

The three health fields are accepted **only** on `PUT /v1/interests/{id}/intake`, never
on the create. Sending one to `POST /v1/interests` is refused with
`INTAKE_SENSITIVE_INLINE` rather than ignored - a create can fail, and details for a
booking that never existed are details we should not have received.

```bash theme={null}
curl -X PUT https://api.expys.com/v1/interests/req_123/intake \
  -H "Authorization: Bearer YOUR_MEMBER_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
        "preferredDates": ["2026-11-06"],
        "adults": 2,
        "dietary": "coeliac, strictly no gluten",
        "allergies": "anaphylactic to peanuts"
      }'
```

`PUT` **replaces** the whole intake, so send the complete answer each time. A partial
merge over a set of dates has no sane meaning - is an omitted `preferredDates`
"unchanged" or "I have none"?

## About `phone`

Optional, E.164, and stored. It is the only contact detail Expys ever holds for a
member - your platform owns their identity, and we deliberately keep no email address.

`contactMethod: "PHONE"` is therefore an instruction to **you**, not to us. We record it
and show it to the concierge team, and we reach the member in chat.

## Verifying the phone number

A number is **display-only until it is verified**. The concierge team will not text an
unverified one — a single typo is all it takes to send a stranger somebody else's
itinerary.

```
POST /v1/interests/{id}/phone/verification   → sends a code
PUT  /v1/interests/{id}/phone/verification   → { "code": "123456" }
```

The confirm returns `{ "verified": true | false }`. **`false` is not an error** — it means
the code was wrong or expired, which is an ordinary thing for somebody to do. Ask again.

We store no code: Twilio generates it and owns its expiry, so there is no column here
holding a one-time code and none to leak one from.

Once verified and with `contactMethod: "SMS"`, concierge messages are relayed by text and
the member can simply reply. Those replies land in the same conversation the app shows —
one thread, two transports.

<Note>
  **`STOP` works, and it is honoured before anything else.** A member who texts `STOP` is
  switched to app chat across every booking carrying that number, not just the one they
  replied to. `START` turns it back on.
</Note>

## Committing

Pass the interest when you create the redemption:

```bash theme={null}
curl -X POST https://api.expys.com/v1/redemptions \
  -H "Authorization: Bearer YOUR_MEMBER_TOKEN" \
  -H "Idempotency-Key: 7c1f...":  \
  -H "Content-Type: application/json" \
  -d '{ "offer": "off_123", "interest": "req_abc" }'
```

The redemption:

* **inherits the interest's conversation** - no second thread is opened, and
  `redemption.conversationId` is the interest's id, not the redemption's
* **carries the intake across**, dates and order included
* **closes the interest**, so it leaves our concierge queue

The interest keeps its own copy of the intake, so the conversation still reads correctly
afterwards.

<Note>
  `interest` is optional. Redeeming without one behaves exactly as it always has.
</Note>

## Errors

| Code                      | Status | Meaning                                                                                                                                                                                                                                 |
| ------------------------- | ------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `INTAKE_SENSITIVE_INLINE` | 422    | A health field was sent to a route that does not accept it. Use the intake endpoint.                                                                                                                                                    |
| `INTAKE_INVALID`          | 422    | A rule about the content failed - too many dates, a date in the past, a party size out of range, a date both preferred and blacked out, or a phone number that is not E.164. The message names the field and the rule, never the value. |
| `NOT_FOUND`               | 404    | The interest does not exist, is not yours, is for a different experience, or has already been converted.                                                                                                                                |
