---
title: "Update automation"
method: PATCH
path: "/automations/{automation_id}"
tags: ["Automations"]
---

# Update automation

`PATCH /automations/{automation_id}`

Partial update across all 4 types. Cannot change automation_type, shop, lifecycle state, or system fields. `Idempotency-Key` optional; `X-Dry-Run: true` supported.

## Path parameters

- `automation_id` integer, required

## Headers

- `Idempotency-Key` string, nullable
- `X-Dry-Run` string, nullable

## Request body

- AutomationUpdateRequest — PATCH /automations/{id} — partial update across all 4 types. Cannot change type / lifecycle fields / system fields. The set of legitimately mutable fields varies by type, but for v1 we accept the union and let the route validate type-specific constraints.
  - `automation_name` string, nullable
  - `schedule` AutomationSchedule — Per-day creator-outreach caps + run window. Stored into Automations.schedule JSONB. Same shape as the existing portal payload — keeps the portal and Public API agreeing on schema.
    - `Monday_maxCreators` integer
    - `Tuesday_maxCreators` integer
    - `Wednesday_maxCreators` integer
    - `Thursday_maxCreators` integer
    - `Friday_maxCreators` integer
    - `Saturday_maxCreators` integer
    - `Sunday_maxCreators` integer
    - `start_time` string, nullable — HH:MM 24-hour, in `timezone`. Hours 00-23, minutes 00-59.
    - `end_time` string, nullable — HH:MM 24-hour, in `timezone`. Hours 00-23, minutes 00-59.
    - `timezone` string, nullable — IANA timezone name, e.g. America/Los_Angeles
  - `creators_to_include` CreatorsToInclude — Outreach-paradigm recipient inclusion. Three mutually-exclusive modes (cf. INTERNAL-design-review.md §11A): * `lists` mode — `list_upload` and/or `lists_selected` populated * `filters` mode — `filters` populated (dynamic by attribute) * `crm_group` mode — `crm_group_id` populated (saved predicate) The validator below rejects any request that mixes modes. The `creator_source_type` discriminator written to `config` is derived from which mode is set — callers don't need to set it explicitly.
    - `list_upload` string[] — Ad-hoc creator handles (TikTok @-names) to include in this automation only. NOT persisted as a Lists row.
    - `lists_selected` string[] — IDs of existing Lists (vault_files.resource_id) whose creators should be included. Use `POST /lists` to upload reusable lists.
    - `filters` CreatorFilters — Dynamic creator selection by attribute. When `is_evergreen=true` on the parent automation, the filter is re-evaluated on every run. Mirrors the portal's "Filters" tab (formDataTransformer.ts:735-824 `buildFilters` for the legacy `config.Filters` shape, and `buildAISearchFilters` for the AI-search variant). The translator splits AI-only fields out into `config.ai_search_filters` and sets `config.uses_ai_filters=true` accordingly. All fields are optional — set only the ones you care about. An empty `CreatorFilters({})` is invalid (use `lists` mode instead).
      - `product_categories` string[], nullable — TikTok Shop product category names (e.g. 'Beauty & Personal Care').
      - `follower_count` FilterRange — Inclusive numeric range. At least one of `min` / `max` must be set; the other is open-ended. Used by `CreatorFilters` for every range-style filter (GMV, follower count, average views, engagement rate, etc.). API exposes `{min, max}` objects; the translator converts to the legacy `["custom:min-max"]` string form `config.Filters` expects.
        - `min` number, nullable
        - `max` number, nullable
      - `creator_ethnicity` string[], nullable
      - `gender` string[], nullable
      - `age_range` string[], nullable — Age buckets, e.g. ['18-24', '25-34']. Buckets are platform-defined.
      - `languages` string[], nullable
      - `community` string[], nullable — Community hashtags the creators post under. Available where the community-hashtag pipeline has coverage (currently US/UK/DE/ES) — GET /ai-search/hashtags is the live source of truth and returns valid hashtags for your region (404 where unsupported); a leading '#' is optional. Resolves the same audience the portal's community filter builds — the lever behind large niche segments (e.g. fitness) that product category alone can't target.
      - `gmv` FilterRange — Inclusive numeric range. At least one of `min` / `max` must be set; the other is open-ended. Used by `CreatorFilters` for every range-style filter (GMV, follower count, average views, engagement rate, etc.). API exposes `{min, max}` objects; the translator converts to the legacy `["custom:min-max"]` string form `config.Filters` expects.
        - `min` number, nullable
        - `max` number, nullable
      - `units_sold` FilterRange — Inclusive numeric range. At least one of `min` / `max` must be set; the other is open-ended. Used by `CreatorFilters` for every range-style filter (GMV, follower count, average views, engagement rate, etc.). API exposes `{min, max}` objects; the translator converts to the legacy `["custom:min-max"]` string form `config.Filters` expects.
        - `min` number, nullable
        - `max` number, nullable
      - `average_views` FilterRange — Inclusive numeric range. At least one of `min` / `max` must be set; the other is open-ended. Used by `CreatorFilters` for every range-style filter (GMV, follower count, average views, engagement rate, etc.). API exposes `{min, max}` objects; the translator converts to the legacy `["custom:min-max"]` string form `config.Filters` expects.
        - `min` number, nullable
        - `max` number, nullable
      - `engagement_rate` FilterRange — Inclusive numeric range. At least one of `min` / `max` must be set; the other is open-ended. Used by `CreatorFilters` for every range-style filter (GMV, follower count, average views, engagement rate, etc.). API exposes `{min, max}` objects; the translator converts to the legacy `["custom:min-max"]` string form `config.Filters` expects.
        - `min` number, nullable
        - `max` number, nullable
      - `post_rate` FilterRange — Inclusive numeric range. At least one of `min` / `max` must be set; the other is open-ended. Used by `CreatorFilters` for every range-style filter (GMV, follower count, average views, engagement rate, etc.). API exposes `{min, max}` objects; the translator converts to the legacy `["custom:min-max"]` string form `config.Filters` expects.
        - `min` number, nullable
        - `max` number, nullable
      - `video_gpm` FilterRange — Inclusive numeric range. At least one of `min` / `max` must be set; the other is open-ended. Used by `CreatorFilters` for every range-style filter (GMV, follower count, average views, engagement rate, etc.). API exposes `{min, max}` objects; the translator converts to the legacy `["custom:min-max"]` string form `config.Filters` expects.
        - `min` number, nullable
        - `max` number, nullable
      - `live_gpm` FilterRange — Inclusive numeric range. At least one of `min` / `max` must be set; the other is open-ended. Used by `CreatorFilters` for every range-style filter (GMV, follower count, average views, engagement rate, etc.). API exposes `{min, max}` objects; the translator converts to the legacy `["custom:min-max"]` string form `config.Filters` expects.
        - `min` number, nullable
        - `max` number, nullable
      - `follower_age` string[], nullable
      - `follower_gender` string[], nullable
      - `face_visibility` string[], nullable
      - `content_style` string[], nullable
      - `production_quality` string[], nullable
      - `personality_tone` string[], nullable
      - `body_type` string[], nullable
      - `hair_type` string[], nullable
    - `crm_group_id` string, nullable — Single CRM group ID (UUID string). Membership is dynamic — the runtime re-evaluates on every send. Mutually exclusive with the other two modes. Use `GET /crm-groups` to discover available groups (returns the canonical IDs to pass here).
    - `crm_group_entered_after` string, date, nullable — Optional CRM-group filter — only include members who entered the group ON or AFTER this date. Ignored unless `crm_group_id` is set.
    - `crm_group_entered_before` string, date, nullable — Optional CRM-group filter — only include members who entered the group ON or BEFORE this date. Ignored unless `crm_group_id` is set.
  - `creators_to_exclude` CreatorsToExclude — Outreach-paradigm recipient exclusion. Mirrors `creators_to_omit` in the legacy DB column / frontend payload, plus the boolean `exclude_previously_messaged` flag from the portal's Exclude Creators panel.
    - `list_upload` string[]
    - `lists_selected` string[]
    - `crm_groups` string[] — UUIDs of existing CRM Groups whose current members should be excluded. Use `GET /crm-groups` to discover.
    - `exclude_previously_messaged` boolean — Skip creators this shop has DM'd before in any prior automation. Maps to config.exclude_previously_messaged.
  - `messages` MessageAddon[], nullable — Replace the initial-outreach addon set. ALL items are sent immediately together when the automation engages a creator. Pass `[]` to clear messages (invite-only). Use `follow_ups` for delayed sequencing. Only valid for `target_collab` and `dm` automations.
    - `type` 'message' | 'image' | 'text_with_image' | 'product_card' | 'spark_code_form' | 'target_invite', required
    - `body` string, nullable — Message body (REQUIRED for type=message and type=text_with_image). Supports `{creator_name}` placeholder.
    - `image_url` string, nullable — Hosted HTTPS URL of the image (REQUIRED for type=image and type=text_with_image). Reacher fetches this URL server-side and re-hosts on the internal CDN — file uploads (multipart) are NOT accepted. Reject if `http://`, `data:`, `blob:`, or scheme-relative.
    - `product_id` string, nullable — Product ID (REQUIRED for type=product_card; OPTIONAL for type=target_invite).
    - `submission_url_slug` string, nullable — Spark code form slug (REQUIRED for type=spark_code_form).
  - `follow_ups` FollowUpStep[], nullable — Replace the delayed follow-up sequence. Pass `[]` to clear. Only valid for `target_collab` and `dm` automations.
    - `delay_days` integer, required — Days to wait after the prior step (or after initial outreach for the first follow-up).
    - `addons` MessageAddon[], required — Addons sent together when this follow-up step fires. Only `message` and `image` addon types are supported in follow-ups; `product_card`, `spark_code_form`, and `target_invite` are initial-outreach-only — use `messages` for those.
      - `type` 'message' | 'image' | 'text_with_image' | 'product_card' | 'spark_code_form' | 'target_invite', required
      - `body` string, nullable — Message body (REQUIRED for type=message and type=text_with_image). Supports `{creator_name}` placeholder.
      - `image_url` string, nullable — Hosted HTTPS URL of the image (REQUIRED for type=image and type=text_with_image). Reacher fetches this URL server-side and re-hosts on the internal CDN — file uploads (multipart) are NOT accepted. Reject if `http://`, `data:`, `blob:`, or scheme-relative.
      - `product_id` string, nullable — Product ID (REQUIRED for type=product_card; OPTIONAL for type=target_invite).
      - `submission_url_slug` string, nullable — Spark code form slug (REQUIRED for type=spark_code_form).
  - `target_collab` TargetCollabPatchPayload — All-Optional companion to `TargetCollabConfig` for PATCH bodies. `extra="forbid"` rejects typos so a PATCH like `{invitatoin_name: "..."}` returns 422 instead of silently no-op'ing.
    - `invitation_name` string, nullable
    - `valid_until` string, date, nullable
    - `message` string, nullable
    - `products` TargetCollabProduct[], nullable
      - `product_id` string, required — TikTok Shop product ID.
      - `commission_rate` number, required — Affiliate commission rate (0.0-1.0, e.g. 0.20 for 20%).
      - `shop_min_commission` number, nullable
      - `shop_ads_commission_rate` number, nullable — Optional Shop Ads (co-funded) commission rate (0.0-1.0). Persisted into the legacy PRODUCTS blob; the TC runtime fills it on the TC form when present.
    - `content_type` 'no_preference' | 'shoppable_video' | 'shoppable_live', nullable
    - `sample_policy` TargetCollabSamplePolicyPatch
      - `offer_free_samples` boolean, nullable
      - `auto_approve` boolean, nullable
      - `send_to` 'brand_recipient' | 'mall_recipient', nullable
    - `support_contact` TargetCollabContactPatch
      - `email` string, nullable
      - `phone` string, nullable
    - `lock_receivers_profiles` boolean, nullable
  - `dm_config` DmConfigPatchPayload — All-Optional companion to `DmConfig` for PATCH bodies.
    - `spark_code` DmSparkCodeConfig — dm-mode=spark_code — TikTok spark code collection form.
      - `only_collect_creator_information` boolean
      - `spark_code_submission_url` string, required — Portal-hosted form URL. Auto-generated if not provided by the route.
  - `sample_request` SampleRequestPatchPayload — All-Optional companion to `SampleRequestPayload` for PATCH bodies. Reuses the create-time nested types (`SampleRequestProducts`, `SampleRequestPerformanceCriteria`, `SampleRequestAutoApprovalSettings`) which already have `extra="forbid"`.
    - `action` 'approve' | 'reject', nullable
    - `rejection_reason` 'NOT_MATCH' | 'OFFLINE' | 'OUT_OF_STOCK' | 'OTHER', nullable
    - `approval_message` string, nullable
    - `rejection_message` string, nullable
    - `message_images` string[], nullable
    - `products` SampleRequestProducts — Sample-request product targeting. Two modes: * `include_all_products=True` → all shop products eligible; supply `weekly_auto_approval_per_product` for the global per-product cap. * `include_all_products=False` → enumerate `selected_products` with per-product `max_approvals_per_week` caps. Mirrors the legacy `sample_request_config.products` shape exactly so FE-built and API-built automations are interchangeable to the worker.
      - `include_all_products` boolean
      - `selected_products` SampleRequestProductCap[]
        - `product_id` string, required — TikTok Shop product ID (numeric string).
        - `max_approvals_per_week` integer, required — Per-product weekly auto-approval cap. Matches the V2 backend field name (`SampleRequestConfigModels.SelectedProduct.max_approvals_per_week`); 0 = no auto-approvals for this product.
      - `weekly_auto_approval_per_product` integer, nullable — Per-product weekly cap that applies to ALL shop products (only meaningful when `include_all_products=True`).
    - `minimum_performance_criteria` SampleRequestPerformanceCriteria — Min/max performance gate for sample-request approval. All fields are optional — omit a field to skip that side of the bound. API contract uses unprefixed field names + decimal 0-1 scale for rates (`engagement_rate=0.05` for 5%). The translator emits the V2 backend's asymmetric prefix names + 0-100 percent scale at persistence. See modules/automations/SampleRequestConfigModels.py for the V2 shape: - min side → `min_followers`, `min_avg_views`, `min_engagement_rate`, `min_post_rate`, `min_creator_gmv_30_days`, `min_gmv_per_sample` - max side → `maximum_followers`, `maximum_avg_views`, `maximum_engagement_rate`, `maximum_post_rate`, `maximum_creator_gmv`, `maximum_gmv_per_sample`
      - `followers` integer, nullable
      - `avg_views` integer, nullable
      - `engagement_rate` number, nullable — Decimal 0-1 (e.g. 0.05 = 5%). Translator scales to 0-100 percent at persistence.
      - `post_rate` number, nullable — Decimal 0-1 (e.g. 0.5 = 50%). Translator scales to 0-100 percent at persistence.
      - `creator_gmv_30_days` number, nullable — Creator GMV over the trailing 30 days.
      - `gmv_per_sample` number, nullable — GMV per sample sent — gates whether the creator's prior samples drove enough revenue.
    - `maximum_performance_criteria` SampleRequestPerformanceCriteria — Min/max performance gate for sample-request approval. All fields are optional — omit a field to skip that side of the bound. API contract uses unprefixed field names + decimal 0-1 scale for rates (`engagement_rate=0.05` for 5%). The translator emits the V2 backend's asymmetric prefix names + 0-100 percent scale at persistence. See modules/automations/SampleRequestConfigModels.py for the V2 shape: - min side → `min_followers`, `min_avg_views`, `min_engagement_rate`, `min_post_rate`, `min_creator_gmv_30_days`, `min_gmv_per_sample` - max side → `maximum_followers`, `maximum_avg_views`, `maximum_engagement_rate`, `maximum_post_rate`, `maximum_creator_gmv`, `maximum_gmv_per_sample`
      - `followers` integer, nullable
      - `avg_views` integer, nullable
      - `engagement_rate` number, nullable — Decimal 0-1 (e.g. 0.05 = 5%). Translator scales to 0-100 percent at persistence.
      - `post_rate` number, nullable — Decimal 0-1 (e.g. 0.5 = 50%). Translator scales to 0-100 percent at persistence.
      - `creator_gmv_30_days` number, nullable — Creator GMV over the trailing 30 days.
      - `gmv_per_sample` number, nullable — GMV per sample sent — gates whether the creator's prior samples drove enough revenue.
    - `auto_approval_settings` SampleRequestAutoApprovalSettings — Optional auto-approval gating beyond the min/max criteria. * `enabled=False` → manual review for every request that passes the performance gate. Other fields ignored. * `enabled=True` + `always_approve_status` set → auto-approve any creator whose `automation_status` matches; bypass criteria. * `total_weekly_approved_sample_limit` → hard ceiling on weekly auto-approvals across all creators (not per-product). * `always_approve_from_lists_ids` → approve members of these vault lists regardless of criteria. Mirrors `sample_request_config.auto_approval_settings`.
      - `enabled` boolean
      - `total_weekly_approved_sample_limit` integer, nullable
      - `always_approve_status` string, nullable — Auto-approve creators whose top-level `automation_status` matches this value (e.g. `APPROVED`, `LIVE_CREATOR`). Null = no status-based bypass.
      - `always_approve_from_lists_ids` integer[] — Vault list IDs whose members bypass criteria gates and are always approved.
    - `target_list` integer[], nullable
  - `ai_enabled` boolean, nullable
  - `is_evergreen` boolean, nullable
  - `end_date` string, date, nullable
  - `business_hours_timezone` string, nullable — Set or change the 9am–9pm send-window timezone (CORE-3031). Pass an IANA name to enable, or the empty string `""` to disable. Only valid for `target_collab` and `dm` automations.
  - `email_sent_per_day` integer, nullable
  - `subject` string, nullable
  - `body` string, nullable

