Skip to main content
Got 200 posts to schedule? Don’t loop POST /post like a maniac. Upload a CSV and let us handle it. Each row in the CSV becomes one post. Multiple platforms per row, multiple teams, media referenced by URL - one bad row won’t block the rest.

How it works

This is an asynchronous job, same pattern as Import Post History.
  1. Upload: You send us a CSV file.
  2. Wait: We process each row in the background.
  3. Poll: You check if we’re done.
  4. Results: You get per-row success/failure details.

Step 1: Upload the CSV

Endpoint: POST /api/v1/post-csv-import
curl -X POST "https://api.bundle.social/api/v1/post-csv-import" \
  -H "x-api-key: YOUR_KEY" \
  -F "file=@./posts.csv"
The file must be .csv and under 100 MB. Response (201):
{
  "id": "import_abc123",
  "teamId": "team_456",
  "organizationId": "org_789",
  "status": "PENDING",
  "fileName": "posts.csv",
  "totalRows": 200,
  "processedRows": 0,
  "successRows": 0,
  "failedRows": 0,
  "error": null,
  "rateLimitResetAt": null,
  "startedAt": null,
  "completedAt": null,
  "createdAt": "2026-03-31T12:00:00.000Z",
  "updatedAt": "2026-03-31T12:00:00.000Z"
}
Every row in the CSV must include a teamId, and all referenced teams must belong to the same organization that owns your API key.

Step 2: Poll Progress

You have two options: lightweight status or full import details.

Status (lightweight)

Endpoint: GET /api/v1/post-csv-import/{importId}/status
curl "https://api.bundle.social/api/v1/post-csv-import/import_abc123/status" \
  -H "x-api-key: YOUR_KEY"
{
  "id": "import_abc123",
  "status": "PROCESSING",
  "totalRows": 200,
  "processedRows": 87,
  "successRows": 85,
  "failedRows": 2,
  "error": null,
  "startedAt": "2026-03-31T12:00:01.000Z",
  "completedAt": null,
  "updatedAt": "2026-03-31T12:00:45.000Z"
}

Full import details

Endpoint: GET /api/v1/post-csv-import/{importId} Returns the full import object (same shape as the POST response).

Import history

Endpoint: GET /api/v1/post-csv-import
ParameterTypeDefaultDescription
offsetnumber0Skip this many records
limitnumber10How many to return
Returns { items, total }, newest first.

Step 3: Row Results

Once the import finishes (or while it’s running), you can inspect individual rows. Endpoint: GET /api/v1/post-csv-import/{importId}/rows
ParameterTypeDefaultDescription
statusstring-Filter by SUCCESS or FAILED
offsetnumber0Skip this many rows
limitnumber10How many to return
curl "https://api.bundle.social/api/v1/post-csv-import/import_abc123/rows?status=FAILED" \
  -H "x-api-key: YOUR_KEY"
{
  "items": [
    {
      "id": "row_001",
      "importId": "import_abc123",
      "rowNumber": 14,
      "status": "FAILED",
      "postId": null,
      "rawRow": {
        "teamId": "team_456",
        "postDate": "2026-04-05T10:00:00Z",
        "socialAccountTypes": "INSTAGRAM,TIKTOK",
        "text": "Hello world!"
      },
      "normalizedPayload": null,
      "error": "Team not found or does not belong to your organization",
      "createdAt": "2026-03-31T12:00:12.000Z"
    }
  ],
  "total": 2
}
Successful rows include the postId so you can track the created post:
{
  "rowNumber": 1,
  "status": "SUCCESS",
  "postId": "post_xyz789",
  "error": null
}

Import Statuses

StatusMeaning
PENDINGQueued, processing hasn’t started yet.
PROCESSINGWe’re working through the rows.
COMPLETEDAll rows processed successfully.
COMPLETED_WITH_ERRORSDone, but at least one row failed.
FAILEDThe entire import failed (e.g. invalid file).
RATE_LIMITEDPaused due to platform rate limits.

CSV Format

Each row is one post. Column names map to post fields:
ColumnRequiredDescription
teamIdYesWhich team this post belongs to
socialAccountTypesYesComma-separated platforms (e.g. INSTAGRAM,TIKTOK,LINKEDIN)
text-Post body text
postDate-ISO 8601 date for scheduling (omit for immediate)
*MediaUrls-Platform-specific media URLs (e.g. instagramMediaUrls, tiktokMediaUrls)
Media is referenced by URL - we download and process it for you. No need to upload files first with /upload.
Download a CSV template from the bundle.social dashboard under the Bulk Post section to see all available columns and example values.

Handling Rate Limits

If the import status goes to RATE_LIMITED, don’t panic. We hit a platform’s posting cap.
  • The import pauses automatically.
  • Check rateLimitResetAt for when it can resume.
  • We retry automatically - just keep polling.
This works the same as Import Post History rate limiting.

See also

  • Import Post History - same async pattern, but for pulling existing posts from platforms.
  • Media Upload - if you prefer uploading media separately before creating posts.
  • Rate Limits - posting limits per platform and plan.