Skip to main content
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:
{
  "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:
{
  "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 fragmentMissingFix
”with product screenshots”imagesUpload via POST /images, include the image_id in image_assets
”using the attached CSV”data_sourcesUpload via POST /files, reference in data_sources
”based on these contracts”reference_filesUpload and reference in context_files
”in our brand colors”brandInclude 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:
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.