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

# Waiting for results

> Get the answer of a hotel AI search in one call, by reading the task, or as a stream of events; cancel a search and retry a message safely.

A search usually ends within the 20 seconds that `POST /v1/hotel_ai_search` waits. When it does not, the call answers `202` and the search goes on as a task that you read or stream until it ends. Pick one of three ways to get the result.

| Way | Request | Use it when |
| - | - | - |
| Wait in the call (default) | `POST` with no extra header | You want one call that answers most searches. Handle `202` for the rest. |
| Answer at once, then read the task | `POST` with `Prefer: wait=0`, then `GET /v1/hotel_ai_search/tasks/{task_id}` | You do not want to hold a connection open, or you run many searches at once. |
| Stream the events | `POST` with `Accept: text/event-stream` | You show progress: the early reply first, then the hotels. |

## Wait in the call

Without a `Prefer` header the call waits up to 20 seconds.

* **`200`** when the turn ended in that time. `status` is `completed`, `input_required`, `failed` or `canceled`. Nothing more to read.
* **`202`** when the search is still running. `status` is `working`, and the response carries:

| Header | Value |
| - | - |
| `Location` | `/v1/hotel_ai_search/tasks/{task_id}`, the task to read. |
| `Retry-After` | `2`, the seconds to wait before reading it. |
| `Preference-Applied` | The wait applied, when you sent `Prefer`. |

`Prefer: wait=N` sets the wait to `N` seconds, from `0` to `20`. A larger value is applied as `20`, and `Preference-Applied` says so (`wait=30` is answered with `Preference-Applied: wait=20`). An unreadable `Prefer` value is ignored and the default wait applies.

## Read the task

```bash theme={null}
curl https://api.gojinko.com/v1/hotel_ai_search/tasks/atk_mvinwb3j1cf34dee951fc702 \
  --header "X-API-Key: $JINKO_API_KEY"
```

The task read answers `200` with the same body as `POST`. Read it every `Retry-After` seconds while `status` is `working`, and stop at any other status.

While the search runs, the task carries the early reply: `message` says what is being searched, and `criteria` holds it.

```json theme={null}
{
  "context_id": "ctx_mvinvg98ecb918c84c4e6770",
  "task_id": "atk_mvinvg9d9c9e4c6732210eeb",
  "status": "working",
  "message": "Searching hotels in Paris for Oct 24–25 (1 night, 2 adults, 1 room). Results are on the way.",
  "criteria": { "city_name": "Paris", "country_code": "FR", "checkin": "2026-10-24", "checkout": "2026-10-25", "occupancies": [{ "adults": 2, "children_ages": [] }] }
}
```

```js theme={null}
async function readUntilEnd(location, apiKey) {
  for (;;) {
    const res = await fetch(`https://api.gojinko.com${location}`, { headers: { "X-API-Key": apiKey } });
    if (!res.ok) throw new Error(`task read failed: ${res.status}`);
    const task = await res.json();
    if (task.status !== "working") return task;
    await new Promise((r) => setTimeout(r, 2000));
  }
}
```

A turn that has not ended 90 seconds after it started ends as `failed` with `error.code` `deadline_exceeded`. Send the message again.

## Stream the events

Send `Accept: text/event-stream` on `POST` to receive the turn as Server-Sent Events on the same connection.

```bash theme={null}
curl -N https://api.gojinko.com/v1/hotel_ai_search \
  --header "X-API-Key: $JINKO_API_KEY" \
  --header "Content-Type: application/json" \
  --header "Accept: text/event-stream" \
  --data '{"text": "A hotel in Paris next weekend for 2 adults with breakfast included", "timezone": "Europe/Paris"}'
```

A search that ends with hotels sends six events:

```text theme={null}
id: 1
event: task.status
data: {"task_id":"atk_mvinvg9d9c9e4c6732210eeb","context_id":"ctx_mvinvg98ecb918c84c4e6770","status":"submitted","turn":1}

id: 2
event: task.status
data: {"task_id":"atk_mvinvg9d9c9e4c6732210eeb","context_id":"ctx_mvinvg98ecb918c84c4e6770","status":"working"}

id: 3
event: agent.message
data: {"task_id":"atk_mvinvg9d9c9e4c6732210eeb","context_id":"ctx_mvinvg98ecb918c84c4e6770","turn":1,"message":"Searching hotels in Paris for Oct 24–25 (1 night, 2 adults, 1 room). Results are on the way.","criteria":{...},"notes":[]}

id: 4
event: hotels.page
data: {"task_id":"atk_mvinvg9d9c9e4c6732210eeb","context_id":"ctx_mvinvg98ecb918c84c4e6770","hotels":[...],"has_more":true,"reference":false}

