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

# Preflight validation

> Why your request might be rejected before the agent runs — and what to do about it.

Every generation request goes through a lightweight **preflight check**
before the agent runs. This is a single cheap LLM call that reads your
prompt and the metadata of any assets you attached, and decides:

1. **Is this fulfillable?** If the prompt references something that isn't
   provided — like "embed product screenshots" when you attached no images —
   we reject with `400 preflight_failed` before wasting credits.
2. **What intents does the agent need to plan for?** Tags like `visual_first`,
   `data_heavy`, `financial_model` are passed to the agent so it picks
   sensible defaults.

## Why we do this

The tradeoff is deliberately in the client's favor: a \~\$0.0005 preflight
call prevents a \$0.10 agent run from producing mediocre output because a
required asset was missing. It also gives the client a clear error message
to fix, instead of a broken document with a placeholder `[IMAGE MISSING]`.

## What preflight sees

The validator **only sees metadata** — names, counts, tags, flags.
It never sees the actual bytes of any asset. The prompt and a summary
like this:

```json theme={null}
{
  "prompt": "10-slide Q3 investor update with product screenshots",
  "format": "slides",
  "mode": "action",
  "assets_summary": {
    "images": { "count": 0, "items": [] },
    "data_sources": { "count": 1, "items": [{"name": "mrr.csv", "kind": "csv"}] },
    "reference_files": { "count": 0, "items": [] },
    "brand": { "has_logo": false, "has_colors": false, "has_font": false }
  }
}
```

And the response:

```json theme={null}
{
  "valid": false,
  "missing": ["images"],
  "reason": "Prompt requests 'product screenshots' but no image assets were provided.",
  "detected_intents": ["visual_first", "data_heavy"]
}
```

## Common rejection patterns

| Prompt fragment            | Missing           | Fix                                                                 |
| -------------------------- | ----------------- | ------------------------------------------------------------------- |
| "with product screenshots" | `images`          | Upload via `POST /images`, include the `image_id` in `image_assets` |
| "using the attached CSV"   | `data_sources`    | Upload via `POST /files`, reference in `data_sources`               |
| "based on these contracts" | `reference_files` | Upload and reference in `context_files`                             |
| "in our brand colors"      | `brand`           | Include `brand: {primary_color, font, logo_url}` in request         |

## Testing requests before submitting

If you want to check whether a request will pass without spending
credits, hit the standalone preflight endpoint:

```bash theme={null}
POST /api/v1/preflight
{
  "prompt": "10-slide investor update",
  "format": "slides"
}
```

Response is the same verdict the real endpoint would return. No run is
created; no credits are deducted.

## When preflight fails open

If the preflight LLM is slow (>20s) or returns malformed output, we
**fail open** — the request proceeds as if `valid: true`. We log the
event and alert internally; the agent will still attempt the work. This
prevents transient validator issues from blocking the API.

## Tuning the rejection bar

If you're hitting false positives ("my prompt wasn't really asking for
images but preflight rejected it"), a few options:

* **Rephrase the prompt** — the validator is pattern-based, so softer
  phrasing helps ("include visuals where appropriate" won't reject, "with
  product screenshots" will)
* **Attach generic assets** — for visual-first decks, uploading one
  placeholder image lets the validator pass; the agent may or may not use it
* **File an issue** — we tune the prompt continuously; false positives are
  a direct input into that tuning

## Under the hood

Implementation lives in `services/api_documents/preflight.py` in the
backend. Uses the same `TrackingContext` pattern as the main agents, so
preflight cost shows up in your Langfuse traces tagged
`purpose=api_preflight`. p50 latency is \~400ms; p95 \~800ms.
