# Responses

`POST /v1/responses` asks Azta one question. The answer cites the provisions it rests on, so every answer links to the provisions it cites. This guide covers how to wait for an answer, how to ask a follow-up and how to retry without paying twice.

## Stream or background

Set exactly one of `stream` and `background` to `true`.

- `stream: true` answers `200` as server-sent events, in OpenAI's Responses event vocabulary. `response.output_text.delta` events carry the text as it is written. `response.output_text.done` carries the whole text and is the authoritative one, so replace what you built from deltas with it. Annotations and sources arrive only in the closing frames.
- `background: true` answers `202` with the response in `queued`. Poll `GET /v1/responses/{id}` until `status` is `completed`, `failed` or `cancelled`. The [quickstart](/quickstart) does this every 5 seconds.

There is no partial text while a response runs. `GET /v1/responses/{id}` returns an empty `output` while the status is `queued` or `in_progress`; `sources` is `[]` until it is `completed`.

The stream has a deadline of 900 seconds. A stream that reaches it ends with `response.failed`, but that ends the stream, not the run. The run keeps executing, and it is charged if it completes. Poll `GET /v1/responses/{id}` for its real outcome, or cancel it. Dropping the connection does not cancel the run either, and reconnecting does not replay the stream.

## Cancel a response

`POST /v1/responses/{id}/cancel` stops a run. A cancel after answer text has started streaming is charged; a cancel before any answer text was delivered is free (see [Pricing](/guides/pricing)). Cancelling a response that has already ended answers `409` `response_finished`. A cancel creates and charges nothing by itself, and a repeat answers the run's current state.

## Depth

`depth` is `standard` by default. Send `deep` only when you want the run to research further before it answers; it is priced higher. The response echoes `depth`. Any other value is `400` `invalid_request` with `param: "depth"`. See [Pricing](/guides/pricing) for the amounts.

## Jurisdiction

`X-Jurisdiction` names the countries whose law the answer draws on, as a comma-separated list of ISO country codes, in any case and spacing. Without it the default is `hu`. `us`, `oecd`, `ifrs` and unknown codes are refused with `400` `unsupported_jurisdiction`.

## Follow-up questions

The API is stateless: it keeps no conversation for you. To ask a follow-up, send the whole conversation as `input`, an item array whose last item is the new question with `role: "user"`.

```bash tab="curl"
#!/usr/bin/env bash
# Ask a follow-up question. The API is stateless, so send the whole
# conversation as `input`; the last item is the new user question.
# Needs curl, jq and uuidgen.
set -euo pipefail

: "${AZTA_API_KEY:?Set AZTA_API_KEY to your Azta API key}"
BASE_URL="${AZTA_BASE_URL:-https://api.azta.ai/v1}"

headers="$(mktemp)"
body="$(mktemp)"
trap 'rm -f "$headers" "$body"' EXIT

# One key per question, reused on every retry of it.
IDEMPOTENCY_KEY="$(uuidgen | tr 'A-Z' 'a-z')"

payload='{
  "model": "azta-agent",
  "background": true,
  "input": [
    {"role": "user", "content": "Mennyi az áfa mértéke?"},
    {"role": "assistant", "content": "Az általános kulcs 27%."},
    {"role": "user", "content": "És építőipari szolgáltatásnál?"}
  ]
}'

# retry_after DEFAULT prints the Retry-After seconds, else DEFAULT.
retry_after() {
  local value
  value="$(grep -i '^retry-after:' "$headers" | tail -n 1 | cut -d: -f2 | tr -d ' \r')"
  if [[ "$value" =~ ^[0-9]+$ ]]; then
    echo "$value"
    return
  fi
  echo "${1}"
}

for attempt in 1 2 3; do
  status="$(curl -sS -D "$headers" -o "$body" -w '%{http_code}' "$BASE_URL/responses" \
    -H "Authorization: Bearer $AZTA_API_KEY" \
    -H 'Content-Type: application/json' \
    -H "Idempotency-Key: $IDEMPOTENCY_KEY" \
    -d "$payload")"
  case "$status" in
    200 | 202) break ;;
    429 | 500 | 503) [ "$attempt" -lt 3 ] && sleep "$(retry_after 1)" ;;
    *) echo "Error: http_$status" >&2; exit 1 ;;
  esac
done
[ "$status" = 200 ] || [ "$status" = 202 ] || exit 1
id="$(jq -r '.id' "$body")"

while true; do
  status="$(curl -sS -D "$headers" -o "$body" -w '%{http_code}' "$BASE_URL/responses/$id" \
    -H "Authorization: Bearer $AZTA_API_KEY")"
  case "$status" in
    429 | 503) sleep "$(retry_after 5)"; continue ;;
    200) ;;
    *) echo "Error: http_$status" >&2; exit 1 ;;
  esac
  state="$(jq -r '.status' "$body")"
  case "$state" in
    completed) break ;;
    failed | cancelled) echo "Error: $(jq -r '.error.code // .status' "$body")" >&2; exit 1 ;;
  esac
  sleep 5
done
jq -r '[.output[].content[] | select(.type == "output_text") | .text] | join("")' "$body"
```

