# OpenAI text generation

Limits, free use, where the answer is, every generation_code, pending and unknown results, and how to ask for help with OpenAI text generation.

Flovia's OpenAI text generation is in alpha and may change. Your agent uses it through the `flovia_openai` tool, or over HTTP at `/openai/v1/responses` with a `Flovia-Operation-Id` header.

## Limits

- The input is one text of at most 16,000 tokens and 64 KiB. A text over 64 KiB is refused with `invalid_request`, and one over 16,000 tokens with `invalid_input_count`. Nothing is generated or charged.
- `max_output_tokens` is 1,000 when you leave it out, and at most 4,000. The 4,000 includes the model's reasoning, so a higher reasoning effort leaves less room for the answer.

For `invalid_input_count`, the refusal gives the counted tokens and the limit when the input is too long. For example, an input counted as 16,001 tokens gets: "The input is 16,001 input tokens, over the limit of 16,000. Nothing was generated or charged. Shorten the input, then send it once with a new operation ID if it is still needed."

When the maximum cost is too high, the message is: "The maximum cost exceeds the supported limit. Nothing was generated or charged. Lower max_output_tokens (or shorten the input), then send it once with a new operation ID if it is still needed." Other input count or cost failures use: "The input count or maximum cost exceeds the supported limit."

## One free generation at a time

Generations paid from free use run one at a time for each account. Data lookups do not take this slot.

If you start another free generation while one is still running, Flovia refuses it with HTTP 429 and `generation_code` `parallel_limit_free`. Over MCP, `explanation.code` is `too_many_in_parallel`. The refusal has `retry_after_seconds` 30, and over HTTP also a `Retry-After` header of 30. Nothing is generated or charged, and that operation is closed. Wait 30 seconds, then send it once with a new operation_id only if you still need the answer. Do not loop.

## Where the answer is

- Over MCP, `flovia_openai` returns the answer text in `output_text`. Inside `data.output`, each text part shows `(see output_text)` instead of repeating the text. Check `status` first: `incomplete` means the answer stopped early.
- Over HTTP, the reply is OpenAI's Responses object, which has no top-level `output_text`. The text is in the `output` items: join the `text` of every `content` part whose `type` is `output_text`, in order. The first `output` item can be a `reasoning` item without text, so do not read only the first item.

## generation_code values

Over HTTP, a refusal's `error.code` is its `generation_code`. Over MCP, `explanation.code` is Flovia's shared code and `explanation.generation_code` is the generation's own reason. `executed` says whether the generation ran: `no`, `yes` or `unknown`. Refusals about consent or a stopped key use Flovia's shared codes.

