---
title: "Policy Scan Automation"
method: POST
path: "/v1/ai/policy_scan_automation"
tags: ["ai"]
---

# Policy Scan Automation

`POST /v1/ai/policy_scan_automation`

## Request

- Base URL: `https://api.linktodm.com` (the document declares no server; this is the origin it was published from)
- URL: `https://api.linktodm.com/v1/ai/policy_scan_automation`
- Auth: HTTP bearer

Scan a draft Automation for Meta Community Standards violations BEFORE
the user persists it. Frontend calls this on the create/edit form submit
and obeys the verdict (block / warn / allow).

Runs:
  - LLM scan against Meta policy KB (Sonnet 5 + caching)
  - Static checks: auto_reply_limit > threshold, keyword reuse on the
    same account's other automations + flows.

## Query parameters

- `exclude_automation_id` integer, nullable: When scanning an EDIT, pass the current automation id so the keyword-reuse check doesn't flag the automation's own keywords.

## Request body

- AutomationPayload
  - `name` string, required
  - `type` 'Auto DM Link' | 'Auto DM Products' | 'Button Text' | 'Attachment', required: Stub schema.
  - `facebook_account_id` string, nullable
  - `instagram_business_account_id` string, nullable
  - `instagram_user_id` string, nullable
  - `media_type` 'IMAGE' | 'VIDEO' | 'CAROUSEL_ALBUM'
  - `media_url` string, nullable
  - `media_id` string, nullable
  - `keywords` string[], nullable
  - `dm_message` string, nullable
  - `attachment_id` integer, nullable
  - `disable_auto_reply` boolean, nullable
  - `auto_reply_messages` string[], nullable
  - `auto_reply_mode` 'MANUAL' | 'AI'
  - `auto_reply_ai_prompt` string, nullable
  - `auto_reply_limit` integer, nullable
  - `product_links` ProductLink[], nullable
    - `url` string, required
    - `image_url` string, nullable
    - `title` string, nullable
    - `url_label` string, nullable
    - `addition_buttons` AdditionButtonSchema[], nullable
      - `url` string, required
      - `url_label` string, nullable
  - `addition_buttons` AdditionButtonSchema[], nullable
    - `url` string, required
    - `url_label` string, nullable
  - `dm_quick_replies` QuickReplySchema[], nullable
    - `id` string
    - `label` string, required
    - `action` 'SEND_MESSAGE' | 'CONNECT_AUTOMATION' | 'NOTHING', required: What tapping a quick-reply chip does.
    - `message_type` 'Auto DM Link' | 'Auto DM Products' | 'Button Text' | 'Attachment': Stub schema.
    - `dm_message` string, nullable
    - `automation_id` integer, nullable
    - `flow_automation_id` integer, nullable
  - `ask_to_follow` boolean, nullable
  - `extra_data` AutomationExtraData
    - `ask_to_follow_title` string, nullable
    - `ask_to_follow_button_label` string, nullable
    - `ask_to_follow_error_title` string, nullable
    - `ask_to_follow_error_button_label` string, nullable
    - `ask_to_follow_visit_profile_button_label` string, nullable
    - `opener_message_type` 'Button Text' | 'Quick Reply Text'
    - `opener_message` string, nullable
    - `opener_message_button_label` string, nullable
    - `opener_message_additional_buttons` AdditionButtonSchema[], nullable
      - `url` string, required
      - `url_label` string, nullable
  - `step_delays` AutomationStepDelays: Optional per-step delays. A missing/None entry means the step runs immediately after the previous one. Each delay is relative to the previous step, so the chain is: trigger -> comment -> opener -> ask_to_follow -> dm.
    - `comment` StepDelay: Wait applied before a step runs, relative to the previous step.
      - `delay_in_seconds` integer, required
      - `delay_unit` 'SECONDS' | 'MINUTES' | 'HOURS' | 'DAYS': Unit a user entered a wait duration in. Shared by the legacy automation step delays (`StepDelay.delay_unit`) and the flow builder's DELAY node / comment auto-reply delay. The backend always schedules with the canonical seconds value; this is what the UI round-trips so the editor can show the delay the way it was typed.
    - `opener` StepDelay: Wait applied before a step runs, relative to the previous step.
      - `delay_in_seconds` integer, required
      - `delay_unit` 'SECONDS' | 'MINUTES' | 'HOURS' | 'DAYS': Unit a user entered a wait duration in. Shared by the legacy automation step delays (`StepDelay.delay_unit`) and the flow builder's DELAY node / comment auto-reply delay. The backend always schedules with the canonical seconds value; this is what the UI round-trips so the editor can show the delay the way it was typed.
    - `ask_to_follow` StepDelay: Wait applied before a step runs, relative to the previous step.
      - `delay_in_seconds` integer, required
      - `delay_unit` 'SECONDS' | 'MINUTES' | 'HOURS' | 'DAYS': Unit a user entered a wait duration in. Shared by the legacy automation step delays (`StepDelay.delay_unit`) and the flow builder's DELAY node / comment auto-reply delay. The backend always schedules with the canonical seconds value; this is what the UI round-trips so the editor can show the delay the way it was typed.
    - `dm` StepDelay: Wait applied before a step runs, relative to the previous step.
      - `delay_in_seconds` integer, required
      - `delay_unit` 'SECONDS' | 'MINUTES' | 'HOURS' | 'DAYS': Unit a user entered a wait duration in. Shared by the legacy automation step delays (`StepDelay.delay_unit`) and the flow builder's DELAY node / comment auto-reply delay. The backend always schedules with the canonical seconds value; this is what the UI round-trips so the editor can show the delay the way it was typed.
  - `is_active` boolean
  - `is_draft` boolean
  - `is_story` boolean, nullable
  - `is_universal` boolean, nullable
  - `trigger_type` 'STORY_MENTION' | 'CURRENT_MEDIA' | 'AD_MEDIA'
  - `disable_url_tracking` boolean, nullable
  - `disable_dm` boolean, nullable
  - `one_dm_per_user` boolean, nullable
  - `one_dm_per_user_window_hours` integer, nullable
  - `share_media_for_dm` boolean
  - `enable_opener_message` boolean, nullable
  - `keyword_trigger_type` 'ANY' | 'EXACT_MATCH' | 'EXCLUDE' | 'AI_INTENT'
  - `media_source` 'INSTAGRAM' | 'FACEBOOK'
  - `trigger_ai_prompt` string, nullable
  - `use_next_post` boolean, nullable
  - `use_next_story` boolean, nullable
  - `use_rewind` boolean, nullable
  - `follow_up_messages` FollowUpMessageSchema[], nullable
    - `follow_up_type` 'IF_NOT_CLICKED_LINK' | 'IF_CLICKED_LINK' | 'IF_NO_READ_MESSAGE' | 'IF_UNREACTED_TO_MESSAGE' | 'IF_NO_REPLY' | 'IF_REPLIED_TO_MESSAGE' | 'IF_SEEN_MESSAGE' | 'IF_UNSEEN_MESSAGE' | 'IF_REACTED_TO_MESSAGE_WITH_EMOJI' | 'IF_USER_FOLLOWED_ME' | 'IF_USER_UNFOLLOWED_ME' | 'ALWAYS', required
    - `message_type` 'TEXT' | 'IMAGE_BUTTON' | 'BUTTON', required
    - `dm_message` string, nullable
    - `delay_in_seconds` integer, nullable
    - `reaction_emoji` string, nullable
  - `collect_info_items` CollectInfoItem[], nullable
    - `message` string, required
    - `error_message` string, nullable
    - `type` 'EMAIL' | 'PHONE' | 'ANY', required
  - `dm_variations` AutomationDmVariationSchema[], nullable
    - `dm_message` string, nullable
    - `product_links` ProductLink[], nullable
      - `url` string, required
      - `image_url` string, nullable
      - `title` string, nullable
      - `url_label` string, nullable
      - `addition_buttons` AdditionButtonSchema[], nullable
        - `url` string, required
        - `url_label` string, nullable
    - `addition_buttons` AdditionButtonSchema[], nullable
      - `url` string, required
      - `url_label` string, nullable
    - `attachment_id` integer, nullable
    - `attachment_data` AttachmentItem
      - `id` integer, required
      - `meta_attachment_id` string, required
      - `file_url` string, required
      - `type` 'image' | 'video' | 'audio' | 'file', required
      - `filename` string, nullable
      - `content_type` string, nullable
      - `file_size` integer, nullable
      - `created_at` string, date-time, required