```python tab="Python"
"""Ask a follow-up. The API is stateless: send the whole conversation as input."""

import os
import sys
import time
import uuid

import httpx

API_KEY = os.environ.get("AZTA_API_KEY")
if not API_KEY:
    sys.exit("Set AZTA_API_KEY to your Azta API key")
BASE_URL = os.environ.get("AZTA_BASE_URL", "https://api.azta.ai/v1")

# The last item is the new user question.
history = [
    {"role": "user", "content": "Mennyi az áfa mértéke?"},
    {"role": "assistant", "content": "Az általános kulcs 27%."},
    {"role": "user", "content": "És építőipari szolgáltatásnál?"},
]
# One key per question, reused on every retry of it.
idempotency_key = str(uuid.uuid4())


def retry_after(response: httpx.Response, default: float) -> float:
    value = response.headers.get("retry-after", "")
    return float(value) if value.isdigit() else default


with httpx.Client(headers={"Authorization": f"Bearer {API_KEY}"}) as client:
    for attempt in range(1, 4):
        created = client.post(
            f"{BASE_URL}/responses",
            json={"model": "azta-agent", "background": True, "input": history},
            headers={"Idempotency-Key": idempotency_key},
        )
        if created.status_code in (200, 202):
            break
        if created.status_code not in (429, 500, 503) or attempt == 3:
            sys.exit(f"Error: http_{created.status_code}")
        time.sleep(retry_after(created, 1))
    while True:
        polled = client.get(f"{BASE_URL}/responses/{created.json()['id']}")
        if polled.status_code in (429, 503):
            time.sleep(retry_after(polled, 5))
            continue
        if polled.status_code != 200:
            sys.exit(f"Error: http_{polled.status_code}")
        data = polled.json()
        if data["status"] == "completed":
            break
        if data["status"] in ("failed", "cancelled"):
            sys.exit(f"Error: {(data.get('error') or {}).get('code', data['status'])}")
        time.sleep(5)

print(
    "".join(
        part["text"]
        for message in data["output"]
        for part in message["content"]
        if part["type"] == "output_text"
    )
)
```

```ts tab="TypeScript"
// Ask a follow-up. The API is stateless: send the whole conversation as input.
// Run with Node 24 or later: node follow-up.ts
import { randomUUID } from 'node:crypto';
import { setTimeout as sleep } from 'node:timers/promises';

const apiKey = process.env.AZTA_API_KEY;
if (!apiKey) {
  console.error('Set AZTA_API_KEY to your Azta API key');
  process.exit(1);
}
const baseUrl = process.env.AZTA_BASE_URL ?? 'https://api.azta.ai/v1';

// The last item is the new user question.
const history = [
  { role: 'user', content: 'Mennyi az áfa mértéke?' },
  { role: 'assistant', content: 'Az általános kulcs 27%.' },
  { role: 'user', content: 'És építőipari szolgáltatásnál?' },
];
// One key per question, reused on every retry of it.
const idempotencyKey = randomUUID();
const auth = { Authorization: `Bearer ${apiKey}` };

type Reply = {
  id: string;
  status: string;
  error: { code: string } | null;
  output: { content: { type: string; text: string }[] }[];
};

const retryAfter = (res: Response, fallback: number): number => {
  const value = res.headers.get('retry-after') ?? '';
  return /^\d+$/.test(value) ? Number(value) : fallback;
};

const create = async (): Promise<string> => {
  for (let attempt = 1; ; attempt += 1) {
    const res = await fetch(`${baseUrl}/responses`, {
      method: 'POST',
      headers: {
        ...auth,
        'Content-Type': 'application/json',
        'Idempotency-Key': idempotencyKey,
      },
      body: JSON.stringify({
        model: 'azta-agent',
        background: true,
        input: history,
      }),
    });
    if (res.status === 200 || res.status === 202) {
      return ((await res.json()) as Reply).id;
    }
    if (![429, 500, 503].includes(res.status) || attempt === 3) {
      console.error(`Error: http_${res.status}`);
      process.exit(1);
    }
    await sleep(retryAfter(res, 1) * 1000);
  }
};

const id = await create();
for (;;) {
  const res = await fetch(`${baseUrl}/responses/${id}`, { headers: auth });
  if (res.status === 429 || res.status === 503) {
    await sleep(retryAfter(res, 5) * 1000);
    continue;
  }
  if (res.status !== 200) {
    console.error(`Error: http_${res.status}`);
    process.exit(1);
  }
  const data = (await res.json()) as Reply;
  if (data.status === 'completed') {
    console.log(
      data.output
        .flatMap((m) => m.content)
        .filter((p) => p.type === 'output_text')
        .map((p) => p.text)
        .join(''),
    );
    break;
  }
  if (data.status === 'failed' || data.status === 'cancelled') {
    console.error(`Error: ${data.error?.code ?? data.status}`);
    process.exit(1);
  }
  await sleep(5000);
}
```

A previous response's `output` can go back as an item, unchanged, so you do not need to rebuild it. Only the most recent turns are used: at most 40 messages and 200,000 characters, oldest dropped first, each turn kept whole or not at all. A `system` or `developer` item is accepted and not replayed into the model, so put instructions in the user turn.

`previous_response_id` and `conversation` are refused with `400` `unsupported_parameter`, because there is no server-side conversation to point at.

## Idempotency-Key

Send an `Idempotency-Key` header on every `POST` under `/v1`: 1 to 255 printable ASCII characters, scoped to your API key and kept for 24 hours. A retry that reuses the key is the same request and is charged once. **A retry without `Idempotency-Key` is a new request and is charged again.** The OpenAI SDKs add no such header on their own retries, so set it yourself.

| Repeat                                      | Answer                                                        |
| ------------------------------------------- | ------------------------------------------------------------- |
| Same key, same request, the response exists | The stored response as JSON, with `Idempotent-Replayed: true` |
| Same key, still being processed             | `409` `idempotency_key_in_use`                                |
| Same key, a different request               | `400` `idempotency_key_reused`                                |

A request that is refused frees its key, so you can fix it and send it again. See [Errors](/guides/errors) for which failures to retry.