| `generation_code` | MCP `explanation.code` | `executed` | What it means and what to do |
| --- | --- | --- | --- |
| `relay_internal_error` | `relay_internal_error` | `no` | Nothing was sent, stored or charged. Retry later once with the same operation_id. |
| `flovia_paused` | `flovia_paused` | `no` | Flovia is paused. Follow next_step and wait. |
| `service_off` | `service_off` | `no` | OpenAI is off. Turn it on with flovia_services if wanted, then retry with the same operation_id. |
| `operation_retired` | `replay_response_unavailable` | `unknown` | The request matching data was forgotten. The operation is closed; do not generate again automatically. |
| `answer_expired` | `replay_response_unavailable` | `yes` | It already ran, but its answer is unavailable. Ask the user before starting a new operation. |
| `answer_unavailable` | `replay_response_unavailable` | `yes` | It already ran, but its answer is unavailable. Ask the user before starting a new operation. |
| `answer_delivery_denied` | `replay_response_unavailable` | `yes` | It already ran, but the answer cannot be delivered. Follow next_step. |
| `generation_unknown` | `upstream_unknown` | `unknown` | It may have run. Keep the same operation_id; do not generate again. |
| `record_unavailable` | `upstream_unknown` | `unknown` | The record is unavailable. Keep the same operation_id; do not start a replacement. |
| `generation_pending` | `operation_pending` | `unknown` | Still running. Wait, then read it with the same operation_id and request. |
| `operation_conflict` | `operation_conflict` | `no` | The operation_id belongs to a different request. Keep the original request when reading it. |
| `invalid_operation_id` | `invalid_body` | `no` | Over MCP: "Send operation_id (the Flovia-Operation-Id header for HTTP): 8-128 characters of A-Z a-z 0-9 _ . : -. Save it before sending and reuse the same ID for every retry of this generation." Over HTTP: "Send the Flovia-Operation-Id header: 8-128 characters of A-Z a-z 0-9 _ . : -. Save it before sending and reuse the same ID for every retry of this generation." A secret-looking ID gets: "Use a non-secret operation ID for the generation." |
| `invalid_request` | `invalid_body` | `no` | The request is unsupported. Check its text, fields and limits. |
| `openai_access_required` | `not_configured` | `no` | OpenAI is unavailable for this account. Ask for a review with flovia_feedback. |
| `openai_not_configured` | `not_configured` | `no` | OpenAI is not configured. Continue without it and follow next_step. |
| `generation_unsent` | `upstream_unavailable` | `no` | Nothing was generated or charged. This operation is closed. If you received the first reply, check it for the refusal reason; that reason is not stored here. Fix the cause and use a new operation ID only if the user still needs it. |
| `model_busy` | `rate_limited` | `no` | Nothing was sent, stored or charged. Wait retry_after_seconds, then retry once with the same operation_id. |
| `busy` | `upstream_unavailable` | `no` | The generation could not start. Follow next_step; nothing was generated or charged. |
| `client_gone` | `upstream_unavailable` | `no` | The request closed before generation. Nothing was generated or charged. |
| `day_cap` | `rate_limited` | `no` | This account's daily OpenAI limit is reached. Nothing was sent or charged. Wait until resets_at, then retry once with the same operation ID if it is still needed. Do not retry in a loop. |
| `global_cap` | `global_cap` | `no` | OpenAI is temporarily unavailable. Continue without it; do not retry in this conversation. |
| `parallel_limit_free` | `too_many_in_parallel` | `no` | Another free generation is running. Wait retry_after_seconds, then use a new operation_id only if still needed. |
| `invalid_input_count` | `upstream_unavailable` | `no` | Nothing was generated or charged. An input over the token limit gets its counted tokens and the limit: shorten the input, then send it once with a new operation ID if it is still needed. For a maximum cost refusal, lower max_output_tokens (or shorten the input), then send it once with a new operation ID if it is still needed. See Limits for the exact messages and the fallback for other input count or cost failures. |
| `capacity_or_credit_limit` | `upstream_unavailable` | `no` | Capacity or budget is unavailable. The operation is closed; send once with a new operation_id only if still needed and available. |
| `count_unavailable` | `upstream_unavailable` | `no` | The input could not be counted. Nothing was generated or charged. Follow next_step. |
| `request_blocked` | `invalid_body` | `no` | This request cannot be processed. Nothing was generated or charged. |
| `safety_check_unavailable` | `upstream_unavailable` | `no` | The safety check is unavailable. Nothing was generated or charged. Follow next_step. |
| `upstream_unavailable` | `upstream_unavailable` | `no` | This is a Flovia connection problem. Do not retry; report it with flovia_feedback. |
| `upstream_rate_limited` | `rate_limited` | `no` | Wait at least retry_after_seconds; try once with a new operation_id only if still needed. |
| `answer_too_large_for_tool` | `replay_response_unavailable` | `yes` | It settled, but the answer is too large for the MCP tool. Open the same operation over HTTP within 15 minutes. |

## Pending, unknown and long generations

- Over MCP, `flovia_openai` waits about 25 seconds. If the generation is still running, the reply has `generation_code` `generation_pending` (`explanation.code` `operation_pending`) and `retry_after_seconds` 30, and the generation keeps running.
- Wait 30 seconds, then call `flovia_openai` again with the same operation_id and the same request, or call `flovia_lookup` with `openai:<generation_id>`. Neither starts a new generation.
- `generation_unknown` means it may have run. Keep the same operation_id, do not generate again, and tell the user the result is unknown.
- Over HTTP, Flovia answers when the generation ends, which can take minutes, so set a long client timeout. If the connection drops, send the same request again with the same `Flovia-Operation-Id`.
- The answer is kept for about 15 minutes, for you only.
- Never send it again with a new operation_id while it is pending or unknown.

## OpenAI in flovia_status

- `services.openai.state` is `normal` when you can start generations.
- It is `paused` when Flovia has paused OpenAI for now: Flovia is paused, Flovia's monthly OpenAI budget is used up, or this account's OpenAI access is under review. Existing operations can still be read.
- It is `off` when you turned OpenAI off. Turn it back on with `flovia_services`.
- `services.openai` is missing when OpenAI is not offered to your account.
- `stopped` appears only when Flovia stopped your key; when it is missing, your key is not stopped.
- `free_pending` appears only while free use is not granted to your account yet. Its `reason` is `email_not_verified` or `free_not_granted`, and its `next_step` says what to do.
- When `free_pending` is missing, free use is granted, which does not mean any of it has been used. The `five_hour` and `week` windows show how much is used and when each resets.

## Ask for help with a generation

- Keep the `generation_id`. Over MCP it is in the reply; over HTTP it is the `Flovia-Generation-Id` header. A refusal before a generation was recorded has none: give the operation_id and the time instead.
- Send it with `flovia_feedback`, with `about` set to `["openai:<generation_id>"]`, and say in your own words what went wrong. Do not paste keys or other secrets.
