---
title: "Create TC Cleanup automation"
method: POST
path: "/automations/tc-cleanup"
tags: ["Automations"]
---

# Create TC Cleanup automation

`POST /automations/tc-cleanup`

Create a TC Cleanup utility automation — re-targets creators in this shop who received a TC invite from a prior automation but didn't accept it. Conceptually closer to `sample_request` than to the proactive TC outreach types: products + creator selection only, no messages, no follow-ups. Required: `read_write` scope, `Idempotency-Key` header. Pass `X-Dry-Run: true` to validate without persisting.

## Headers

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

## Request body

- AutomationTcCleanupRequest — Body for POST /automations/tc-cleanup. A utility automation that re-targets creators in this shop who received a TC invite from a prior automation but didn't accept it. The runtime walks unaccepted-invite state shop-wide and re-invites matching creators with the products supplied here. Conceptually closer to `sample_request` (auto-process based on criteria) than to the proactive TC outreach types — no messages, no follow-ups. Maps to DB `automation_type='TC Cleanup'`.
  - `automation_name` string, required
  - `schedule` AutomationSchedule, required — 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.
  - `products` TargetCollabProduct[], required — Products to re-invite the matching creators for. Cleanup uses the same TargetCollabProduct shape (product_id + commission rates) as a regular TC. Required — runtime needs at least one product to issue the re-invite.
    - `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.
  - `invite_start_before_days` integer, nullable — Re-target only creators whose original TC invite was sent at least N days ago. Null = no lower bound on invite age. Persists as `config.INVITE_START_BEFORE_DAYS`.
  - `invite_expire_after_days` integer, nullable — Re-target only creators whose original TC invite expires within the next N days. Null = no upper bound on time-to-expiry. Persists as `config.INVITE_EXPIRE_AFTER_DAYS`.
  - `end_date` string, date, nullable

## Response `201`

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/d4fe3e380424/schema)
