AI Edit Brand Layout (stream)
POST/api/store-email-layouts/:storeId/ai-edit/stream
Server-Sent Events: one AI assistant turn on a brand layout document. :storeId is a store id, or default for the tenant default layout; layout_id in the body names the layout explicitly. An empty current_html uses the saved layout HTML.
settings:writeGrant this scope to your token under Settings → Developer → Personal Access Tokens.
Authentication: Requires Bearer token and the email_templates.update permission.
Body: layout_id (int|null), store_id (int|null), instruction (required, max 5000), current_html (max 200000), history, target, reference_images, provider, model (as for AI Edit Email Template).
The proposal is checked in layout mode ({{content}} exactly once, only layout tokens, no unsafe content) and trial-rendered around the order confirmation with sample data. complete has the same shape as the template stream with proposal.subject / proposal.preheader always null; status steps start with "Reading layout".
Errors arrive as an error event {type: "error", message, status?} on a 200 stream: the template editor switched off in AI settings (503), no API key for the provider (503), the hourly limit (AiSettings::max_requests_per_user_per_hour, default 60, per user per tenant; 429 — "You've hit the hourly AI limit; try again at 15:40."), provider failures mapped to friendly copy (quota, rate limit, bad key, timeout). Request validation failures are a normal 422 JSON response.
Nothing is saved: the editor applies the proposal to its buffer and the user saves (with version_source: ai). A proposal that fails the content checks is retried once with the errors fed back; still invalid → success: false, validation.ok: false, no rendered_preview_html.
reference_images (max 3; {mime_type, data (base64, no prefix), name} objects or data:image/...;base64, URIs; png/jpeg/webp/gif) are sent as vision input and stored as email assets so the proposal can embed them by URL; images the proposal does not embed are deleted again.
Request
Responses
- 200
- 401
- 403
- 404
- 422
- 429
OK
Response Headers
Unauthenticated — the bearer token is missing, revoked, expired, or malformed. Never retry automatically; fix the credential. See the Errors guide.
Forbidden
Response Headers
Not found — no record with the given identifier (or the route does not exist). Verify the ID before retrying.
Validation failed — the body is a field → messages map (Laravel shape) or the platform envelope with a stable machine-readable code. Fix the payload and resubmit.
Rate limited — platform limit is 1,000 requests/min; individual tokens may carry lower limits. Honor the Retry-After header before retrying. See the Rate Limits guide.