Nimply Developers
Examples

Bulk-create posts from a CSV

Turn a content calendar spreadsheet into up to 50 posts in one request.

Scopes: posts:write, plus posts:publish if any row schedules.

POST /v1/posts/bulk accepts 1–50 items, each shaped exactly like a single createPost body. Items are processed in order, and a failing item does not abort the batch — you get a per-item result to check.

The CSV

channelId,content,schedule
123e4567-e89b-12d3-a456-426614174000,Monday motivation 💪,2026-08-03T09:00:00.000Z
123e4567-e89b-12d3-a456-426614174000,Midweek tips 🛠️,2026-08-05T09:00:00.000Z
123e4567-e89b-12d3-a456-426614174000,Friday recap 📋,2026-08-07T09:00:00.000Z

Convert and send (Node)

import { readFileSync } from "node:fs";

const rows = readFileSync("calendar.csv", "utf8")
  .trim()
  .split("\n")
  .slice(1) // skip header
  .map((line) => {
    const [channelId, content, schedule] = line.split(",");
    return { channelIds: [channelId], content, schedule };
  });

const results = await (
  await fetch("https://api.nimply.io/v1/posts/bulk", {
    method: "POST",
    headers: {
      Authorization: `Bearer ${process.env.NIMPLY_API_KEY}`,
      "Content-Type": "application/json",
      "Idempotency-Key": "calendar-import-2026-08-w1",
    },
    body: JSON.stringify({ posts: rows }),
  })
).json();

for (const r of results) {
  if (r.success) {
    console.log(`row ${r.index}: created ${r.posts.map((p) => p.id).join(", ")}`);
  } else {
    console.error(`row ${r.index}: FAILED — ${r.error}`);
  }
}

Or the equivalent curl with an inline body:

curl -X POST https://api.nimply.io/v1/posts/bulk \
  -H "Authorization: Bearer $NIMPLY_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: calendar-import-2026-08-w1" \
  -d '{
    "posts": [
      { "channelIds": ["123e4567-e89b-12d3-a456-426614174000"], "content": "Monday motivation 💪", "schedule": "2026-08-03T09:00:00.000Z" },
      { "channelIds": ["123e4567-e89b-12d3-a456-426614174000"], "content": "Midweek tips 🛠️",   "schedule": "2026-08-05T09:00:00.000Z" }
    ]
  }'

The response — check every item

Results come back in input order, one per item:

[
  { "index": 0, "success": true, "posts": [{ "id": "7a245d46-…", "status": "SCHEDULED" }] },
  { "index": 1, "success": false, "error": "scheduledAt must be in the future" },
  { "index": 2, "success": true, "posts": [{ "id": "8b356e57-…", "status": "SCHEDULED" }] }
]

Gotchas

  • A 201 does not mean everything succeeded. Item 1 above failed while the batch as a whole returned normally — always iterate the results and handle success: false.
  • Each item can target multiple channels, so posts inside a result is an array (one post per channel id).
  • Maximum 50 items per request; split larger calendars into multiple batches.
  • Scheduling rules and scopes are identical to single-post creation: "draft" is the default, anything else needs posts:publish.
  • Naive line.split(",") breaks on captions containing commas — use a real CSV parser for production imports.

On this page