> ## 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 flight AI search by reading the task or as a stream of events; cancel a search and retry a message safely.

A turn that searches flights usually takes 10 to 60 seconds, and can take longer. `POST /v1/flight_ai_search` waits 20 seconds, so most searches answer `202` first and go on as a task that you read or stream until it ends. A question, or a message that needs no search, usually answers within the wait. Pick one of three ways to get the result.

| Way | Request | Use it when |
| - | - | - |
| Wait in the call (default) | `POST` with no extra header, then read the task after a `202` | You want one call for questions and quick answers, and handle `202` for the searches. |
| Answer at once, then read the task | `POST` with `Prefer: wait=0`, then `GET /v1/flight_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 options. |

## 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/flight_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/flight_ai_search/tasks/atk_mvq3e2f97a1c4d0b8e6f2359 \
  --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_mvq3e2f1d8b04a6c2e9f7135",
  "task_id": "atk_mvq3e2f97a1c4d0b8e6f2359",
  "status": "working",
  "message": "Looking for the best days for Amsterdam → Lisbon, 1 Mar 2027 – 31 Mar 2027, one way (1 adult).",
  "criteria": { "trip_type": "oneway", "origins": ["..."], "destinations": ["..."], "departure": { "window": { "start": "2027-03-01", "end": "2027-03-31" } }, "passengers": { "adults": 1, "children": 0, "infants": 0 }, "currency": "EUR" },
  "notes": [{ "kind": "default_passengers", "text": "No travellers were given; priced for one adult." }]
}
```

The early reply depends on the search:

| Search | Early reply |
| - | - |
| Exact dates | `Searching flights: Paris → Tokyo, 3 Oct 2027, back 17 Oct 2027, 4 adults.` |
| A date window, or several destinations | `Looking for the best days for Amsterdam → Lisbon, … (1 adult).` |
| No destination | `Looking for destinations from Paris, ….` |

```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 5 minutes after it was sent 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/flight_ai_search \
  --header "X-API-Key: $JINKO_API_KEY" \
  --header "Content-Type: application/json" \
  --header "Accept: text/event-stream" \
  --data '{"text": "The cheapest day from Amsterdam to Lisbon in March 2027", "timezone": "Europe/Amsterdam", "currency": "EUR"}'
```

A search that ends with options sends five events:

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

id: 2
event: task.status
data: {"task_id":"atk_mvq3e2f97a1c4d0b8e6f2359","context_id":"ctx_mvq3e2f1d8b04a6c2e9f7135","status":"working"}

id: 3
event: agent.message
data: {"task_id":"atk_mvq3e2f97a1c4d0b8e6f2359","context_id":"ctx_mvq3e2f1d8b04a6c2e9f7135","turn":1,"message":"Looking for the best days for Amsterdam → Lisbon, 1 Mar 2027 – 31 Mar 2027, one way (1 adult).","criteria":{...},"notes":[...]}

id: 4
event: agent.message
data: {"task_id":"atk_mvq3e2f97a1c4d0b8e6f2359","context_id":"ctx_mvq3e2f1d8b04a6c2e9f7135","turn":1,"message":"3 bookable options for Amsterdam → Lisbon, 1 Mar 2027 – 31 Mar 2027, one way, 1 adult. Best match: 54 EUR in total, nonstop.","status":"completed","criteria":{...},"notes":[...],"options":[...],"groups":[...],"has_more":false}

id: 5
event: task.completed
data: {"task_id":"atk_mvq3e2f97a1c4d0b8e6f2359","context_id":"ctx_mvq3e2f1d8b04a6c2e9f7135","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`, `options`, `groups`, `has_more`, and `question` and `question_options` when it asks one. |
| `task.completed` | The turn ended with an answer. | `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.

Events carry no `charge`. Read the task once the turn has ended to get it; see [Charges](https://docs.dev.gojinko.com/ai/flight-search/overview.md#charges).

### Resume a stream

A stream also closes after 45 seconds without an event that ends the turn, or when the connection drops. Many flight searches run longer than 45 seconds, so plan to reconnect. 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/flight_ai_search/tasks/atk_mvq3e2f97a1c4d0b8e6f2359/events \
  --header "X-API-Key: $JINKO_API_KEY" \
  --header "Last-Event-ID: 3"
```

The stream resumes with the next event (`id: 4` 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`, `402`, `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/flight_ai_search/tasks/atk_mvq3e2f97a1c4d0b8e6f2359: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/flight-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

* [Flight AI search](https://docs.dev.gojinko.com/ai/flight-search/overview.md)
* [Conversations](https://docs.dev.gojinko.com/ai/flight-search/conversations.md)
* [Statuses and errors](https://docs.dev.gojinko.com/ai/flight-search/statuses-and-errors.md)


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