id: 5
event: agent.message
data: {"task_id":"atk_mvinvg9d9c9e4c6732210eeb","context_id":"ctx_mvinvg98ecb918c84c4e6770","turn":1,"status":"completed","message":"Found 33 hotels in Paris for Oct 24–25 (1 night, 2 adults, 1 room) matching: breakfast included. More are available.","hotels":[...],"criteria":{...},"has_more":true,"notes":[]}

id: 6
event: task.completed
data: {"task_id":"atk_mvinvg9d9c9e4c6732210eeb","context_id":"ctx_mvinvg98ecb918c84c4e6770","status":"completed"}
```

| Event | Sent when | `data` carries |
| - | - | - |
| `task.status` | The turn is accepted (`submitted`), then starts (`working`). | `status`, and `turn` on `submitted`. |
| `agent.message` | A reply is written: the early reply first, then the final reply. | `message`, `criteria`, `notes`; the final reply also carries `status`, `hotels`, `has_more`, and `question` when it asks one. |
| `hotels.page` | Hotels are ready. | `hotels`, `has_more`, `reference`. |
| `task.completed` | The turn ended with hotels. | `status: "completed"`. |
| `task.input_required` | The turn ended with a question. | `status: "input_required"`. |
| `task.failed` | The turn failed. | `status: "failed"` and `error` (`code`, `message`). |
| `task.expired` | The turn ran past its deadline, or its conversation expired while it waited for an answer. | `status: "failed"` and `error`. |
| `task.canceled` | The task was canceled or replaced. | `status: "canceled"`, and `superseded_by` when a later message replaced it. |

Every event carries `task_id` and `context_id`. `id` is the event's sequence number in the task. The stream ends after the event that ends the turn. Comment lines (`: keep-alive`) arrive every 15 seconds and carry no event; most SSE clients skip them.

### Resume a stream

A stream also closes after 45 seconds without an event that ends the turn, or when the connection drops. Reconnect to the task's events with the `id` of the last event you received:

```bash theme={null}
curl -N https://api.gojinko.com/v1/hotel_ai_search/tasks/atk_mvinvg9d9c9e4c6732210eeb/events \
  --header "X-API-Key: $JINKO_API_KEY" \
  --header "Last-Event-ID: 2"
```

The stream resumes with the next event (`id: 3` here), with no gap and no repeat. Without `Last-Event-ID`, or with a value that is not a number, the stream starts from the first event of the task. For a task that has already ended, the stream sends the remaining events and closes.

`GET .../events` always answers with a stream, whatever the `Accept` header.

When `POST` with `Accept: text/event-stream` is refused (`400`, `404`, `410`, `422`, or an authentication error), the answer is the JSON error, with no stream.

## Cancel a search

```bash theme={null}
curl --request POST https://api.gojinko.com/v1/hotel_ai_search/tasks/atk_mvinwbgbd34d5e7c92c823ff:cancel \
  --header "X-API-Key: $JINKO_API_KEY"
```

* A task that is `working` or `input_required` is canceled: `200` with `status: "canceled"`. It writes no further result.
* A task that has already ended answers `409` `CONFLICT`, for example `The task has already ended (canceled).`

You do not need to cancel before sending a correction. A new message in the conversation replaces a search that is still running; see [Conversations](https://docs.dev.gojinko.com/ai/hotel-search/conversations.md#change-the-search-while-it-runs).

## Retry a message safely

Send an `Idempotency-Key` header (1 to 200 characters) with each message when your client may send it twice, for example after a timeout or a `502`, `503` or `504`.

* A retry with the same key does not send a second message. It answers the first message again: its stored answer once that turn is over, or the same wait as a new message while it runs.
* Keys belong to your account, not to a conversation: every API key of the account shares them. The body of a retry is not compared with the first one, so a retry with a different `text` still gets the first answer. Use a new key for each new message.

## Related

* [Hotel AI search](https://docs.dev.gojinko.com/ai/hotel-search/overview.md)
* [Conversations](https://docs.dev.gojinko.com/ai/hotel-search/conversations.md)
* [Statuses and errors](https://docs.dev.gojinko.com/ai/hotel-search/statuses-and-errors.md)
* API reference: [`POST /v1/hotel_ai_search`](https://docs.dev.gojinko.com/api/hotel-ai-search), [`GET /v1/hotel_ai_search/tasks/{task_id}`](https://docs.dev.gojinko.com/api/hotel-ai-search-task), [`GET .../events`](https://docs.dev.gojinko.com/api/hotel-ai-search-events), [`POST ...:cancel`](https://docs.dev.gojinko.com/api/hotel-ai-search-cancel)


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.