---
title: "Create an automation"
method: POST
path: "/api/v1/automations"
tags: ["Automations"]
---

# Create an automation

`POST /api/v1/automations`

## Request

- Base URL: `https://spoofy.social`
- URL: `https://spoofy.social/api/v1/automations`
- Auth: one of:
  - HTTP bearer
  - API key in cookie `better-auth.session_token`

Nothing is charged now: each run is charged when it goes, like an order you place then. Send an `Idempotency-Key` header so a retry never makes two. With `dry_run: true` you get the plan — every run with its time, size and cost — and your balance, and nothing is created. A run that would cost more than planned (a price change) pauses the automation instead; a short balance skips a run of a repeat (tried again next run) and pauses a planned total until you top up — it then resumes by itself.

## Headers

- `Idempotency-Key` string

## Request body

- AutomationCreate: What to automate. Every run is an ordinary order of `service`, charged from your balance when it goes, at a price never above the one planned. Fields by kind — **spread**: service, link, quantity (the total), over ("3d") or over_minutes, or runs + interval_minutes; pace even | steady | front (S-curves); natural (±15 % on time and size, default on). **scheduled**: service, link, quantity, start_at. **recurring**: service, link, quantity (each run), every ("6h", "1d", "1w"), at ("14:00" in `timezone`), and times, until or budget; natural (±15 % on each run's time, default on). **split**: service, link, quantity (beyond one order's max). **campaign**: steps [{service, link, quantity, offset_minutes, over_minutes, runs, pace}] — or `template` (a kit id or a saved template) with `links`. **keep_above**: service, link, target, days, budget (public Telegram channels and groups, YouTube videos); when the count is under the target it places a new order. **feed**: feed (a public Telegram channel, a YouTube channel or any RSS / Atom feed), service, quantity per post, max_per_day, days, budget.
  - `kind` 'spread' | 'scheduled' | 'recurring' | 'split' | 'campaign' | 'keep_above' | 'feed', required
  - `name` string
  - `timezone` string: IANA time zone the times are in, e.g. "America/Costa_Rica" (default UTC).
  - `start_at` string, date-time: When the first run goes (default now), with an offset, e.g. 2026-10-18T14:00:00-06:00. Up to 90 days ahead.
  - `budget` string: The most its orders may cost together, net of refunds. USD as a decimal string.
  - `notify` object
    - `email` boolean
    - `progress` boolean: Also an email at halfway.
  - `service` integer: Service id (GET /api/v1/services).
  - `link` string
  - `quantity` integer: spread / split: the total · scheduled / recurring: each run · feed: each post.
  - `over` string: spread: how long, e.g. "3d", "12h", "90m".
  - `over_minutes` integer: spread: the same in minutes (0 = all at once).
  - `runs` integer: spread / campaign steps: how many runs (default: about one every 3 h, kept within the service range).
  - `interval_minutes` integer: spread with runs: minutes between runs.
  - `pace` 'even' | 'steady' | 'front': even, or an S-curve: steady (slow start, fast middle, slow end) or front (the bulk early).
  - `natural` boolean: ±15 % on each run's time and size, the total exact (default on; off = exact runs). Exact runs of equal size (2–100 runs, 1–1,440 minutes apart) on a service that delivers in runs itself go as one order, delivered in those runs.
  - `every` union: recurring: "6h", "1d", "1w", "daily", or {"n": 2, "unit": "days"}.
    - string
    - object
      - `n` integer
      - `unit` 'hours' | 'days' | 'weeks'
  - `at` string: recurring by day or week: the time of day in `timezone`, e.g. "14:00".
  - `times` integer: recurring: how many runs.
  - `until` string, date-time: recurring: no runs after this.
  - `steps` object[]: campaign: each step its own service, link and timing.
    - `service` integer, required
    - `link` string, required
    - `quantity` integer, required
    - `offset_minutes` integer: Minutes after the campaign starts.
    - `over_minutes` integer: Spread this step over this long.
    - `runs` integer
    - `pace` 'even' | 'steady' | 'front'
    - `label` string
  - `template` string: A kit id (e.g. "token-launch") or one of your saved templates (GET /api/v1/automations/templates).
  - `links` object: With a template: the link for each slot, e.g. {"x_post": "https://x.com/…"}.
  - `quantities` object: With a kit: another quantity per slot or step index.
  - `target` integer: keep_above: the count to stay at or above.
  - `days` integer: keep_above / feed: for how many days (up to 90).
  - `check_every_minutes` integer: keep_above: how often the public count is read (at least 30, default 60).
  - `feed` string: feed: https://t.me/yourchannel, https://www.youtube.com/@yourchannel, or an RSS / Atom feed URL. Not X.
  - `max_per_day` integer: feed: at most this many posts a day (default 5).
  - `delay_minutes` integer: feed: wait this long after a post before ordering.
  - `dry_run` boolean: Answer the plan and its cost; create nothing.

## Response `200`

Dry run — or a replay of an earlier create with the same Idempotency-Key (`replayed: true`).

- object
  - `dry_run` unknown
  - `plan` AutomationPlan: A dry run: the plan the automation would make.
    - `kind` string
    - `name` string
    - `timezone` string
    - `startsAt` string, date-time
    - `endsAt` string, date-time, nullable
    - `budget` string, nullable
    - `planned` string: The plan at today's price. USD as a decimal string.
    - `feePerRun` string: Added to each run (0.00: automation costs nothing extra). USD as a decimal string.
    - `runs` object[]
      - `seq` integer
      - `step` integer
      - `at` string, date-time
      - `service` integer
      - `link` string
      - `quantity` integer
      - `cost` string: This run. USD as a decimal string.
    - `balance` string: Your balance now. USD as a decimal string.
    - `sufficient` boolean: The balance covers the whole plan now (each run is charged when it goes).
    - `firstRunCost` string: The first run. USD as a decimal string.

## Other responses

- `201`: Created.
- `400`: Not valid, or the numbers do not fit the service.
- `401`: No valid API key or session.
- `403`: Refused by this key's safety rails (scope, platforms, caps) or session-only.
- `409`: Too many running.
- `413`: The request body is too big (uploads up to 4 MB, everything else up to 64 KB).
- `429`: Slow down: too many requests in a short time. Wait `Retry-After` seconds and carry on.

---

[API](https://skmtc.dev/spoofy/apis/spoofy-api.md) · [All operations](https://skmtc.dev/spoofy/apis/spoofy-api/llms.txt) · [OpenAPI document](https://skmtc.dev/spoofy/apis/spoofy-api/revisions/58adc986f826?raw)