## Response `200`

Successful Response

- AutomationWriteResponseV2
  - `data` AutomationResourceV2, required — Canonical write-endpoint response. Replaces the v1 AutomationResource (which mirrored the legacy single-shape body). Now reflects the four-type taxonomy — `automation_type` is the public-API type, not the DB string. The DB string is in `_internal_type` for debugging only.
    - `automation_id` integer, nullable — None on dry-run.
    - `shop_id` integer, required
    - `automation_name` string, required
    - `automation_type` string, required — One of: target_collab | dm | email | sample_request.
    - `automation_status` string, required
    - `state` string, required
    - `config` object — Full config as stored. Type-specific.
    - `schedule` object, nullable
    - `creators_to_include` object, nullable
    - `creators_to_exclude` object, nullable
    - `crm_group_id` string, nullable
    - `ai_enabled` boolean
    - `end_date` string, nullable
    - `created_at` string, nullable
    - `updated_at` string, nullable
    - `created_via` string, nullable — Surface that created the automation. Always populated on response: rows with NULL or empty `created_via` (predating migration 020) are surfaced as `portal` since the portal was historically the only writer. Common values: portal | api | api_direct | mcp | agent | slack | internal | campaign_tc_auto (set is not closed — other internal surfaces may appear). `api_direct` marks single-creator invites created by `POST /public/v1/target-collabs`.
    - `side_effects` object, nullable — Backend side-effects, e.g. {'crm_group_created': '<uuid>'} on sample_request creates.
  - `dry_run` boolean
  - `side_effects` object, nullable — Backend side-effects, e.g. {'crm_group_created': 'uuid-...'} for sample_request creates.

## Other responses

- `400` — Validation, missing/invalid Idempotency-Key, or forbidden update field.
- `403` — API key lacks `read_write` scope (`WRITE_NOT_PERMITTED`).
- `404` — Automation/list/group not in this shop.
- `409` — Idempotency conflict or archived row.
- `422` — Validation Error
- `429` — Write-tier rate limit (10/min, 50/hr, 100/day) exceeded.

---

[API](https://skmtc.dev/reacherapp/apis/reacher-data-api.md) · [All operations](https://skmtc.dev/reacherapp/apis/reacher-data-api/llms.txt) · [OpenAPI document](https://skmtc-service-production.skmtc.workers.dev/v1/apis/reacherapp/reacher-data-api/revisions/5eefff009b5b/schema)
