odysseus-dev/odysseus · warning · HTTPException
Invalid draft payload: {exc}
Error message
Invalid draft payload: {exc} What it means
Raised as HTTP 400 by POST /api/codex/emails/draft (and the send sibling) when the JSON body fails to construct routes.email_routes.SendEmailRequest — a pydantic-style request model. The exception text is appended verbatim, so the message enumerates exactly which fields failed validation (missing required fields, wrong types, unknown constraints).
Source
Thrown at routes/codex_routes.py:373
)
result = await _as_owner(request, owner, documents_create_endpoint, request, req)
if isinstance(result, dict):
result = dict(result)
result["draft_type"] = "document"
result["send_required_confirmation"] = True
return result
@router.post("/emails/draft")
async def codex_email_draft(request: Request, body: dict[str, Any] = Body(default_factory=dict)):
owner = _scope_owner(request, EMAIL_DRAFT_SCOPES)
if email_draft_endpoint is None:
raise HTTPException(503, "Email integration is not available")
from routes.email_routes import SendEmailRequest
try:
req = SendEmailRequest(**body)
except Exception as exc:
raise HTTPException(400, f"Invalid draft payload: {exc}")
return await email_draft_endpoint(req=req, owner=owner)
@router.post("/emails/send")
async def codex_email_send(request: Request, body: dict[str, Any] = Body(default_factory=dict)):
owner = _scope_owner(request, EMAIL_SEND_SCOPES)
if email_send_endpoint is None:
raise HTTPException(503, "Email integration is not available")
from routes.email_routes import SendEmailRequest
try:
req = SendEmailRequest(**body)
except Exception as exc:
raise HTTPException(400, f"Invalid send payload: {exc}")
return await email_send_endpoint(req=req, background_tasks=BackgroundTasks(), owner=owner)
# ── Memory ────────────────────────────────────────────────────────────
@router.get("/memory")View on GitHub (pinned to f9235ebbf1)
Solutions
- Read the interpolated pydantic error in the message — it names the offending field(s); fix the payload accordingly.
- Fetch/inspect SendEmailRequest's current field list (routes/email_routes.py) and send all required fields with correct types.
- Ensure the request sends a JSON object with Content-Type: application/json.
- After an upgrade, re-check the model: renamed or newly-required fields are the usual breakers.
Example fix
# before
curl -X POST .../api/codex/emails/draft -d '{"body": "hello"}' # 400 Invalid draft payload
# after
curl -X POST .../api/codex/emails/draft -H 'Content-Type: application/json' \
-d '{"to": "a@b.com", "subject": "hi", "body": "hello"}' Defensive patterns
Strategy: validation
Validate before calling
function validDraftPayload(b) {
return !!b && typeof b === 'object'
&& typeof b.to === 'string' && b.to.includes('@')
&& typeof b.subject === 'string'
&& typeof b.body === 'string';
}
if (!validDraftPayload(body)) throw new Error('fix payload before sending'); Type guard
interface SendEmailRequest { to: string; subject: string; body: string }
function isSendEmailRequest(v: unknown): v is SendEmailRequest {
const o = v as Record<string, unknown>;
return typeof o?.to === 'string' && typeof o?.subject === 'string' && typeof o?.body === 'string';
} Try / catch
try { r = await codexEmailDraft(body) } catch (e) { if (e.status === 400 && e.detail.startsWith('Invalid draft payload')) { showFieldErrors(e.detail) } else throw } Prevention
- Validate against the SendEmailRequest model shape client-side before POSTing.
- Always send Content-Type: application/json with a JSON object body.
- Re-read the model definition after upgrades — field renames/new required fields are the usual breakers.
When it happens
Trigger: POSTing a body missing required fields (e.g. no recipient/subject/content depending on the model), sending strings where the model expects other types, extra fields rejected by the model config, or sending a raw string body instead of a JSON object.
Common situations: Codex plugin or script submitting a partial draft (only 'body' text); client omits Content-Type: application/json so the body parses as garbage; field renames between versions (to -> recipient) breaking older clients.
Related errors
- Invalid send payload: {exc}
- Preset payload invalid: {exc}
- await res.text()
- Cannot create reply: sender address is missing from this ema
- Email account not found for selector {account!r}. Available
AI-assisted analysis of odysseus-dev/odysseus@f9235ebbf1 (2026-08-14).
Data as JSON: /api/errors/d17f1404d816c582.
Report an issue: GitHub.