Skip to main content
Emit a piece of content for the brand: validates it against the target persona’s guardrails, saves a draft at status=‘queued’, and records the action so its citation / share-of-voice impact can be measured over time. This is the one-call way to record a complete content action. If you want staged control — draft now, schedule later, mark posted when it goes live — use create_content_draft / schedule_content / mark_content_posted instead. Use this when:
  • The user (or upstream agent) has decided WHAT to post and on WHICH channel.
  • You want one atomic emit that records the action for later outcome measurement.
  • You’re building a hub-spoke fan-out: set fan_out_role=‘hub’ on the long-form piece, then call emit_content again with fan_out_role=‘spoke’ + parent_action_id=<hub action id> for each channel adaptation.
Skip when:
  • The user is brainstorming (no decision yet) — ingest_conversation instead.
  • The content is a paid ad — use emit_paid.
  • The action is an outreach DM/email — use emit_outreach.
Persona guardrails: the buyer persona (persona or persona_id) + intent are REQUIRED — every content emit is validated against the persona guardrails, as brand voice unless voice_persona_id says otherwise. If a blocking violation fires, fix the body to clear it, or pass override_reason (≥ 8 chars) to proceed; overrides are recorded with your reason. Inputs:
  • persona or persona_id (required, one of them, never both): the buyer persona this content is for — give its name exactly as get_brand_context lists it (same capitals and spacing), or its id. Recorded on the content and the action so outcomes are measured per persona.
  • voice_persona_id (optional): the voice persona the content speaks as (founder, insider, community). Omit for brand voice, which is right for blog posts.
  • intent (required): ‘engagement’ (build relationships) or ‘commercial’ (drive a buy decision).
  • platform (required): linkedin / twitter / reddit / blog / instagram / youtube.
  • body (required, 1-8000 chars): the draft. Long-form for blogs; short-form for socials.
  • title (optional): caps 200 chars. Defaults to body[:80].
  • source_task_id OR from_conversation_id OR origin=‘editorial’ (required: one of): what the content answers — an action ticket, a conversation stored with ingest_conversation, or, for a news-led post that answers neither, origin=‘editorial’. Use a conversation only if it actually took place; never create one just to pass this check. Without an anchor, the draft is refused with ANCHOR_MISSING.
  • editorial_reason (required with origin=‘editorial’, ≥ 8 chars): why this post is worth publishing.
  • source_url (with origin=‘editorial’, when there is one): the news or source the post responds to.
  • action_subtype (optional): e.g. ‘long_form_hub’, ‘reddit_spoke’, ‘linkedin_spoke’.
  • fan_out_role (optional): ‘hub’ for the canonical piece; ‘spoke’ for adaptations.
  • parent_action_id (optional): when fan_out_role=‘spoke’, the hub action’s id.
  • target_prompt_ids (required, at least one): integer[] of tracked-question ids on this brand, as plan_question_bank returns them (not search_prompt_intelligence row ids, which are a different id for the same question), that this content targets. The post is measured by these. A spoke passes its hub’s questions (or its own).
  • override_reason (optional, ≥ 8 chars): bypass block-severity violations with audit.
Response: { actionId, contentId, deepLinkUrl, geoScore, artifactsFlagged, validation: { status, violations, overrideIds } }. (geoScore is the unified GEO/AEO citability score; call score_content for the explainable breakdown + per-dimension fixes.)

Inputs