---
title: "Create several atomizations for one article"
method: POST
path: "/atomizations/batch"
tags: ["Atomizations"]
---

# Create several atomizations for one article

`POST /atomizations/batch`

## Request

- Base URL: `https://next.frase.io/api/v1`
- URL: `https://next.frase.io/api/v1/atomizations/batch`
- Auth: API key in header `X-API-KEY`

Queues up to four channel formats for a single content item in one call. Requires the `write:atomize` scope.

Same gate order as `POST /atomizations`: body validation, then the plan gate (professional or higher), then the 20/hour per organization+user and 50/hour per organization limiters, then billing. `types` must be a non-empty array of at most 4 entries; unknown type values come back as a 400 with `param: "input"`.

Billing is one charge for the whole batch, taken lazily just before the first type that actually dispatches — so a batch in which every type is already in flight costs nothing. `overage_incurred` is therefore a single flag for the call, not one per type.

Each type is processed sequentially: a row already at `generating` is reported back unchanged and skipped (no second dispatch), any other row is reset to `generating`, and new rows are inserted. Every dispatched type fires its own `atomization/create` background event, so the 201 means "queued", not "generated" — poll `GET /atomizations/{id}` for each returned id. There is no transaction across the loop: a mid-loop failure can leave some types queued and the ledger charged, but a retry is free within the same billing period.

## Request body

- object
  - `content_id` string, uuid, required: Required. The content item to atomize. Must be a UUID belonging to your organization.
  - `types` string[], required: Required. 1-4 channel formats to produce.

## Response `201`

Batch queued

- object
  - `data` object
    - `content_id` string, uuid: Echoed back from the request body
    - `atomizations` object[]: One entry per requested type, in request order. Types that were already in flight appear here with their existing id.
      - `id` string, uuid
      - `type` 'linkedin_carousel' | 'twitter_thread' | 'newsletter' | 'instagram_carousel'
      - `status` 'generating'
    - `overage_incurred` boolean: True when the batch's single charge crossed the organization into paid per-article overage.

## Other responses

- `400`: Validation failed
- `401`: Authentication failed
- `403`: `plan_upgrade_required` — the organization's plan does not include Omni-Channel Atomization (professional or higher); `subscription_inactive` — the subscription is canceled, paused, past due beyond grace, or the trial has expired; or an auth-layer refusal (`insufficient_scope` when the key lacks `write:atomize`, `ip_not_allowed`).
- `404`: The content does not exist or belongs to another organization. Returned in the plain `{ "data": { "error": ... } }` envelope, not the structured error object.
- `429`: Told apart by `error.code`: `rate_limit_exceeded` (20/hour per organization+user or 50/hour per organization — carries `Retry-After` plus the `X-RateLimit-*` bag), `article_limit_exceeded` (monthly content allowance used up), or `site_article_budget_exceeded` (a per-site article budget is exhausted; only a site admin raising it clears this).
- `500`: Unhandled failure, including a malformed/non-JSON request body.

---

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