Bulk upload: check, then schedule many posts at once (CSV or rows)

A CSV (field csv, or the request body itself with Content-Type: text/csv) or a list of rows. Columns: date ("2026-11-02 09:00" in the row's timezone or the upload's, an ISO time with its offset, or next_slot), time (optional), timezone, networks, text, text_<network> (each network's own text), title, title_<network>, media_urls (https; prefix video: or document:), media_urls_<network>, image_urls, video_url, alt_text (| between images), first_comment (LinkedIn, Bluesky, Mastodon), x_reply, x_thread (||| between posts), brand (name or id), label, privacy. Comma, semicolon or tab separated; UTF-8 with or without a BOM; quoted fields may hold commas and line breaks.

dry_run is true by default: nothing is stored, and every row comes back with its time in its timezone, its networks, each network's length as that network counts it (X: a link 23, an emoji 2), the X credits it would use, whether it waits for approval (the brand's rules, a key's limits, a Contributor) or is held by the plan, and its errors and warnings. With dry_run false, ALL rows are scheduled or NONE: any row with an error answers 422 bulk_rows_invalid and nothing is stored; the rows that go to the queue are inserted in one statement; if the rows that wait for approval cannot be stored, the inserted ones are canceled again (503). The same upload twice answers 409 bulk_already_scheduled unless allow_duplicate is true. Rows per upload: Free 20, Starter 500, Pro 1,000, Agency 2,000, Scale 5,000 (403 bulk_rows_limit past it). On Free, rows past the 3 scheduled posts at a time are saved as held (never published on Free).

post/api/bulk

Request

  • Base URL: https://postwire.io
  • URL: https://postwire.io/api/bulk
  • Auth: HTTP bearer

Request body

csvstring

The CSV text, first line = column names. Up to 3 MB.

rowsobject[]

Instead of csv: objects with the same column names.

dry_runboolean
timezonestring

IANA timezone for dates without one.

brand_idstring

Default brand (id or name) for rows without a brand column.

fit_long_textboolean

A text over a network's limit is cut at a sentence or word when published (a warning), instead of an error.

date_format'ymd' | 'dmy' | 'mdy'

How to read 12/10/2026.

allow_duplicateboolean
check_mediaboolean

Check that each media link answers (each link once, up to 20 seconds in all).

batch_idstring

Your own id for the upload (6-64 letters, digits, - or _). Default: a hash of the file.

Response

{ ok, dry_run, batch_id, summary: { rows, ok, with_errors, with_warnings, posts, x_credits, wait_for_approval, held, will_queue, plan }, rows: [{ line, ok, run_at, local, timezone, networks, brand, preview, lengths, x_credits?, approval?, held_for?, errors: [{ field, network?, message, code }], warnings, schedule_id?, status? }], x_credits?, ignored_columns?, undo?, message }

Changes

No recorded changes to this operation across all 1 revision of this API.