## Response `200`

Successful Response

- PolicyScanResponse: Response body for the policy_scan_automation / policy_scan_flow_automation endpoints. Pre-creation scans, so no automation_id is present. Note: model name is intentionally NOT exposed in the response. It's captured in Axiom logs for ops/cost monitoring but kept private — callers shouldn't depend on it.
  - `automation_type` 'automation' | 'flow_automation', required
  - `scanned_at` string, required
  - `policy_version` string, required
  - `verdict` 'allow' | 'warn' | 'block', required
  - `verdict_message` string, required
  - `verdict_category` string, nullable
  - `totals` ScanTotals, required
    - `items_total` integer, required
    - `items_scanned` integer, required
    - `items_failed` integer, required
    - `items_with_violations` integer, required
  - `highest_severity` 'none' | 'low' | 'medium' | 'high', required
  - `items` ScanItemResult[], required
    - `item_id` string, required
    - `item_type` string, required
    - `content_preview` string, required
    - `status` 'scanned' | 'scan_failed', required
    - `highest_severity` 'none' | 'low' | 'medium' | 'high', required
    - `violations` Violation[]
      - `category` string, required: Key of the Meta policy category. Must match a key from app.api.v1.ai.policy_scan.meta_policies.
      - `severity` 'low' | 'medium' | 'high', required
      - `evidence` string, required: Verbatim quote from the content showing the violation.
      - `explanation` string, required: One-sentence mapping of the evidence to the named category.
      - `policy_url` string, nullable

## Other responses

- `422`: Validation Error

---

[API](https://skmtc.dev/linktodm/apis/salesmorph.md) - [All operations](https://skmtc.dev/linktodm/apis/salesmorph/llms.txt) - [OpenAPI document](https://skmtc.dev/linktodm/apis/salesmorph/revisions/4af0ad5dbe92?raw)
