---
title: "Place several orders at once"
method: POST
path: "/api/v1/orders/batch"
tags: ["Orders"]
---

# Place several orders at once

`POST /api/v1/orders/batch`

## Request

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

Up to 25 orders — several services, one link or several — paid together from your balance. Every order is checked first, at today’s price, exactly as it would be placed (link, quantity, order fields, your key’s caps, running orders per link counting the batch’s own, your balance for all of it). If any one cannot go, **none is placed** and nothing is charged: `batch_invalid` with every item’s reason in `details.items` (or `insufficient_balance` with the `shortfall` for all of it).

Then each is placed on its own, never above the price just checked. Should one fail at that very moment (your balance spent elsewhere meanwhile), the others stay placed and that one says why — nothing is charged for it.

A batch counts as one checkout for the orders-per-minute pace: checked once before the first order, so a batch of 10 is not stopped half-way by its own orders.

Send an `Idempotency-Key`: a retry with the same key places exactly the same orders again — the ones placed answer with their order, never a second charge; the same key with other orders is `idempotency_conflict`. `max_total` (the total you agreed to) refuses everything with `price_changed` if prices moved above it. `dry_run: true` checks and prices only.

An item with a `schedule` becomes an automation (nothing charged now; each part when it goes).

## Headers

- `Idempotency-Key` string

## Request body

- object
  - `items` BatchItem[], required
    - `service` integer, required
    - `link` string, required
    - `quantity` integer: How many. Left out where the inputs count it (comments, usernames) or it is fixed (packages, subscriptions).
    - `inputs` OrderInputs: Order fields, for services with `input.fields`. Text we post for you is checked first (see text_blocked).
      - `comments` union: Custom comments: one per line (or a list), each different. Their number is the quantity.
        - string[]
        - string
      - `usernames` union: Mentions: the accounts to mention, one per line (or a list), at most 50. Their number is the quantity.
        - string[]
        - string
      - `hashtag` string: Mentions by hashtag: one hashtag, e.g. "#yourbrand".
      - `username` string: An account (handle or profile link): whose followers get mentioned, whose comment gets the likes, or a subscription’s account.
      - `answer_number` integer: Poll votes: the option to vote for, 1 = the first (GET /api/v1/polls reads the options).
      - `runs` integer: Spread the order over this many runs; `quantity` is then per run and you pay for quantity × runs. Only where `input.spread` is true.
      - `interval` integer: Minutes between runs.
      - `min` integer: Subscriptions: the least each new post gets.
      - `max` integer: Subscriptions: the most each new post gets.
      - `posts` integer: Subscriptions: how many of your future posts it covers.
      - `old_posts` integer: Subscriptions: also this many of your latest posts.
      - `delay` 0 | 5 | 10 | 15 | 20 | 30 | 40 | 50 | 60 | 90 | 120 | 150 | 180 | 210 | 240 | 270 | 300 | 360 | 420 | 480 | 540 | 600: Subscriptions: minutes after a post goes up before it starts.
      - `expiry` string, date: Subscriptions: the last day (optional, up to a year ahead).
    - `details` object: Done-for-you services only (batch, not the cart): the answers to the service’s `form`.
    - `schedule` ItemSchedule: Deliver this order on a schedule instead of all at once: it becomes an automation (the same fields as `POST /api/v1/automations`, without service, link and quantity). `quantity` is then the automation’s — all of it for `spread`, `split` or `scheduled`, each time for `recurring`. Each part is charged when it goes.
      - `kind` 'spread' | 'scheduled' | 'recurring' | 'split', required
      - `start_at` string: ISO 8601 with an offset; a later start.
      - `timezone` string: IANA zone, e.g. "America/Costa_Rica".
      - `runs` integer: spread: this many runs, `interval_minutes` apart.
      - `interval_minutes` number
      - `over` string: spread: over "1d", "3d", "7d" or "14d" in natural parts.
      - `pace` string: spread: even, steady, front or back.
      - `natural` boolean
      - `every` unknown
      - `times` integer: recurring: how many times.
  - `dry_run` boolean
  - `max_total` string: The most to pay now, USD (the total you showed and agreed to).

## Response `200`

A dry run — or a replay of an earlier batch with the same Idempotency-Key (`replayed: true`, the same `batch`).

- BatchCheck: A dry run: every order checked at today’s price, nothing charged.
  - `dry_run` unknown
  - `ok` boolean: Every order can go, nothing stops the batch, and your balance holds it.
  - `items` BatchItemCheck[]
    - `index` integer: Its place in `items`, from 0.
    - `ok` boolean: It can be placed as it is.
    - `service` integer
    - `link` string
    - `quantity` integer, nullable: The units charged.
    - `total` string, nullable: Its price now (a schedule: its whole plan). USD as a decimal string.
    - `needs` string, nullable: What your balance must hold for it now: the order, a plan, or a repeat’s first run.
    - `scheduled` boolean: It becomes an automation.
    - `textReview` boolean: Our team may check its text before it starts.
    - `done` boolean: Placed already by an earlier try with the same Idempotency-Key.
    - `cartItemId` string: Paying the cart: the line it is.
    - `error` object: Why it cannot be placed: `code` (from the error table), `message`, `details`.
      - `code` string
      - `message` string
      - `details` object
  - `total` string: Today’s price of all of it. USD as a decimal string.
  - `needs` string: What your balance must hold now. USD as a decimal string.
  - `balance` string: Your balance. USD as a decimal string.
  - `sufficient` boolean
  - `shortfall` string: What to top up first (0.00 when nothing). USD as a decimal string.
  - `problem` object, nullable: A reason for the whole batch (orders per minute, the key’s daily cap, prices moved past `max_total`), else null.
    - `code` string
    - `message` string
  - `fit` object: Paying the cart only, when your balance holds some of its lines but not all and nothing else stops them: the lines it pays for now (`items`, by line id, in cart order — a line that does not fit waits and the ones after it may still fit) and those that wait (`keep`). Pay with `items` and `max_total` = `total` under the same Idempotency-Key: those are placed, the rest stays in the cart. Absent otherwise.
    - `items` string[]
    - `keep` string[]
    - `total` string: Today’s price of the lines that go now. USD as a decimal string.
    - `needs` string: What your balance must hold for them now. USD as a decimal string.

## Other responses

- `201`: Placed (each item says how it went).
- `400`: One or more orders cannot go; none was placed.
- `401`: No valid API key or session.
- `402`: Your balance does not hold all of it; none was placed.
- `403`: Refused by this key's safety rails (scope, platforms, caps) or session-only.
- `409`: Prices moved past `max_total`, or the key was used for other orders; none was placed.
- `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)
