# PostFast API Documentation

> Complete API reference with code examples in Node.js, cURL, and Python for every endpoint and platform.

---

## Introduction

The PostFast REST API lets you programmatically manage your social media content: scheduling posts, uploading media, and retrieving information about your connected social accounts.

**Base URL:** `https://api.postfa.st/`

## Authentication

All API requests must be authenticated with an API key specific to your workspace. Include it in the request headers as `pf-api-key`.

```text
pf-api-key: YOUR_WORKSPACE_API_KEY
```

## Rate Limiting

Every endpoint is subject to global rate limits AND its own endpoint-specific limit, tracked per API key. The most restrictive limit applies first; exceeding any limit returns a `429`.

**Global limits (all endpoints):**

- 60 requests per minute
- 150 requests per 5 minutes
- 300 requests per hour
- 2000 requests per day

## File Management

### POST /file/get-signed-upload-urls

Generates pre-signed URLs for uploading media files (images, videos, or PDFs) directly to S3.

**Rate Limit:** 350 requests per day, 150 per minute
**File Size Limit:** Maximum 250 MB per video, 10 MB per image, and 60 MB per document (PDF, DOC, DOCX, PPT, PPTX).

Perform an HTTP `PUT` of the raw file to each returned `signedUrl`; the `Content-Type` of that PUT must match the `contentType` you requested.

**Node.js:**

```javascript
const response = await fetch('https://api.postfa.st/file/get-signed-upload-urls', {
  method: 'POST',
  headers: {
    'pf-api-key': 'YOUR_API_KEY',
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({
    contentType: 'image/png',
    count: 2
  })
});

const data = await response.json();
```

**cURL:**

```bash
curl -X POST "https://api.postfa.st/file/get-signed-upload-urls" \
  -H "pf-api-key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "contentType": "image/png",
    "count": 2
  }'
```

**Python:**

```python
import requests

url = "https://api.postfa.st/file/get-signed-upload-urls"
headers = {
    "pf-api-key": "YOUR_API_KEY",
    "Content-Type": "application/json"
}
data = {
    "contentType": "image/png",
    "count": 2
}

response = requests.post(url, headers=headers, json=data)
result = response.json()
```

**S3 Upload (Node.js):**

```javascript
// Upload file to S3 using the signed URL from previous step  
const fs = require('fs');
const file = fs.readFileSync('/path/to/your/image.png');

const response = await fetch(signedUrl, {
  method: 'PUT',
  body: file,
  headers: {
    'Content-Type': 'image/png' // Must match contentType from step 1
  }
});

if (response.ok) {
  // File uploaded successfully
  // Use the 'key' from step 1 in your social posts
}
```

**S3 Upload (cURL):**

```bash
# Upload file to S3 using the signed URL from previous step
curl -X PUT "https://s3.amazonaws.com/postfast-uploads/image/a1b2c3d4-e5f6-7890-1234-567890abcdef.png?AWSAccessKeyId=AKIAEXAMPLE&Expires=1640995200&Signature=..." \
  -H "Content-Type: image/png" \
  --data-binary "@/path/to/your/image.png"
```

**S3 Upload (Python):**

```python
import requests

# Upload file to S3 using the signed URL from previous step
with open('/path/to/your/image.png', 'rb') as file:
    response = requests.put(
        signed_url,  # From previous step
        data=file,
        headers={'Content-Type': 'image/png'}
    )

if response.status_code == 200:
    # File uploaded successfully
    # Use the 'key' from step 1 in your social posts
    pass
```

## Social Media Account Management

### GET /social-media/my-social-accounts

Retrieves a list of social media accounts connected to the workspace associated with the API key. Each account includes its latest daily follower or subscriber count.

**Rate Limit:** 350 requests per hour

**Response:** Array of account objects with `id`, `platform`, `platformUsername`, `displayName`, `connectionStatus` (always present: `CONNECTED` or `DISABLED`), and `disabledReason` (`TOKEN_REVOKED` | `ACCOUNT_SUSPENDED` | `PERMISSION_REVOKED` | `MANUAL`; only present when disabled). A DISABLED account has publishing paused until it is reconnected in the app: scheduled posts are held (not deleted) and resume on reconnect, or are marked FAILED if their scheduled time passes while still disabled. Scheduling a NEW post to a DISABLED account returns 400 (saving it as a DRAFT is still allowed).

**Follower counts:** each connected account also returns `followerCount` (a bigint-safe numeric string, or null) and `followerCountUpdatedAt` (the UTC day it was captured for), refreshed once daily around 04:00 UTC. Supported on Instagram, Facebook Pages, YouTube (approximate; hidden-subscriber channels return null), Threads, Pinterest, Bluesky, Telegram (the PostFast bot must stay in the chat), and LinkedIn organization Pages, plus TikTok. Not yet available on X or Google Business Profile. For day-by-day history and the net change over a range, use GET /social-media/:id/follower-history (below).

**Social Inbox coverage:** each account also returns `inboxCapable` (boolean), which is `true` when comments left on that account's posts reach the Social Inbox and can be answered through `GET /social-inbox/conversations`. It is `true` on TikTok, Instagram, Facebook Page, and Threads connections; Facebook and Threads accounts connected before the Social Inbox launched need one reconnect in the app first.

**Recent posts imported on connect:** when an account is first connected or reconnected, PostFast imports the posts published on it in the last 60 days (including posts not created through PostFast) so they appear in GET /social-posts with analytics. The import runs in the background and skips posts already in your account (no duplicates). Supported on Facebook Pages, Instagram, Threads, and TikTok; metrics fill in shortly after (TikTok can take 24-48h), and imported TikTok posts have no stored thumbnail because TikTok media is not retained.

**Node.js:**

```javascript
const response = await fetch('https://api.postfa.st/social-media/my-social-accounts', {
  method: 'GET',
  headers: {
    'pf-api-key': 'YOUR_API_KEY'
  }
});

const accounts = await response.json();
```

**cURL:**

```bash
curl -X GET "https://api.postfa.st/social-media/my-social-accounts" \
  -H "pf-api-key: YOUR_API_KEY"
```

**Python:**

```python
import requests

url = "https://api.postfa.st/social-media/my-social-accounts"
headers = {
    "pf-api-key": "YOUR_API_KEY"
}

response = requests.get(url, headers=headers)
accounts = response.json()
```

### GET /social-media/:id/follower-history

Returns daily follower/subscriber snapshots for one connected account, plus the current count and the net change over the range. Build growth charts without storing your own snapshots. The latest count is also on `GET /social-media/my-social-accounts`.

**Rate Limit:** 200 requests per hour

**Path params:** `socialMediaId` (UUID of the connected account). **Query params:** `from`, `to` (optional ISO 8601; default last 90 days, window capped at 365 days). **Response:** `{ socialMediaId, currentFollowerCount, delta, trackingStartedAt, series[] }`, where `series[]` holds daily `{ capturedAt, followerCount }` snapshots oldest-first. Counts are bigint strings; `delta` is signed (`-123` for a drop, `57` for growth, no leading `+`). `currentFollowerCount` and `trackingStartedAt` cover the account's full history, while `series` and `delta` cover the requested range. Same platform coverage as the daily follower count above (Instagram, Facebook Pages, YouTube, Threads, Pinterest, Bluesky, Telegram, LinkedIn organization Pages, and TikTok; not X, Google Business Profile, or personal accounts). An id not in your workspace returns 200 with an empty `series`; a non-UUID id returns 400.

**Node.js:**

```javascript
const response = await fetch(
  'https://api.postfa.st/social-media/550e8400-e29b-41d4-a716-446655440000/follower-history?from=2026-03-01T00:00:00.000Z&to=2026-06-01T00:00:00.000Z',
  {
    method: 'GET',
    headers: {
      'pf-api-key': 'YOUR_API_KEY'
    }
  }
);

const history = await response.json();
```

**cURL:**

```bash
curl -X GET "https://api.postfa.st/social-media/550e8400-e29b-41d4-a716-446655440000/follower-history?from=2026-03-01T00:00:00.000Z&to=2026-06-01T00:00:00.000Z" \
  -H "pf-api-key: YOUR_API_KEY"
```

**Python:**

```python
import requests

social_media_id = "550e8400-e29b-41d4-a716-446655440000"
url = f"https://api.postfa.st/social-media/{social_media_id}/follower-history"
headers = {
    "pf-api-key": "YOUR_API_KEY"
}
params = {
    "from": "2026-03-01T00:00:00.000Z",
    "to": "2026-06-01T00:00:00.000Z",
}

response = requests.get(url, headers=headers, params=params)
history = response.json()
```

### GET /social-media/search-places

Search real-world places (restaurants, venues, hotels, landmarks) to geotag on Facebook and Instagram posts. The returned `id` works as both `facebookPlaceId` and `instagramLocationId`.

**Rate Limit:** 90 requests per hour

**Query params:** `q` (required, min 2 chars). **Response:** array (up to 100) of places with `id`, `name`, `city`, `country`, `street`, `zip`, `pictureUrl`. The `id` is a Facebook Page ID with location data and works as BOTH `facebookPlaceId` (Facebook) and `instagramLocationId` (Instagram). Only Pages with address data are returned; results are cached 7 days server-side.

**Node.js:**

```javascript
const query = "national palace of culture";
const response = await fetch(
  `https://api.postfa.st/social-media/search-places?q=${encodeURIComponent(query)}`,
  {
    method: 'GET',
    headers: {
      'pf-api-key': 'YOUR_API_KEY'
    }
  }
);

const places = await response.json();
// places[0].id works as BOTH facebookPlaceId and instagramLocationId
```

**cURL:**

```bash
curl -G "https://api.postfa.st/social-media/search-places" \
  --data-urlencode "q=national palace of culture" \
  -H "pf-api-key: YOUR_API_KEY"
```

**Python:**

```python
import requests

url = "https://api.postfa.st/social-media/search-places"
headers = {
    "pf-api-key": "YOUR_API_KEY"
}
params = {"q": "national palace of culture"}

response = requests.get(url, headers=headers, params=params)
places = response.json()
```

### GET /social-media/:id/pinterest-boards

Retrieves all Pinterest boards for a connected Pinterest account. Use the `boardId` field when setting `pinterestBoardId` on a Pinterest post.

**Rate Limit:** 90 requests per hour

**Path params:** `socialMediaId` (UUID of the connected Pinterest account). **Response:** array of boards with `id`, `boardId` (use for `pinterestBoardId`), `name`, `description`, `imageUrl`. New boards do not sync automatically; use Sync Boards in the dashboard.

### GET /social-media/:id/youtube-playlists

Retrieves all YouTube playlists for a connected YouTube account. Use the `playlistId` field when setting `youtubePlaylistId` on a YouTube post.

**Rate Limit:** 90 requests per hour

**Path params:** `socialMediaId` (UUID of the connected YouTube account). **Response:** array of playlists with `id`, `playlistId` (use for `youtubePlaylistId`), `title`, `description`, `thumbnailUrl`.

### GET /social-media/:id/gbp-locations

Returns all synced Google Business Profile locations for a connected account. Use the `locationId` field as `gbpLocationId` when creating GBP posts.

**Rate Limit:** 90 requests per hour

**Path params:** `socialMediaId` (UUID where platform is GOOGLE_BUSINESS_PROFILE). **Response:** array of locations with `id`, `locationId` (use as `gbpLocationId` when creating posts), `title`, `address`, `mapsUri`.

### GET /social-media/:id/tiktok-sounds

Returns TikTok's trending, pre-cleared sounds for a connected TikTok account, ranked by a genre, country, and time window you choose. Use the `musicSoundId` field as `tiktokMusicSoundId` when creating a TikTok photo carousel.

**Rate Limit:** 90 requests per hour

**Path params:** `socialMediaId` (UUID of the connected TikTok account). **Query params:** `genre` (raw TikTok genre value, default ALL; values containing `/` or `&` such as `HIP_HOP/RAP` and `R&B/SOUL` must be URL-encoded), `countryCode` (two-letter uppercase, default US), `dateRange` (1DAY | 7DAY | 30DAY | 90DAY, default 7DAY). **Response:** up to 100 sounds already ordered by trending rank, each with `musicSoundId` (use for `tiktokMusicSoundId`), `name`, `artist`, `duration` (seconds of the exact clip), `thumbnailUrl`, `previewUrl` (plays the clip that will be attached), `rankPosition`, `genres`, and the advanced `commercialMusicId` / `fullDurationClipId` / `trendingClipId`. No pagination: narrow with the query params and filter the rest client-side. Only pre-cleared Commercial Music Library tracks are returned; the list rotates roughly daily and each filter combination is cached ~6h.

**Errors:** a valid `countryCode` with no TikTok chart returns 200 and an empty array (not an error). `tiktokSounds.invalidGenre`, `tiktokSounds.invalidDateRange`, and `tiktokSounds.invalidCountryCode` are 400s; a `socialMediaId` that is not a TikTok account in this workspace returns 404 `tiktokSounds.socialMediaNotFound`. A TikTok account connected some time ago returns 400 `tiktokMusic.requiresBusinessApi` until it is reconnected once from the Accounts page.

**Node.js:**

```javascript
const socialMediaId = "550e8400-e29b-41d4-a716-446655440001";
const params = new URLSearchParams({
  genre: "HIP_HOP/RAP", // URLSearchParams encodes the "/" for you
  countryCode: "DE",
  dateRange: "7DAY"
});

const response = await fetch(
  `https://api.postfa.st/social-media/${socialMediaId}/tiktok-sounds?${params}`,
  {
    method: 'GET',
    headers: {
      'pf-api-key': 'YOUR_API_KEY'
    }
  }
);

const sounds = await response.json();
// Already ranked, so sounds[0] is the top trending track.
// Pass sounds[0].musicSoundId as controls.tiktokMusicSoundId on /social-posts
```

**cURL:**

```bash
curl -G "https://api.postfa.st/social-media/550e8400-e29b-41d4-a716-446655440001/tiktok-sounds" \
  --data-urlencode "genre=HIP_HOP/RAP" \
  --data-urlencode "countryCode=DE" \
  --data-urlencode "dateRange=7DAY" \
  -H "pf-api-key: YOUR_API_KEY"
```

**Python:**

```python
import requests

social_media_id = "550e8400-e29b-41d4-a716-446655440001"
url = f"https://api.postfa.st/social-media/{social_media_id}/tiktok-sounds"
headers = {
    "pf-api-key": "YOUR_API_KEY"
}
params = {
    "genre": "HIP_HOP/RAP",
    "countryCode": "DE",
    "dateRange": "7DAY",
}

response = requests.get(url, headers=headers, params=params)
sounds = response.json()

# Already ranked, so sounds[0] is the top trending track.
# Pass sounds[0]["musicSoundId"] as controls.tiktokMusicSoundId on /social-posts
```

### POST /social-media/connect-link

Generates a secure connect link that lets someone connect their social accounts to your workspace without a PostFast account. Can be scoped to specific platforms and return the user to your own app when done. Optionally emails the link.

**Rate Limit:** 50 requests per hour

**Request body:**
- `expiryDays` (optional, int 1-30, default 7) - Days until the link expires
- `platforms` (optional, string[]) - Restrict the link to these platforms; omit to offer all 11. Enforced server-side, so a scoped link cannot connect anything else
- `redirectUrl` (optional) - Where the connect page offers to send the user when connecting finishes. https only, except http on localhost, max 2000 chars
- `externalId` (optional) - Your reference, echoed back on the return URL. Max 128 chars, `A-Za-z0-9-._~:@`
- `sendEmail` (optional, default false) - Email the connect link; delivery is best effort, a send failure still returns 201
- `email` (required when sendEmail=true) - Recipient email address

**Response:** `{ "connectUrl": "https://app.postfa.st/connect?token=..." }`. The token is a JWT and can be long; use the full URL as returned, do not truncate.

**Return URL:** when `redirectUrl` is set, the connect page offers a `Return to <your host>` button carrying `status` (`success` or `error`), plus `platform` and `accountId` on success or `message` on error, plus your `externalId`. `accountId` is the same id `GET /social-media/my-social-accounts` returns, so it is the completion signal: there is no webhook, and no need to poll and diff. Facebook and LinkedIn offer the button after the page step; Bluesky and Telegram connect in the page and do not offer it.

**Node.js:**

```javascript
const response = await fetch('https://api.postfa.st/social-media/connect-link', {
  method: 'POST',
  headers: {
    'pf-api-key': 'YOUR_API_KEY',
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({
    expiryDays: 7,
    platforms: ['INSTAGRAM'],
    redirectUrl: 'https://yourapp.com/onboarding/social-connected',
    externalId: 'tenant-42',
    sendEmail: true,
    email: 'client@example.com'
  })
});

const data = await response.json();
console.log(data.connectUrl);
```

**cURL:**

```bash
curl -X POST "https://api.postfa.st/social-media/connect-link" \
  -H "pf-api-key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "expiryDays": 7,
    "platforms": ["INSTAGRAM"],
    "redirectUrl": "https://yourapp.com/onboarding/social-connected",
    "externalId": "tenant-42",
    "sendEmail": true,
    "email": "client@example.com"
  }'
```

**Python:**

```python
import requests

url = "https://api.postfa.st/social-media/connect-link"
headers = {
    "pf-api-key": "YOUR_API_KEY",
    "Content-Type": "application/json"
}
payload = {
    "expiryDays": 7,
    "platforms": ["INSTAGRAM"],
    "redirectUrl": "https://yourapp.com/onboarding/social-connected",
    "externalId": "tenant-42",
    "sendEmail": True,
    "email": "client@example.com"
}

response = requests.post(url, json=payload, headers=headers)
data = response.json()
print(data["connectUrl"])
```

## Social Post Management

### GET /social-posts

Query and paginate your social posts with filters. Supports filtering by IDs, platforms, statuses, and date ranges.

**Rate Limit:** 200 requests per hour

Query params: `page` (0-based, default 0), `limit` (1-50, default 20), `ids` (comma-separated UUIDs, max 100), `platforms`, `statuses` (DRAFT/SCHEDULED/PUBLISHED/FAILED), `from`, `to` (ISO 8601, UTC, filter by `scheduledAt`). Response includes `firstComment` and `firstCommentError` (null unless set/failed).

#### Example: Pagination

Navigate through pages of posts

**Node.js:**

```javascript
const response = await fetch('https://api.postfa.st/social-posts?page=0&limit=20', {
  method: 'GET',
  headers: {
    'pf-api-key': 'YOUR_API_KEY'
  }
});

const data = await response.json();
// Request page=0 returns first page
// Response shows page: 1 in pageInfo (display number)
// Use page=1, page=2, etc. for subsequent pages

// Check if more pages available:
if (data.pageInfo.hasNextPage) {
  // Fetch next page with page=1
}
```

**cURL:**

```bash
curl -X GET "https://api.postfa.st/social-posts?page=0&limit=20" \
  -H "pf-api-key: YOUR_API_KEY"

# Response shows page: 1 in pageInfo for page=0 request
```

**Python:**

```python
import requests

url = "https://api.postfa.st/social-posts"
params = {
    "page": 0,  # Request first page (0-based)
    "limit": 20  # Posts per page (max 50)
}
headers = {
    "pf-api-key": "YOUR_API_KEY"
}

response = requests.get(url, params=params, headers=headers)
result = response.json()

# pageInfo.page shows 1 for page=0 request (display number)
# Check pagination info
if result["pageInfo"]["hasNextPage"]:
    # Fetch next page with page=1
    pass
```

---

#### Example: Fetch Specific Posts (IDs)

Fetch a known set of posts by their IDs

**Node.js:**

```javascript
const response = await fetch('https://api.postfa.st/social-posts?ids=3fa85f64-5717-4562-b3fc-2c963f66afa6,7c9e6679-7425-40de-944b-e07fc1f90ae7', {
  method: 'GET',
  headers: {
    'pf-api-key': 'YOUR_API_KEY'
  }
});

const data = await response.json();
// Returns only the requested posts, scoped to your workspace
// Up to 100 IDs, each a UUID v4 (an invalid value returns 400)
// IDs that don't exist or belong to another workspace are silently omitted
// Page size auto-expands to fit every requested ID (never capped at 50)
// Order follows scheduledAt, not the order the IDs were passed
```

**cURL:**

```bash
curl -G "https://api.postfa.st/social-posts" \
  --data-urlencode "ids=3fa85f64-5717-4562-b3fc-2c963f66afa6,7c9e6679-7425-40de-944b-e07fc1f90ae7" \
  -H "pf-api-key: YOUR_API_KEY"
```

**Python:**

```python
import requests

url = "https://api.postfa.st/social-posts"
params = {
    # Comma-separated UUIDs, up to 100, each a UUID v4
    "ids": "3fa85f64-5717-4562-b3fc-2c963f66afa6,7c9e6679-7425-40de-944b-e07fc1f90ae7"
}
headers = {
    "pf-api-key": "YOUR_API_KEY"
}

response = requests.get(url, params=params, headers=headers)
result = response.json()

# Only the requested posts in this workspace are returned.
# Combine with statuses to narrow further, e.g. these IDs but only FAILED ones:
# params["statuses"] = "FAILED"
```

---

#### Example: Filter by Platforms

Get posts for specific social media platforms

**Node.js:**

```javascript
const response = await fetch('https://api.postfa.st/social-posts?page=0&platforms=FACEBOOK,INSTAGRAM,X', {
  method: 'GET',
  headers: {
    'pf-api-key': 'YOUR_API_KEY'
  }
});

const data = await response.json();
// Returns posts for Facebook, Instagram, and X only
// Note: socialMediaId will be null for group posts
```

**cURL:**

```bash
curl -X GET "https://api.postfa.st/social-posts?page=0&platforms=FACEBOOK,INSTAGRAM,X" \
  -H "pf-api-key: YOUR_API_KEY"
```

**Python:**

```python
import requests

url = "https://api.postfa.st/social-posts"
params = {
    "page": 0,
    "platforms": "FACEBOOK,INSTAGRAM,X"  # Comma-separated, no spaces
}
headers = {
    "pf-api-key": "YOUR_API_KEY"
}

response = requests.get(url, params=params, headers=headers)
result = response.json()
```

---

#### Example: Filter by Status

Get posts with specific statuses

**Node.js:**

```javascript
const response = await fetch('https://api.postfa.st/social-posts?page=0&statuses=SCHEDULED,DRAFT', {
  method: 'GET',
  headers: {
    'pf-api-key': 'YOUR_API_KEY'
  }
});

const data = await response.json();
// Returns only SCHEDULED and DRAFT posts
// Available: DRAFT, SCHEDULED, PUBLISHED, FAILED
```

**cURL:**

```bash
curl -X GET "https://api.postfa.st/social-posts?page=0&statuses=SCHEDULED,DRAFT" \
  -H "pf-api-key: YOUR_API_KEY"
```

**Python:**

```python
import requests

url = "https://api.postfa.st/social-posts"
params = {
    "page": 0,
    "statuses": "SCHEDULED,DRAFT"  # Comma-separated, no spaces
}
headers = {
    "pf-api-key": "YOUR_API_KEY"
}

response = requests.get(url, params=params, headers=headers)
result = response.json()
```

---

#### Example: Date Range Filter

Get posts within a specific date range

**Node.js:**

```javascript
const response = await fetch('https://api.postfa.st/social-posts?page=0&from=2025-01-01T00:00:00Z&to=2025-01-31T23:59:59Z', {
  method: 'GET',
  headers: {
    'pf-api-key': 'YOUR_API_KEY'
  }
});

const data = await response.json();
// Returns posts scheduled between Jan 1-31, 2025 UTC
// Filters by scheduledAt field (stored in UTC)
```

**cURL:**

```bash
curl -G "https://api.postfa.st/social-posts" \
  --data-urlencode "page=0" \
  --data-urlencode "from=2025-01-01T00:00:00Z" \
  --data-urlencode "to=2025-01-31T23:59:59Z" \
  -H "pf-api-key: YOUR_API_KEY"
```

**Python:**

```python
import requests

url = "https://api.postfa.st/social-posts"
params = {
    "page": 0,
    "from": "2025-01-01T00:00:00Z",  # UTC time (ISO 8601)
    "to": "2025-01-31T23:59:59Z"     # UTC time (ISO 8601)
}
headers = {
    "pf-api-key": "YOUR_API_KEY"
}

response = requests.get(url, params=params, headers=headers)
result = response.json()
```

---

#### Example: Combined Filters

Use multiple filters together for precise queries

**Node.js:**

```javascript
const response = await fetch('https://api.postfa.st/social-posts?page=0&limit=25&platforms=FACEBOOK,INSTAGRAM&statuses=SCHEDULED&from=2025-01-01T00:00:00Z', {
  method: 'GET',
  headers: {
    'pf-api-key': 'YOUR_API_KEY'
  }
});

const data = await response.json();
// Returns scheduled Facebook and Instagram posts from Jan 2025 onwards
// Limited to 25 posts per page

// Example response for page=0 request:
/*
{
  "data": [...],
  "totalCount": 150,
  "pageInfo": {
    "page": 1,        // Shows 1 for page=0 request (display number)
    "hasNextPage": true,  // More pages available
    "perPage": 25
  }
}
*/
```

**cURL:**

```bash
curl -G "https://api.postfa.st/social-posts" \
  --data-urlencode "page=0" \
  --data-urlencode "limit=25" \
  --data-urlencode "platforms=FACEBOOK,INSTAGRAM" \
  --data-urlencode "statuses=SCHEDULED" \
  --data-urlencode "from=2025-01-01T00:00:00Z" \
  -H "pf-api-key: YOUR_API_KEY"
```

**Python:**

```python
import requests

url = "https://api.postfa.st/social-posts"
params = {
    "page": 0,
    "limit": 25,
    "platforms": "FACEBOOK,INSTAGRAM",
    "statuses": "SCHEDULED",
    "from": "2025-01-01T00:00:00Z"
}
headers = {
    "pf-api-key": "YOUR_API_KEY"
}

response = requests.get(url, params=params, headers=headers)
result = response.json()

# Process pagination
total_posts = result["totalCount"]
has_more = result["pageInfo"]["hasNextPage"]
```

---

#### Example: Failed Posts

Get posts that failed to publish

**Node.js:**

```javascript
const response = await fetch('https://api.postfa.st/social-posts?page=0&statuses=FAILED&limit=50', {
  method: 'GET',
  headers: {
    'pf-api-key': 'YOUR_API_KEY'
  }
});

const data = await response.json();
// Returns all failed posts (up to 50 per page)
// Check failedAt field for failure timestamp
// lastError contains user-friendly message and platform error code
// Use groupId to identify related posts across platforms
```

**cURL:**

```bash
curl -X GET "https://api.postfa.st/social-posts?page=0&statuses=FAILED&limit=50" \
  -H "pf-api-key: YOUR_API_KEY"
```

**Python:**

```python
import requests

url = "https://api.postfa.st/social-posts"
params = {
    "page": 0,
    "statuses": "FAILED",
    "limit": 50  # Maximum allowed per page
}
headers = {
    "pf-api-key": "YOUR_API_KEY"
}

response = requests.get(url, params=params, headers=headers)
result = response.json()

# Iterate through failed posts
for post in result["data"]:
    print(f"Failed at: {post['failedAt']}")
    if post.get('lastError'):
        print(f"Error: {post['lastError']['message']}")
        print(f"Error Code: {post['lastError']['code']}")
    print(f"Group ID: {post['groupId']}")
```

---

### POST /social-posts

Creates and schedules one or more social posts (up to 15 per request).

**Rate Limit:** 350 requests per day, 150 per minute
**File Size Limit:** Maximum 250 MB per video and 10 MB per image.

Body: `posts[]` (1-15, each with `content`, optional `mediaItems`, `scheduledAt` (required unless DRAFT), `socialMediaId`, optional `firstComment`), optional `status` (DRAFT|SCHEDULED, default SCHEDULED), `approvalStatus` (APPROVED|PENDING_APPROVAL, default APPROVED), and an optional `controls` object of platform-specific settings.

**First comment:** posted ~10s after publish (up to 3 attempts). Supported on X, Instagram, Facebook, YouTube, Threads, and TikTok (on TikTok: max 1,200 chars since 2026-08-07 and comments enabled on the post; a TikTok account that can't post comments returns `firstComment.tiktok.notSupported`). NOT supported on Pinterest, BlueSky, LinkedIn, or Google Business Profile (returns a validation error).

**Google Business Profile controls:** `gbpLocationId` (required, from GET /social-media/:id/gbp-locations), `gbpTopicType` (STANDARD/EVENT/OFFER), `gbpCallToActionType` (BOOK/ORDER/SHOP/LEARN_MORE/SIGN_UP/CALL), `gbpCallToActionUrl`, `gbpEventTitle` (max 58 chars), `gbpEventStartDate`/`gbpEventEndDate` (ISO 8601, required for EVENT/OFFER), `gbpOfferCouponCode`, `gbpOfferRedeemUrl`, `gbpOfferTerms`. Media: 1 image only (JPEG/PNG). Content limit: 1500 chars.

**TikTok sound selection:** on a TikTok photo carousel, `controls.tiktokMusicSoundId` attaches a specific licensed track (a `musicSoundId` from GET /social-media/:id/tiktok-sounds, max 128 chars) and TikTok adds the audio server-side at publish. Optional `controls.tiktokMusicSoundName` (max 256 chars) is a display-only label, never sent to TikTok. It is mutually exclusive with `tiktokAutoAddMusic: true` (both set returns `tiktokMusic.conflictAutoAddMusic`); with neither set the post publishes silent. Music controls are not applied to drafts (`tiktokIsDraft: true`).

**Geo controls (Facebook + Instagram):** geotag a post with `facebookPlaceId` (Facebook feed posts only) or `instagramLocationId` (Instagram single image/video/reel/story, not carousels) - resolve the id via GET /social-media/search-places (one id works for both). Optional `facebookPlaceName`/`instagramLocationName` are display-only and never sent to Meta. `facebookTargetCountries` (array of ISO 3166-1 alpha-2 codes, max 25) limits a Facebook feed post's audience by country (gating: only people in those countries, signed in, can see it). The `controls` object is shared by every post in the request, so batch by platform when using geo controls.

**AI disclosure controls:** three separate flags, all optional booleans defaulting to false, each ignored by every platform other than its own. `tiktokIsAigc` applies TikTok's AIGC label; `instagramIsAiGenerated` adds Instagram's "AI info" label to images, videos, reels, stories, and carousels (labeling the whole post, not individual slides); `youtubeContainsSyntheticMedia` discloses realistic altered or synthetic content and is sent to YouTube only when true. All three are set at creation only - there is no update route for a post, and on Instagram the label cannot be removed after publishing. The `controls` object is shared by every post in the request, so a flag set on a mixed batch applies to all of them.

#### Platform: TikTok Video

**Node.js:**

```javascript
const response = await fetch('https://api.postfa.st/social-posts', {
  method: 'POST',
  headers: {
    'pf-api-key': 'YOUR_API_KEY',
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({
    posts: [
      {
        content: "3 productivity hacks that changed my morning routine ☀️ Which one are you trying first? #productivity #morningroutine #lifehacks",
        mediaItems: [
          {
            key: "video/a7b8c9d1-e2f3-4567-8901-23456789abcd.mp4",
            type: "VIDEO",
            sortOrder: 0,
            coverTimestamp: "3000"
          }
        ],
        scheduledAt: "2025-01-31T10:00:00.000Z",
        socialMediaId: "550e8400-e29b-41d4-a716-446655440001"
      }
    ],
    controls: {
      tiktokPrivacy: "PUBLIC",
      tiktokAllowComments: true,
      tiktokAllowDuet: true
    }
  })
});

const data = await response.json();
```

**cURL:**

```bash
curl -X POST "https://api.postfa.st/social-posts" \
  -H "pf-api-key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "posts": [
      {
        "content": "3 productivity hacks that changed my morning routine ☀️ Which one are you trying first? #productivity #morningroutine #lifehacks",
        "mediaItems": [
          {
            "key": "video/a7b8c9d1-e2f3-4567-8901-23456789abcd.mp4",
            "type": "VIDEO",
            "sortOrder": 0,
            "coverTimestamp": "3000"
          }
        ],
        "scheduledAt": "2025-01-31T10:00:00.000Z",
        "socialMediaId": "550e8400-e29b-41d4-a716-446655440001"
      }
    ],
    "controls": {
      "tiktokPrivacy": "PUBLIC",
      "tiktokAllowComments": true,
      "tiktokAllowDuet": true
    }
  }'
```

**Python:**

```python
import requests

url = "https://api.postfa.st/social-posts"
headers = {
    "pf-api-key": "YOUR_API_KEY",
    "Content-Type": "application/json"
}
data = {
    "posts": [
        {
            "content": "3 productivity hacks that changed my morning routine ☀️ Which one are you trying first? #productivity #morningroutine #lifehacks",
            "mediaItems": [
                {
                    "key": "video/a7b8c9d1-e2f3-4567-8901-23456789abcd.mp4",
                    "type": "VIDEO",
                    "sortOrder": 0,
                    "coverTimestamp": "3000"
                }
            ],
            "scheduledAt": "2025-01-31T10:00:00.000Z",
            "socialMediaId": "550e8400-e29b-41d4-a716-446655440001"
        }
    ],
    "controls": {
        "tiktokPrivacy": "PUBLIC",
        "tiktokAllowComments": true,
        "tiktokAllowDuet": true
    }
}

response = requests.post(url, headers=headers, json=data)
result = response.json()
```

---

#### Platform: Facebook Post

**Node.js:**

```javascript
const response = await fetch('https://api.postfa.st/social-posts', {
  method: 'POST',
  headers: {
    'pf-api-key': 'YOUR_API_KEY',
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({
    posts: [
      {
        content: "Just launched our new product line! 🚀 Here's a behind-the-scenes look at what we've been working on. What feature are you most excited about?",
        mediaItems: [
          {
            key: "image/b1c2d3e4-f5a6-7890-1234-567890abcdef.jpg",
            type: "IMAGE",
            sortOrder: 0
          },
          {
            key: "image/c2d3e4f5-a6b7-8901-2345-67890abcdef1.jpg",
            type: "IMAGE",
            sortOrder: 1
          }
        ],
        scheduledAt: "2025-01-31T10:00:00.000Z",
        socialMediaId: "660f9500-f3ac-42e5-b827-556766550002"
      }
    ],
    controls: {
      facebookContentType: "POST"
    }
  })
});

const data = await response.json();
```

**cURL:**

```bash
curl -X POST "https://api.postfa.st/social-posts" \
  -H "pf-api-key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "posts": [
      {
        "content": "Just launched our new product line! 🚀 Here's a behind-the-scenes look at what we've been working on. What feature are you most excited about?",
        "mediaItems": [
          {
            "key": "image/b1c2d3e4-f5a6-7890-1234-567890abcdef.jpg",
            "type": "IMAGE",
            "sortOrder": 0
          },
          {
            "key": "image/c2d3e4f5-a6b7-8901-2345-67890abcdef1.jpg",
            "type": "IMAGE",
            "sortOrder": 1
          }
        ],
        "scheduledAt": "2025-01-31T10:00:00.000Z",
        "socialMediaId": "660f9500-f3ac-42e5-b827-556766550002"
      }
    ],
    "controls": {
      "facebookContentType": "POST"
    }
  }'
```

**Python:**

```python
import requests

url = "https://api.postfa.st/social-posts"
headers = {
    "pf-api-key": "YOUR_API_KEY",
    "Content-Type": "application/json"
}
data = {
    "posts": [
        {
            "content": "Just launched our new product line! 🚀 Here's a behind-the-scenes look at what we've been working on. What feature are you most excited about?",
            "mediaItems": [
                {
                    "key": "image/b1c2d3e4-f5a6-7890-1234-567890abcdef.jpg",
                    "type": "IMAGE",
                    "sortOrder": 0
                },
                {
                    "key": "image/c2d3e4f5-a6b7-8901-2345-67890abcdef1.jpg",
                    "type": "IMAGE",
                    "sortOrder": 1
                }
            ],
            "scheduledAt": "2025-01-31T10:00:00.000Z",
            "socialMediaId": "660f9500-f3ac-42e5-b827-556766550002"
        }
    ],
    "controls": {
        "facebookContentType": "POST"
    }
}

response = requests.post(url, headers=headers, json=data)
result = response.json()
```

---

#### Platform: Facebook Reel

**Node.js:**

```javascript
const response = await fetch('https://api.postfa.st/social-posts', {
  method: 'POST',
  headers: {
    'pf-api-key': 'YOUR_API_KEY',
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({
    posts: [
      {
        content: "How I edit my videos in under 5 minutes ⚡ Save this for later! #contentcreator #videoediting #socialmediatips",
        mediaItems: [
          {
            key: "video/d3e4f5a6-b7c8-9012-3456-7890abcdef12.mp4",
            type: "VIDEO",
            sortOrder: 0,
            coverTimestamp: "2000"
          }
        ],
        scheduledAt: "2025-01-31T10:00:00.000Z",
        socialMediaId: "660f9500-f3ac-42e5-b827-556766550002"
      }
    ],
    controls: {
      facebookContentType: "REEL"
    }
  })
});

const data = await response.json();
```

**cURL:**

```bash
curl -X POST "https://api.postfa.st/social-posts" \
  -H "pf-api-key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "posts": [
      {
        "content": "How I edit my videos in under 5 minutes ⚡ Save this for later! #contentcreator #videoediting #socialmediatips",
        "mediaItems": [
          {
            "key": "video/d3e4f5a6-b7c8-9012-3456-7890abcdef12.mp4",
            "type": "VIDEO",
            "sortOrder": 0,
            "coverTimestamp": "2000"
          }
        ],
        "scheduledAt": "2025-01-31T10:00:00.000Z",
        "socialMediaId": "660f9500-f3ac-42e5-b827-556766550002"
      }
    ],
    "controls": {
      "facebookContentType": "REEL"
    }
  }'
```

**Python:**

```python
import requests

url = "https://api.postfa.st/social-posts"
headers = {
    "pf-api-key": "YOUR_API_KEY",
    "Content-Type": "application/json"
}
data = {
    "posts": [
        {
            "content": "How I edit my videos in under 5 minutes ⚡ Save this for later! #contentcreator #videoediting #socialmediatips",
            "mediaItems": [
                {
                    "key": "video/d3e4f5a6-b7c8-9012-3456-7890abcdef12.mp4",
                    "type": "VIDEO",
                    "sortOrder": 0,
                    "coverTimestamp": "2000"
                }
            ],
            "scheduledAt": "2025-01-31T10:00:00.000Z",
            "socialMediaId": "660f9500-f3ac-42e5-b827-556766550002"
        }
    ],
    "controls": {
        "facebookContentType": "REEL"
    }
}

response = requests.post(url, headers=headers, json=data)
result = response.json()
```

---

#### Platform: Facebook Story

**Node.js:**

```javascript
const response = await fetch('https://api.postfa.st/social-posts', {
  method: 'POST',
  headers: {
    'pf-api-key': 'YOUR_API_KEY',
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({
    posts: [
      {
        content: "POV: Your Monday morning coffee hits different ☕ New blog post link in bio!",
        mediaItems: [
          {
            key: "image/e4f5a6b7-c8d9-0123-4567-890abcdef123.jpg",
            type: "IMAGE",
            sortOrder: 0
          }
        ],
        scheduledAt: "2025-01-31T10:00:00.000Z",
        socialMediaId: "660f9500-f3ac-42e5-b827-556766550002"
      }
    ],
    controls: {
      facebookContentType: "STORY"
    }
  })
});

const data = await response.json();
```

**cURL:**

```bash
curl -X POST "https://api.postfa.st/social-posts" \
  -H "pf-api-key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "posts": [
      {
        "content": "POV: Your Monday morning coffee hits different ☕ New blog post link in bio!",
        "mediaItems": [
          {
            "key": "image/e4f5a6b7-c8d9-0123-4567-890abcdef123.jpg",
            "type": "IMAGE",
            "sortOrder": 0
          }
        ],
        "scheduledAt": "2025-01-31T10:00:00.000Z",
        "socialMediaId": "660f9500-f3ac-42e5-b827-556766550002"
      }
    ],
    "controls": {
      "facebookContentType": "STORY"
    }
  }'
```

**Python:**

```python
import requests

url = "https://api.postfa.st/social-posts"
headers = {
    "pf-api-key": "YOUR_API_KEY",
    "Content-Type": "application/json"
}
data = {
    "posts": [
        {
            "content": "POV: Your Monday morning coffee hits different ☕ New blog post link in bio!",
            "mediaItems": [
                {
                    "key": "image/e4f5a6b7-c8d9-0123-4567-890abcdef123.jpg",
                    "type": "IMAGE",
                    "sortOrder": 0
                }
            ],
            "scheduledAt": "2025-01-31T10:00:00.000Z",
            "socialMediaId": "660f9500-f3ac-42e5-b827-556766550002"
        }
    ],
    "controls": {
        "facebookContentType": "STORY"
    }
}

response = requests.post(url, headers=headers, json=data)
result = response.json()
```

---

#### Platform: Instagram Timeline

**Node.js:**

```javascript
const response = await fetch('https://api.postfa.st/social-posts', {
  method: 'POST',
  headers: {
    'pf-api-key': 'YOUR_API_KEY',
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({
    posts: [
      {
        content: "The sunset from our office last night 🌅 Sometimes you just have to stop and appreciate the view. Where's your favorite spot to unwind? #sunsetphotography #worklifebalance #officeviews",
        mediaItems: [
          {
            key: "image/f5a6b7c8-d9e0-1234-5678-90abcdef1234.jpg",
            type: "IMAGE",
            sortOrder: 0
          }
        ],
        scheduledAt: "2025-01-31T10:00:00.000Z",
        socialMediaId: "770fa600-04bd-43f6-c938-667877660003"
      }
    ],
    controls: {
      instagramPublishType: "TIMELINE",
      instagramPostToGrid: true
    }
  })
});

const data = await response.json();
```

**cURL:**

```bash
curl -X POST "https://api.postfa.st/social-posts" \
  -H "pf-api-key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "posts": [
      {
        "content": "The sunset from our office last night 🌅 Sometimes you just have to stop and appreciate the view. Where's your favorite spot to unwind? #sunsetphotography #worklifebalance #officeviews",
        "mediaItems": [
          {
            "key": "image/f5a6b7c8-d9e0-1234-5678-90abcdef1234.jpg",
            "type": "IMAGE",
            "sortOrder": 0
          }
        ],
        "scheduledAt": "2025-01-31T10:00:00.000Z",
        "socialMediaId": "770fa600-04bd-43f6-c938-667877660003"
      }
    ],
    "controls": {
      "instagramPublishType": "TIMELINE",
      "instagramPostToGrid": true
    }
  }'
```

**Python:**

```python
import requests

url = "https://api.postfa.st/social-posts"
headers = {
    "pf-api-key": "YOUR_API_KEY",
    "Content-Type": "application/json"
}
data = {
    "posts": [
        {
            "content": "The sunset from our office last night 🌅 Sometimes you just have to stop and appreciate the view. Where's your favorite spot to unwind? #sunsetphotography #worklifebalance #officeviews",
            "mediaItems": [
                {
                    "key": "image/f5a6b7c8-d9e0-1234-5678-90abcdef1234.jpg",
                    "type": "IMAGE",
                    "sortOrder": 0
                }
            ],
            "scheduledAt": "2025-01-31T10:00:00.000Z",
            "socialMediaId": "770fa600-04bd-43f6-c938-667877660003"
        }
    ],
    "controls": {
        "instagramPublishType": "TIMELINE",
        "instagramPostToGrid": true
    }
}

response = requests.post(url, headers=headers, json=data)
result = response.json()
```

---

#### Platform: Instagram Story

**Node.js:**

```javascript
const response = await fetch('https://api.postfa.st/social-posts', {
  method: 'POST',
  headers: {
    'pf-api-key': 'YOUR_API_KEY',
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({
    posts: [
      {
        content: "Sneak peek at tomorrow's launch 👀 Set your alarms! #comingsoon #sneakpeek #newrelease",
        mediaItems: [
          {
            key: "video/a6b7c8d9-e0f1-2345-6789-0abcdef12345.mp4",
            type: "VIDEO",
            sortOrder: 0,
            coverTimestamp: "1000"
          }
        ],
        scheduledAt: "2025-01-31T10:00:00.000Z",
        socialMediaId: "770fa600-04bd-43f6-c938-667877660003"
      }
    ],
    controls: {
      instagramPublishType: "STORY"
    }
  })
});

const data = await response.json();
```

**cURL:**

```bash
curl -X POST "https://api.postfa.st/social-posts" \
  -H "pf-api-key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "posts": [
      {
        "content": "Sneak peek at tomorrow's launch 👀 Set your alarms! #comingsoon #sneakpeek #newrelease",
        "mediaItems": [
          {
            "key": "video/a6b7c8d9-e0f1-2345-6789-0abcdef12345.mp4",
            "type": "VIDEO",
            "sortOrder": 0,
            "coverTimestamp": "1000"
          }
        ],
        "scheduledAt": "2025-01-31T10:00:00.000Z",
        "socialMediaId": "770fa600-04bd-43f6-c938-667877660003"
      }
    ],
    "controls": {
      "instagramPublishType": "STORY"
    }
  }'
```

**Python:**

```python
import requests

url = "https://api.postfa.st/social-posts"
headers = {
    "pf-api-key": "YOUR_API_KEY",
    "Content-Type": "application/json"
}
data = {
    "posts": [
        {
            "content": "Sneak peek at tomorrow's launch 👀 Set your alarms! #comingsoon #sneakpeek #newrelease",
            "mediaItems": [
                {
                    "key": "video/a6b7c8d9-e0f1-2345-6789-0abcdef12345.mp4",
                    "type": "VIDEO",
                    "sortOrder": 0,
                    "coverTimestamp": "1000"
                }
            ],
            "scheduledAt": "2025-01-31T10:00:00.000Z",
            "socialMediaId": "770fa600-04bd-43f6-c938-667877660003"
        }
    ],
    "controls": {
        "instagramPublishType": "STORY"
    }
}

response = requests.post(url, headers=headers, json=data)
result = response.json()
```

---

#### Platform: Instagram Reel

**Node.js:**

```javascript
const response = await fetch('https://api.postfa.st/social-posts', {
  method: 'POST',
  headers: {
    'pf-api-key': 'YOUR_API_KEY',
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({
    posts: [
      {
        content: "5 Instagram growth strategies that actually work in 2025 📈 Save this and thank me later! #instagramgrowth #socialmediamarketing #contentcreator #growthhacks",
        mediaItems: [
          {
            key: "video/r1e2e3l4-5678-90ab-cdef-1234567890ab.mp4",
            type: "VIDEO",
            sortOrder: 0,
            coverImageKey: "image/c0v3r-1234-5678-90ab-cdef12345678.jpg",
            coverTimestamp: "2000"
          }
        ],
        scheduledAt: "2025-01-31T10:00:00.000Z",
        socialMediaId: "770fa600-04bd-43f6-c938-667877660003"
      }
    ],
    controls: {
      instagramPublishType: "REEL"
    }
  })
});

const data = await response.json();
```

**cURL:**

```bash
curl -X POST "https://api.postfa.st/social-posts" \
  -H "pf-api-key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "posts": [
      {
        "content": "5 Instagram growth strategies that actually work in 2025 📈 Save this and thank me later! #instagramgrowth #socialmediamarketing #contentcreator #growthhacks",
        "mediaItems": [
          {
            "key": "video/r1e2e3l4-5678-90ab-cdef-1234567890ab.mp4",
            "type": "VIDEO",
            "sortOrder": 0,
            "coverImageKey": "image/c0v3r-1234-5678-90ab-cdef12345678.jpg",
            "coverTimestamp": "2000"
          }
        ],
        "scheduledAt": "2025-01-31T10:00:00.000Z",
        "socialMediaId": "770fa600-04bd-43f6-c938-667877660003"
      }
    ],
    "controls": {
      "instagramPublishType": "REEL"
    }
  }'
```

**Python:**

```python
import requests

url = "https://api.postfa.st/social-posts"
headers = {
    "pf-api-key": "YOUR_API_KEY",
    "Content-Type": "application/json"
}
data = {
    "posts": [
        {
            "content": "5 Instagram growth strategies that actually work in 2025 📈 Save this and thank me later! #instagramgrowth #socialmediamarketing #contentcreator #growthhacks",
            "mediaItems": [
                {
                    "key": "video/r1e2e3l4-5678-90ab-cdef-1234567890ab.mp4",
                    "type": "VIDEO",
                    "sortOrder": 0,
                    "coverImageKey": "image/c0v3r-1234-5678-90ab-cdef12345678.jpg",
                    "coverTimestamp": "2000"
                }
            ],
            "scheduledAt": "2025-01-31T10:00:00.000Z",
            "socialMediaId": "770fa600-04bd-43f6-c938-667877660003"
        }
    ],
    "controls": {
        "instagramPublishType": "REEL"
    }
}

response = requests.post(url, headers=headers, json=data)
result = response.json()
```

---

#### Platform: TikTok Carousel

**Node.js:**

```javascript
const response = await fetch('https://api.postfa.st/social-posts', {
  method: 'POST',
  headers: {
    'pf-api-key': 'YOUR_API_KEY',
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({
    posts: [
      {
        content: "10 home office setup ideas under $100 💡 Swipe for the full transformation! #homeoffice #workfromhome #desksetup #productivitytips",
        mediaItems: [
          {
            key: "image/b7c8d9e0-f1a2-3456-7890-abcdef123456.jpg",
            type: "IMAGE",
            sortOrder: 0
          },
          {
            key: "image/c8d9e0f1-a2b3-4567-8901-bcdef1234567.jpg",
            type: "IMAGE",
            sortOrder: 1
          },
          {
            key: "image/d9e0f1a2-b3c4-5678-9012-cdef12345678.jpg",
            type: "IMAGE",
            sortOrder: 2
          }
        ],
        scheduledAt: "2025-01-31T10:00:00.000Z",
        socialMediaId: "550e8400-e29b-41d4-a716-446655440001"
      }
    ],
    controls: {
      tiktokTitle: "10 home office setup ideas under $100",
      tiktokPrivacy: "PUBLIC",
      tiktokAllowComments: true,
      // musicSoundId from GET /social-media/:id/tiktok-sounds
      tiktokMusicSoundId: "7363314838511175697",
      tiktokMusicSoundName: "Golden Hour Drive - Nova Reef"
    }
  })
});

const data = await response.json();
```

**cURL:**

```bash
curl -X POST "https://api.postfa.st/social-posts" \
  -H "pf-api-key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "posts": [
      {
        "content": "10 home office setup ideas under $100 💡 Swipe for the full transformation! #homeoffice #workfromhome #desksetup #productivitytips",
        "mediaItems": [
          {
            "key": "image/b7c8d9e0-f1a2-3456-7890-abcdef123456.jpg",
            "type": "IMAGE",
            "sortOrder": 0
          },
          {
            "key": "image/c8d9e0f1-a2b3-4567-8901-bcdef1234567.jpg",
            "type": "IMAGE",
            "sortOrder": 1
          },
          {
            "key": "image/d9e0f1a2-b3c4-5678-9012-cdef12345678.jpg",
            "type": "IMAGE",
            "sortOrder": 2
          }
        ],
        "scheduledAt": "2025-01-31T10:00:00.000Z",
        "socialMediaId": "550e8400-e29b-41d4-a716-446655440001"
      }
    ],
    "controls": {
      "tiktokTitle": "10 home office setup ideas under $100",
      "tiktokPrivacy": "PUBLIC",
      "tiktokAllowComments": true,
      "tiktokMusicSoundId": "7363314838511175697",
      "tiktokMusicSoundName": "Golden Hour Drive - Nova Reef"
    }
  }'
```

**Python:**

```python
import requests

url = "https://api.postfa.st/social-posts"
headers = {
    "pf-api-key": "YOUR_API_KEY",
    "Content-Type": "application/json"
}
data = {
    "posts": [
        {
            "content": "10 home office setup ideas under $100 💡 Swipe for the full transformation! #homeoffice #workfromhome #desksetup #productivitytips",
            "mediaItems": [
                {
                    "key": "image/b7c8d9e0-f1a2-3456-7890-abcdef123456.jpg",
                    "type": "IMAGE",
                    "sortOrder": 0
                },
                {
                    "key": "image/c8d9e0f1-a2b3-4567-8901-bcdef1234567.jpg",
                    "type": "IMAGE",
                    "sortOrder": 1
                },
                {
                    "key": "image/d9e0f1a2-b3c4-5678-9012-cdef12345678.jpg",
                    "type": "IMAGE",
                    "sortOrder": 2
                }
            ],
            "scheduledAt": "2025-01-31T10:00:00.000Z",
            "socialMediaId": "550e8400-e29b-41d4-a716-446655440001"
        }
    ],
    "controls": {
        "tiktokTitle": "10 home office setup ideas under $100",
        "tiktokPrivacy": "PUBLIC",
        "tiktokAllowComments": True,
        # musicSoundId from GET /social-media/:id/tiktok-sounds
        "tiktokMusicSoundId": "7363314838511175697",
        "tiktokMusicSoundName": "Golden Hour Drive - Nova Reef"
    }
}

response = requests.post(url, headers=headers, json=data)
result = response.json()
```

---

#### Platform: TikTok Draft

**Node.js:**

```javascript
const response = await fetch('https://api.postfa.st/social-posts', {
  method: 'POST',
  headers: {
    'pf-api-key': 'YOUR_API_KEY',
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({
    posts: [
      {
        content: "Recipe tutorial: 15-minute healthy dinner ideas 🥗 Perfect for busy weeknights! #mealprep #healthyrecipes #quickdinners #cookingtips",
        mediaItems: [
          {
            key: "video/e0f1a2b3-c4d5-6789-0123-def456789012.mp4",
            type: "VIDEO",
            sortOrder: 0,
            coverTimestamp: "4000"
          }
        ],
        scheduledAt: "2025-01-31T10:00:00.000Z",
        socialMediaId: "550e8400-e29b-41d4-a716-446655440001"
      }
    ],
    controls: {
      tiktokIsDraft: true
    }
  })
});

const data = await response.json();
```

**cURL:**

```bash
curl -X POST "https://api.postfa.st/social-posts" \
  -H "pf-api-key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "posts": [
      {
        "content": "Recipe tutorial: 15-minute healthy dinner ideas 🥗 Perfect for busy weeknights! #mealprep #healthyrecipes #quickdinners #cookingtips",
        "mediaItems": [
          {
            "key": "video/e0f1a2b3-c4d5-6789-0123-def456789012.mp4",
            "type": "VIDEO",
            "sortOrder": 0,
            "coverTimestamp": "4000"
          }
        ],
        "scheduledAt": "2025-01-31T10:00:00.000Z",
        "socialMediaId": "550e8400-e29b-41d4-a716-446655440001"
      }
    ],
    "controls": {
      "tiktokIsDraft": true
    }
  }'
```

**Python:**

```python
import requests

url = "https://api.postfa.st/social-posts"
headers = {
    "pf-api-key": "YOUR_API_KEY",
    "Content-Type": "application/json"
}
data = {
    "posts": [
        {
            "content": "Recipe tutorial: 15-minute healthy dinner ideas 🥗 Perfect for busy weeknights! #mealprep #healthyrecipes #quickdinners #cookingtips",
            "mediaItems": [
                {
                    "key": "video/e0f1a2b3-c4d5-6789-0123-def456789012.mp4",
                    "type": "VIDEO",
                    "sortOrder": 0,
                    "coverTimestamp": "4000"
                }
            ],
            "scheduledAt": "2025-01-31T10:00:00.000Z",
            "socialMediaId": "550e8400-e29b-41d4-a716-446655440001"
        }
    ],
    "controls": {
        "tiktokIsDraft": true
    }
}

response = requests.post(url, headers=headers, json=data)
result = response.json()
```

---

#### Platform: TikTok AI-Generated

**Node.js:**

```javascript
const response = await fetch('https://api.postfa.st/social-posts', {
  method: 'POST',
  headers: {
    'pf-api-key': 'YOUR_API_KEY',
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({
    posts: [
      {
        content: "AI-generated content #fyp",
        mediaItems: [
          {
            key: "video/a1b2c3d4-e5f6-7890-abcd-ef0123456789.mp4",
            type: "VIDEO",
            sortOrder: 0
          }
        ],
        scheduledAt: "2026-03-25T10:00:00.000Z",
        socialMediaId: "550e8400-e29b-41d4-a716-446655440001"
      }
    ],
    controls: {
      tiktokPrivacy: "FOLLOWER_OF_CREATOR",
      tiktokIsAigc: true
    }
  })
});

const data = await response.json();
```

**cURL:**

```bash
curl -X POST "https://api.postfa.st/social-posts" \
  -H "pf-api-key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "posts": [
      {
        "content": "AI-generated content #fyp",
        "mediaItems": [
          {
            "key": "video/a1b2c3d4-e5f6-7890-abcd-ef0123456789.mp4",
            "type": "VIDEO",
            "sortOrder": 0
          }
        ],
        "scheduledAt": "2026-03-25T10:00:00.000Z",
        "socialMediaId": "550e8400-e29b-41d4-a716-446655440001"
      }
    ],
    "controls": {
      "tiktokPrivacy": "FOLLOWER_OF_CREATOR",
      "tiktokIsAigc": true
    }
  }'
```

**Python:**

```python
import requests

url = "https://api.postfa.st/social-posts"
headers = {
    "pf-api-key": "YOUR_API_KEY",
    "Content-Type": "application/json"
}
data = {
    "posts": [
        {
            "content": "AI-generated content #fyp",
            "mediaItems": [
                {
                    "key": "video/a1b2c3d4-e5f6-7890-abcd-ef0123456789.mp4",
                    "type": "VIDEO",
                    "sortOrder": 0
                }
            ],
            "scheduledAt": "2026-03-25T10:00:00.000Z",
            "socialMediaId": "550e8400-e29b-41d4-a716-446655440001"
        }
    ],
    "controls": {
        "tiktokPrivacy": "FOLLOWER_OF_CREATOR",
        "tiktokIsAigc": True
    }
}

response = requests.post(url, headers=headers, json=data)
result = response.json()
```

---

#### Platform: Instagram AI-Labeled

**Node.js:**

```javascript
const response = await fetch('https://api.postfa.st/social-posts', {
  method: 'POST',
  headers: {
    'pf-api-key': 'YOUR_API_KEY',
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({
    posts: [
      {
        content: "Concept art for the next campaign, generated with AI 🎨 #aiart #designprocess",
        mediaItems: [
          {
            key: "image/f5a6b7c8-d9e0-1234-5678-90abcdef1234.jpg",
            type: "IMAGE",
            sortOrder: 0
          }
        ],
        scheduledAt: "2026-03-25T10:00:00.000Z",
        socialMediaId: "770fa600-04bd-43f6-c938-667877660003"
      }
    ],
    controls: {
      instagramPublishType: "TIMELINE",
      instagramIsAiGenerated: true
    }
  })
});

const data = await response.json();
```

**cURL:**

```bash
curl -X POST "https://api.postfa.st/social-posts" \
  -H "pf-api-key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "posts": [
      {
        "content": "Concept art for the next campaign, generated with AI 🎨 #aiart #designprocess",
        "mediaItems": [
          {
            "key": "image/f5a6b7c8-d9e0-1234-5678-90abcdef1234.jpg",
            "type": "IMAGE",
            "sortOrder": 0
          }
        ],
        "scheduledAt": "2026-03-25T10:00:00.000Z",
        "socialMediaId": "770fa600-04bd-43f6-c938-667877660003"
      }
    ],
    "controls": {
      "instagramPublishType": "TIMELINE",
      "instagramIsAiGenerated": true
    }
  }'
```

**Python:**

```python
import requests

url = "https://api.postfa.st/social-posts"
headers = {
    "pf-api-key": "YOUR_API_KEY",
    "Content-Type": "application/json"
}
data = {
    "posts": [
        {
            "content": "Concept art for the next campaign, generated with AI 🎨 #aiart #designprocess",
            "mediaItems": [
                {
                    "key": "image/f5a6b7c8-d9e0-1234-5678-90abcdef1234.jpg",
                    "type": "IMAGE",
                    "sortOrder": 0
                }
            ],
            "scheduledAt": "2026-03-25T10:00:00.000Z",
            "socialMediaId": "770fa600-04bd-43f6-c938-667877660003"
        }
    ],
    "controls": {
        "instagramPublishType": "TIMELINE",
        "instagramIsAiGenerated": True
    }
}

response = requests.post(url, headers=headers, json=data)
result = response.json()
```

---

#### Platform: Instagram Trial Reel

**Node.js:**

```javascript
const response = await fetch('https://api.postfa.st/social-posts', {
  method: 'POST',
  headers: {
    'pf-api-key': 'YOUR_API_KEY',
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({
    posts: [
      {
        content: "Testing this with new audiences",
        mediaItems: [
          {
            key: "video/b2c3d4e5-f6a7-8901-bcde-f01234567890.mp4",
            type: "VIDEO",
            sortOrder: 0
          }
        ],
        scheduledAt: "2026-03-25T10:00:00.000Z",
        socialMediaId: "550e8400-e29b-41d4-a716-446655440002"
      }
    ],
    controls: {
      instagramPublishType: "REEL",
      instagramTrialReelStrategy: "SS_PERFORMANCE",
      instagramPostToGrid: true
    }
  })
});

const data = await response.json();
```

**cURL:**

```bash
curl -X POST "https://api.postfa.st/social-posts" \
  -H "pf-api-key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "posts": [
      {
        "content": "Testing this with new audiences",
        "mediaItems": [
          {
            "key": "video/b2c3d4e5-f6a7-8901-bcde-f01234567890.mp4",
            "type": "VIDEO",
            "sortOrder": 0
          }
        ],
        "scheduledAt": "2026-03-25T10:00:00.000Z",
        "socialMediaId": "550e8400-e29b-41d4-a716-446655440002"
      }
    ],
    "controls": {
      "instagramPublishType": "REEL",
      "instagramTrialReelStrategy": "SS_PERFORMANCE",
      "instagramPostToGrid": true
    }
  }'
```

**Python:**

```python
import requests

url = "https://api.postfa.st/social-posts"
headers = {
    "pf-api-key": "YOUR_API_KEY",
    "Content-Type": "application/json"
}
data = {
    "posts": [
        {
            "content": "Testing this with new audiences",
            "mediaItems": [
                {
                    "key": "video/b2c3d4e5-f6a7-8901-bcde-f01234567890.mp4",
                    "type": "VIDEO",
                    "sortOrder": 0
                }
            ],
            "scheduledAt": "2026-03-25T10:00:00.000Z",
            "socialMediaId": "550e8400-e29b-41d4-a716-446655440002"
        }
    ],
    "controls": {
        "instagramPublishType": "REEL",
        "instagramTrialReelStrategy": "SS_PERFORMANCE",
        "instagramPostToGrid": True
    }
}

response = requests.post(url, headers=headers, json=data)
result = response.json()
```

---

#### Platform: YouTube Shorts

**Node.js:**

```javascript
const response = await fetch('https://api.postfa.st/social-posts', {
  method: 'POST',
  headers: {
    'pf-api-key': 'YOUR_API_KEY',
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({
    posts: [
      {
        content: "This AI tool writes code for you in seconds 🤯 #shorts #coding #programming #developer #tech #ai",
        mediaItems: [
          {
            key: "video/f1a2b3c4-d5e6-7890-1234-ef0123456789.mp4",
            type: "VIDEO",
            sortOrder: 0,
            coverTimestamp: "2000"
          }
        ],
        scheduledAt: "2025-01-31T10:00:00.000Z",
        socialMediaId: "880fb700-15ce-44f7-d049-778988770004"
      }
    ],
    controls: {
      youtubeIsShort: true,
      youtubeThumbnailKey: 'image/e7f8a9b0-c1d2-3456-7890-abcdef123456.jpg'
    }
  })
});

const data = await response.json();
```

**cURL:**

```bash
curl -X POST "https://api.postfa.st/social-posts" \
  -H "pf-api-key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "posts": [
      {
        "content": "This AI tool writes code for you in seconds 🤯 #shorts #coding #programming #developer #tech #ai",
        "mediaItems": [
          {
            "key": "video/f1a2b3c4-d5e6-7890-1234-ef0123456789.mp4",
            "type": "VIDEO",
            "sortOrder": 0,
            "coverTimestamp": "2000"
          }
        ],
        "scheduledAt": "2025-01-31T10:00:00.000Z",
        "socialMediaId": "880fb700-15ce-44f7-d049-778988770004"
      }
    ],
    "controls": {
      "youtubeIsShort": true,
      "youtubeThumbnailKey": "image/e7f8a9b0-c1d2-3456-7890-abcdef123456.jpg"
    }
  }'
```

**Python:**

```python
import requests

url = "https://api.postfa.st/social-posts"
headers = {
    "pf-api-key": "YOUR_API_KEY",
    "Content-Type": "application/json"
}
data = {
    "posts": [
        {
            "content": "This AI tool writes code for you in seconds 🤯 #shorts #coding #programming #developer #tech #ai",
            "mediaItems": [
                {
                    "key": "video/f1a2b3c4-d5e6-7890-1234-ef0123456789.mp4",
                    "type": "VIDEO",
                    "sortOrder": 0,
                    "coverTimestamp": "2000"
                }
            ],
            "scheduledAt": "2025-01-31T10:00:00.000Z",
            "socialMediaId": "880fb700-15ce-44f7-d049-778988770004"
        }
    ],
    "controls": {
        "youtubeIsShort": true,
        "youtubeThumbnailKey": "image/e7f8a9b0-c1d2-3456-7890-abcdef123456.jpg"
    }
}

response = requests.post(url, headers=headers, json=data)
result = response.json()
```

---

#### Platform: X Retweet

**Node.js:**

```javascript
const response = await fetch('https://api.postfa.st/social-posts', {
  method: 'POST',
  headers: {
    'pf-api-key': 'YOUR_API_KEY',
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({
    posts: [
      {
        content: "",
        scheduledAt: "2025-01-31T10:00:00.000Z",
        socialMediaId: "440d7300-d18a-31c3-9605-335544330005"
      }
    ],
    controls: {
      xRetweetUrl: "https://x.com/username/status/1234567890123456789"
    }
  })
});

const data = await response.json();
```

**cURL:**

```bash
curl -X POST "https://api.postfa.st/social-posts" \
  -H "pf-api-key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "posts": [
      {
        "content": "",
        "scheduledAt": "2025-01-31T10:00:00.000Z",
        "socialMediaId": "440d7300-d18a-31c3-9605-335544330005"
      }
    ],
    "controls": {
      "xRetweetUrl": "https://x.com/username/status/1234567890123456789"
    }
  }'
```

**Python:**

```python
import requests

url = "https://api.postfa.st/social-posts"
headers = {
    "pf-api-key": "YOUR_API_KEY",
    "Content-Type": "application/json"
}
data = {
    "posts": [
        {
            "content": "",
            "scheduledAt": "2025-01-31T10:00:00.000Z",
            "socialMediaId": "440d7300-d18a-31c3-9605-335544330005"
        }
    ],
    "controls": {
        "xRetweetUrl": "https://x.com/username/status/1234567890123456789"
    }
}

response = requests.post(url, headers=headers, json=data)
result = response.json()
```

---

#### Platform: Pinterest Pin

**Node.js:**

```javascript
const response = await fetch('https://api.postfa.st/social-posts', {
  method: 'POST',
  headers: {
    'pf-api-key': 'YOUR_API_KEY',
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({
    posts: [
      {
        content: "25 Easy Meal Prep Ideas for Busy Weeknights\n\nSave hours every week with these simple, healthy recipes. Perfect for batch cooking and portion control. Includes shopping list and storage tips!",
        mediaItems: [
          {
            key: "image/a1b2c3d4-e5f6-7890-1234-567890abcdef.jpg",
            type: "IMAGE",
            sortOrder: 0
          }
        ],
        scheduledAt: "2025-01-31T10:00:00.000Z",
        socialMediaId: "ee4m6277-o1ik-20m3-j605-334544338010"
      }
    ],
    controls: {
      pinterestBoardId: "1234567890123456789",
      pinterestLink: "https://yourblog.com/meal-prep-ideas"
    }
  })
});

const data = await response.json();
```

**cURL:**

```bash
curl -X POST "https://api.postfa.st/social-posts" \
  -H "pf-api-key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "posts": [
      {
        "content": "25 Easy Meal Prep Ideas for Busy Weeknights\n\nSave hours every week with these simple, healthy recipes. Perfect for batch cooking and portion control. Includes shopping list and storage tips!",
        "mediaItems": [
          {
            "key": "image/a1b2c3d4-e5f6-7890-1234-567890abcdef.jpg",
            "type": "IMAGE",
            "sortOrder": 0
          }
        ],
        "scheduledAt": "2025-01-31T10:00:00.000Z",
        "socialMediaId": "ee4m6277-o1ik-20m3-j605-334544338010"
      }
    ],
    "controls": {
      "pinterestBoardId": "1234567890123456789",
      "pinterestLink": "https://yourblog.com/meal-prep-ideas"
    }
  }'
```

**Python:**

```python
import requests

url = "https://api.postfa.st/social-posts"
headers = {
    "pf-api-key": "YOUR_API_KEY",
    "Content-Type": "application/json"
}
data = {
    "posts": [
        {
            "content": "25 Easy Meal Prep Ideas for Busy Weeknights\n\nSave hours every week with these simple, healthy recipes. Perfect for batch cooking and portion control. Includes shopping list and storage tips!",
            "mediaItems": [
                {
                    "key": "image/a1b2c3d4-e5f6-7890-1234-567890abcdef.jpg",
                    "type": "IMAGE",
                    "sortOrder": 0
                }
            ],
            "scheduledAt": "2025-01-31T10:00:00.000Z",
            "socialMediaId": "ee4m6277-o1ik-20m3-j605-334544338010"
        }
    ],
    "controls": {
        "pinterestBoardId": "1234567890123456789",
        "pinterestLink": "https://yourblog.com/meal-prep-ideas"
    }
}

response = requests.post(url, headers=headers, json=data)
result = response.json()
```

---

#### Platform: Threads Carousel

**Node.js:**

```javascript
const response = await fetch('https://api.postfa.st/social-posts', {
  method: 'POST',
  headers: {
    'pf-api-key': 'YOUR_API_KEY',
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({
    posts: [
      {
        content: "5 design principles every developer should know (swipe through)\n\n1. Whitespace is your friend\n2. Limit your color palette\n3. Typography matters\n4. Consistency builds trust\n5. Less is more\n\nWhich one do you struggle with?",
        mediaItems: [
          {
            key: "image/t1h2r3e4-a5d6-7890-1234-567890abcdef.jpg",
            type: "IMAGE",
            sortOrder: 0
          },
          {
            key: "image/t2h3r4e5-b6c7-8901-2345-67890abcdef1.jpg",
            type: "IMAGE",
            sortOrder: 1
          },
          {
            key: "image/t3h4r5e6-c7d8-9012-3456-7890abcdef12.jpg",
            type: "IMAGE",
            sortOrder: 2
          },
          {
            key: "image/t4h5r6e7-d8e9-0123-4567-890abcdef123.jpg",
            type: "IMAGE",
            sortOrder: 3
          },
          {
            key: "image/t5h6r7e8-e9f0-1234-5678-90abcdef1234.jpg",
            type: "IMAGE",
            sortOrder: 4
          }
        ],
        scheduledAt: "2025-01-31T10:00:00.000Z",
        socialMediaId: "990i2833-j6df-75h8-e150-889099883005"
      }
    ],
    controls: {}
  })
});

const data = await response.json();
```

**cURL:**

```bash
curl -X POST "https://api.postfa.st/social-posts" \
  -H "pf-api-key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "posts": [
      {
        "content": "5 design principles every developer should know (swipe through)\n\n1. Whitespace is your friend\n2. Limit your color palette\n3. Typography matters\n4. Consistency builds trust\n5. Less is more\n\nWhich one do you struggle with?",
        "mediaItems": [
          {
            "key": "image/t1h2r3e4-a5d6-7890-1234-567890abcdef.jpg",
            "type": "IMAGE",
            "sortOrder": 0
          },
          {
            "key": "image/t2h3r4e5-b6c7-8901-2345-67890abcdef1.jpg",
            "type": "IMAGE",
            "sortOrder": 1
          },
          {
            "key": "image/t3h4r5e6-c7d8-9012-3456-7890abcdef12.jpg",
            "type": "IMAGE",
            "sortOrder": 2
          },
          {
            "key": "image/t4h5r6e7-d8e9-0123-4567-890abcdef123.jpg",
            "type": "IMAGE",
            "sortOrder": 3
          },
          {
            "key": "image/t5h6r7e8-e9f0-1234-5678-90abcdef1234.jpg",
            "type": "IMAGE",
            "sortOrder": 4
          }
        ],
        "scheduledAt": "2025-01-31T10:00:00.000Z",
        "socialMediaId": "990i2833-j6df-75h8-e150-889099883005"
      }
    ],
    "controls": {}
  }'
```

**Python:**

```python
import requests

url = "https://api.postfa.st/social-posts"
headers = {
    "pf-api-key": "YOUR_API_KEY",
    "Content-Type": "application/json"
}
data = {
    "posts": [
        {
            "content": "5 design principles every developer should know (swipe through)\n\n1. Whitespace is your friend\n2. Limit your color palette\n3. Typography matters\n4. Consistency builds trust\n5. Less is more\n\nWhich one do you struggle with?",
            "mediaItems": [
                {
                    "key": "image/t1h2r3e4-a5d6-7890-1234-567890abcdef.jpg",
                    "type": "IMAGE",
                    "sortOrder": 0
                },
                {
                    "key": "image/t2h3r4e5-b6c7-8901-2345-67890abcdef1.jpg",
                    "type": "IMAGE",
                    "sortOrder": 1
                },
                {
                    "key": "image/t3h4r5e6-c7d8-9012-3456-7890abcdef12.jpg",
                    "type": "IMAGE",
                    "sortOrder": 2
                },
                {
                    "key": "image/t4h5r6e7-d8e9-0123-4567-890abcdef123.jpg",
                    "type": "IMAGE",
                    "sortOrder": 3
                },
                {
                    "key": "image/t5h6r7e8-e9f0-1234-5678-90abcdef1234.jpg",
                    "type": "IMAGE",
                    "sortOrder": 4
                }
            ],
            "scheduledAt": "2025-01-31T10:00:00.000Z",
            "socialMediaId": "990i2833-j6df-75h8-e150-889099883005"
        }
    ],
    "controls": {}
}

response = requests.post(url, headers=headers, json=data)
result = response.json()
```

---

#### Platform: Instagram Carousel

**Node.js:**

```javascript
const response = await fetch('https://api.postfa.st/social-posts', {
  method: 'POST',
  headers: {
    'pf-api-key': 'YOUR_API_KEY',
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({
    posts: [
      {
        content: "The complete guide to building your morning routine (save this!)\n\nSlide 1: Wake up at the same time\nSlide 2: Hydrate before caffeine\nSlide 3: 10 min movement\nSlide 4: Review your priorities\nSlide 5: Start with your hardest task\n\nWhich tip are you trying tomorrow?\n\n#morningroutine #productivity #habits",
        mediaItems: [
          {
            key: "image/ig1a2b3c-d4e5-6789-0123-456789abcdef.jpg",
            type: "IMAGE",
            sortOrder: 0
          },
          {
            key: "image/ig2b3c4d-e5f6-7890-1234-567890abcdef.jpg",
            type: "IMAGE",
            sortOrder: 1
          },
          {
            key: "image/ig3c4d5e-f6a7-8901-2345-67890abcdef1.jpg",
            type: "IMAGE",
            sortOrder: 2
          },
          {
            key: "image/ig4d5e6f-a7b8-9012-3456-7890abcdef12.jpg",
            type: "IMAGE",
            sortOrder: 3
          },
          {
            key: "image/ig5e6f7a-b8c9-0123-4567-890abcdef123.jpg",
            type: "IMAGE",
            sortOrder: 4
          }
        ],
        scheduledAt: "2025-01-31T10:00:00.000Z",
        socialMediaId: "770fa600-04bd-43f6-c938-667877660003"
      }
    ],
    controls: {
      instagramPublishType: "TIMELINE",
      instagramPostToGrid: true
    }
  })
});

const data = await response.json();
```

**cURL:**

```bash
curl -X POST "https://api.postfa.st/social-posts" \
  -H "pf-api-key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "posts": [
      {
        "content": "The complete guide to building your morning routine (save this!)\n\nSlide 1: Wake up at the same time\nSlide 2: Hydrate before caffeine\nSlide 3: 10 min movement\nSlide 4: Review your priorities\nSlide 5: Start with your hardest task\n\nWhich tip are you trying tomorrow?\n\n#morningroutine #productivity #habits",
        "mediaItems": [
          {
            "key": "image/ig1a2b3c-d4e5-6789-0123-456789abcdef.jpg",
            "type": "IMAGE",
            "sortOrder": 0
          },
          {
            "key": "image/ig2b3c4d-e5f6-7890-1234-567890abcdef.jpg",
            "type": "IMAGE",
            "sortOrder": 1
          },
          {
            "key": "image/ig3c4d5e-f6a7-8901-2345-67890abcdef1.jpg",
            "type": "IMAGE",
            "sortOrder": 2
          },
          {
            "key": "image/ig4d5e6f-a7b8-9012-3456-7890abcdef12.jpg",
            "type": "IMAGE",
            "sortOrder": 3
          },
          {
            "key": "image/ig5e6f7a-b8c9-0123-4567-890abcdef123.jpg",
            "type": "IMAGE",
            "sortOrder": 4
          }
        ],
        "scheduledAt": "2025-01-31T10:00:00.000Z",
        "socialMediaId": "770fa600-04bd-43f6-c938-667877660003"
      }
    ],
    "controls": {
      "instagramPublishType": "TIMELINE",
      "instagramPostToGrid": true
    }
  }'
```

**Python:**

```python
import requests

url = "https://api.postfa.st/social-posts"
headers = {
    "pf-api-key": "YOUR_API_KEY",
    "Content-Type": "application/json"
}
data = {
    "posts": [
        {
            "content": "The complete guide to building your morning routine (save this!)\n\nSlide 1: Wake up at the same time\nSlide 2: Hydrate before caffeine\nSlide 3: 10 min movement\nSlide 4: Review your priorities\nSlide 5: Start with your hardest task\n\nWhich tip are you trying tomorrow?\n\n#morningroutine #productivity #habits",
            "mediaItems": [
                {
                    "key": "image/ig1a2b3c-d4e5-6789-0123-456789abcdef.jpg",
                    "type": "IMAGE",
                    "sortOrder": 0
                },
                {
                    "key": "image/ig2b3c4d-e5f6-7890-1234-567890abcdef.jpg",
                    "type": "IMAGE",
                    "sortOrder": 1
                },
                {
                    "key": "image/ig3c4d5e-f6a7-8901-2345-67890abcdef1.jpg",
                    "type": "IMAGE",
                    "sortOrder": 2
                },
                {
                    "key": "image/ig4d5e6f-a7b8-9012-3456-7890abcdef12.jpg",
                    "type": "IMAGE",
                    "sortOrder": 3
                },
                {
                    "key": "image/ig5e6f7a-b8c9-0123-4567-890abcdef123.jpg",
                    "type": "IMAGE",
                    "sortOrder": 4
                }
            ],
            "scheduledAt": "2025-01-31T10:00:00.000Z",
            "socialMediaId": "770fa600-04bd-43f6-c938-667877660003"
        }
    ],
    "controls": {
        "instagramPublishType": "TIMELINE",
        "instagramPostToGrid": True
    }
}

response = requests.post(url, headers=headers, json=data)
result = response.json()
```

---

#### Platform: X Post with First Comment

**Node.js:**

```javascript
const response = await fetch('https://api.postfa.st/social-posts', {
  method: 'POST',
  headers: {
    'pf-api-key': 'YOUR_API_KEY',
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({
    posts: [
      {
        content: "We just shipped automatic first comments via the API. Schedule a post, and your first comment drops ~10 seconds after it goes live.",
        firstComment: "Try it free for 7 days at postfa.st - works with X, Instagram, Facebook, YouTube, and Threads.",
        scheduledAt: "2025-01-31T10:00:00.000Z",
        socialMediaId: "440d7300-d18a-31c3-9605-335544330005"
      }
    ]
  })
});

const data = await response.json();
```

**cURL:**

```bash
curl -X POST "https://api.postfa.st/social-posts" \
  -H "pf-api-key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "posts": [
      {
        "content": "We just shipped automatic first comments via the API. Schedule a post, and your first comment drops ~10 seconds after it goes live.",
        "firstComment": "Try it free for 7 days at postfa.st - works with X, Instagram, Facebook, YouTube, and Threads.",
        "scheduledAt": "2025-01-31T10:00:00.000Z",
        "socialMediaId": "440d7300-d18a-31c3-9605-335544330005"
      }
    ]
  }'
```

**Python:**

```python
import requests

url = "https://api.postfa.st/social-posts"
headers = {
    "pf-api-key": "YOUR_API_KEY",
    "Content-Type": "application/json"
}
data = {
    "posts": [
        {
            "content": "We just shipped automatic first comments via the API. Schedule a post, and your first comment drops ~10 seconds after it goes live.",
            "firstComment": "Try it free for 7 days at postfa.st - works with X, Instagram, Facebook, YouTube, and Threads.",
            "scheduledAt": "2025-01-31T10:00:00.000Z",
            "socialMediaId": "440d7300-d18a-31c3-9605-335544330005"
        }
    ]
}

response = requests.post(url, headers=headers, json=data)
result = response.json()
```

---

#### Platform: LinkedIn Document

**Node.js:**

```javascript
const response = await fetch('https://api.postfa.st/social-posts', {
  method: 'POST',
  headers: {
    'pf-api-key': 'YOUR_API_KEY',
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({
    posts: [
      {
        content: "I analyzed 500+ successful LinkedIn posts.\n\nHere are the 7 patterns that get the most engagement (PDF guide attached):\n\n1. Hook in first 2 lines\n2. Use white space\n3. End with a question\n4. Post Tuesday-Thursday 8-10am\n5. 3-5 hashtags max\n6. Native content > links\n7. Engage in first hour\n\nWhat's your #1 LinkedIn tip?\n\n#linkedin #contentmarketing #b2b",
        scheduledAt: "2025-01-31T09:00:00.000Z",
        socialMediaId: "bb1j3944-l8fh-97j0-g372-001211005007"
      }
    ],
    controls: {
      linkedinAttachmentKey: "file/4aa995e5-fbd5-4899-91e8-c3bdae175e23.pdf",
      linkedinAttachmentTitle: "LinkedIn Engagement Analysis 2025"
    }
  })
});

const data = await response.json();
```

**cURL:**

```bash
curl -X POST "https://api.postfa.st/social-posts" \
  -H "pf-api-key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "posts": [
      {
        "content": "I analyzed 500+ successful LinkedIn posts.\n\nHere are the 7 patterns that get the most engagement (PDF guide attached):\n\n1. Hook in first 2 lines\n2. Use white space\n3. End with a question\n4. Post Tuesday-Thursday 8-10am\n5. 3-5 hashtags max\n6. Native content > links\n7. Engage in first hour\n\nWhat\'s your #1 LinkedIn tip?\n\n#linkedin #contentmarketing #b2b",
        "scheduledAt": "2025-01-31T09:00:00.000Z",
        "socialMediaId": "bb1j3944-l8fh-97j0-g372-001211005007"
      }
    ],
    "controls": {
      "linkedinAttachmentKey": "file/4aa995e5-fbd5-4899-91e8-c3bdae175e23.pdf",
      "linkedinAttachmentTitle": "LinkedIn Engagement Analysis 2025"
    }
  }'
```

**Python:**

```python
import requests

url = "https://api.postfa.st/social-posts"
headers = {
    "pf-api-key": "YOUR_API_KEY",
    "Content-Type": "application/json"
}
data = {
    "posts": [
        {
            "content": "I analyzed 500+ successful LinkedIn posts.\n\nHere are the 7 patterns that get the most engagement (PDF guide attached):\n\n1. Hook in first 2 lines\n2. Use white space\n3. End with a question\n4. Post Tuesday-Thursday 8-10am\n5. 3-5 hashtags max\n6. Native content > links\n7. Engage in first hour\n\nWhat's your #1 LinkedIn tip?\n\n#linkedin #contentmarketing #b2b",
            "scheduledAt": "2025-01-31T09:00:00.000Z",
            "socialMediaId": "bb1j3944-l8fh-97j0-g372-001211005007"
        }
    ],
    "controls": {
        "linkedinAttachmentKey": "file/4aa995e5-fbd5-4899-91e8-c3bdae175e23.pdf",
        "linkedinAttachmentTitle": "LinkedIn Engagement Analysis 2025"
    }
}

response = requests.post(url, headers=headers, json=data)
result = response.json()
```

---

#### Platform: Google Business Profile — Standard Update

**Node.js:**

```javascript
const response = await fetch('https://api.postfa.st/social-posts', {
  method: 'POST',
  headers: {
    'pf-api-key': 'YOUR_API_KEY',
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({
    posts: [
      {
        content: "We've just expanded our opening hours! Now open until 9 PM on weekdays. Come visit us for all your needs.",
        scheduledAt: "2026-04-10T09:00:00.000Z",
        socialMediaId: "550e8400-e29b-41d4-a716-446655440011"
      }
    ],
    controls: {
      gbpLocationId: "accounts/109049740544589765860/locations/4875357571247123933",
      gbpTopicType: "STANDARD",
      gbpCallToActionType: "LEARN_MORE",
      gbpCallToActionUrl: "https://example.com/hours"
    }
  })
});

const data = await response.json();
```

**cURL:**

```bash
curl -X POST "https://api.postfa.st/social-posts" \
  -H "pf-api-key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "posts": [
      {
        "content": "We've just expanded our opening hours! Now open until 9 PM on weekdays. Come visit us for all your needs.",
        "scheduledAt": "2026-04-10T09:00:00.000Z",
        "socialMediaId": "550e8400-e29b-41d4-a716-446655440011"
      }
    ],
    "controls": {
      "gbpLocationId": "accounts/109049740544589765860/locations/4875357571247123933",
      "gbpTopicType": "STANDARD",
      "gbpCallToActionType": "LEARN_MORE",
      "gbpCallToActionUrl": "https://example.com/hours"
    }
  }'
```

**Python:**

```python
import requests

url = "https://api.postfa.st/social-posts"
headers = {
    "pf-api-key": "YOUR_API_KEY",
    "Content-Type": "application/json"
}

data = {
    "posts": [
        {
            "content": "We've just expanded our opening hours! Now open until 9 PM on weekdays. Come visit us for all your needs.",
            "scheduledAt": "2026-04-10T09:00:00.000Z",
            "socialMediaId": "550e8400-e29b-41d4-a716-446655440011"
        }
    ],
    "controls": {
        "gbpLocationId": "accounts/109049740544589765860/locations/4875357571247123933",
        "gbpTopicType": "STANDARD",
        "gbpCallToActionType": "LEARN_MORE",
        "gbpCallToActionUrl": "https://example.com/hours"
    }
}

response = requests.post(url, headers=headers, json=data)
result = response.json()
```

---

#### Platform: Google Business Profile — Offer Post

**Node.js:**

```javascript
const response = await fetch('https://api.postfa.st/social-posts', {
  method: 'POST',
  headers: {
    'pf-api-key': 'YOUR_API_KEY',
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({
    posts: [
      {
        content: "Summer Sale! 25% off all items this month. Shop in-store or online.",
        mediaItems: [
          {
            key: "image/b8c9d0e1-f2a3-4567-8901-23456789abcd.jpg",
            type: "IMAGE",
            sortOrder: 0
          }
        ],
        scheduledAt: "2026-06-01T08:00:00.000Z",
        socialMediaId: "550e8400-e29b-41d4-a716-446655440011"
      }
    ],
    controls: {
      gbpLocationId: "accounts/109049740544589765860/locations/4875357571247123933",
      gbpTopicType: "OFFER",
      gbpEventTitle: "Summer Sale",
      gbpEventStartDate: "2026-06-01T00:00:00Z",
      gbpEventEndDate: "2026-06-30T23:59:00Z",
      gbpOfferCouponCode: "SUMMER25",
      gbpOfferRedeemUrl: "https://example.com/redeem",
      gbpOfferTerms: "25% off all items. Online and in-store.",
      gbpCallToActionType: "SHOP",
      gbpCallToActionUrl: "https://example.com/sale"
    }
  })
});

const data = await response.json();
```

**cURL:**

```bash
curl -X POST "https://api.postfa.st/social-posts" \
  -H "pf-api-key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "posts": [
      {
        "content": "Summer Sale! 25% off all items this month. Shop in-store or online.",
        "mediaItems": [
          {
            "key": "image/b8c9d0e1-f2a3-4567-8901-23456789abcd.jpg",
            "type": "IMAGE",
            "sortOrder": 0
          }
        ],
        "scheduledAt": "2026-06-01T08:00:00.000Z",
        "socialMediaId": "550e8400-e29b-41d4-a716-446655440011"
      }
    ],
    "controls": {
      "gbpLocationId": "accounts/109049740544589765860/locations/4875357571247123933",
      "gbpTopicType": "OFFER",
      "gbpEventTitle": "Summer Sale",
      "gbpEventStartDate": "2026-06-01T00:00:00Z",
      "gbpEventEndDate": "2026-06-30T23:59:00Z",
      "gbpOfferCouponCode": "SUMMER25",
      "gbpOfferRedeemUrl": "https://example.com/redeem",
      "gbpOfferTerms": "25% off all items. Online and in-store.",
      "gbpCallToActionType": "SHOP",
      "gbpCallToActionUrl": "https://example.com/sale"
    }
  }'
```

**Python:**

```python
import requests

url = "https://api.postfa.st/social-posts"
headers = {
    "pf-api-key": "YOUR_API_KEY",
    "Content-Type": "application/json"
}

data = {
    "posts": [
        {
            "content": "Summer Sale! 25% off all items this month. Shop in-store or online.",
            "mediaItems": [
                {
                    "key": "image/b8c9d0e1-f2a3-4567-8901-23456789abcd.jpg",
                    "type": "IMAGE",
                    "sortOrder": 0
                }
            ],
            "scheduledAt": "2026-06-01T08:00:00.000Z",
            "socialMediaId": "550e8400-e29b-41d4-a716-446655440011"
        }
    ],
    "controls": {
        "gbpLocationId": "accounts/109049740544589765860/locations/4875357571247123933",
        "gbpTopicType": "OFFER",
        "gbpEventTitle": "Summer Sale",
        "gbpEventStartDate": "2026-06-01T00:00:00Z",
        "gbpEventEndDate": "2026-06-30T23:59:00Z",
        "gbpOfferCouponCode": "SUMMER25",
        "gbpOfferRedeemUrl": "https://example.com/redeem",
        "gbpOfferTerms": "25% off all items. Online and in-store.",
        "gbpCallToActionType": "SHOP",
        "gbpCallToActionUrl": "https://example.com/sale"
    }
}

response = requests.post(url, headers=headers, json=data)
result = response.json()
```

---

### DELETE /social-posts/:id

Deletes a scheduled or failed social post.

**Rate Limit:** 160 requests per hour

**Path params:** `id` (UUID of the post to delete). **Response:** `{ "deleted": true }`.

**Node.js:**

```javascript
const response = await fetch('https://api.postfa.st/social-posts/8d9e0f1a-2b3c-4567-8901-def123456789', {
  method: 'DELETE',
  headers: {
    'pf-api-key': 'YOUR_API_KEY'
  }
});

const result = await response.json();
```

**cURL:**

```bash
curl -X DELETE "https://api.postfa.st/social-posts/8d9e0f1a-2b3c-4567-8901-def123456789" \
  -H "pf-api-key: YOUR_API_KEY"
```

**Python:**

```python
import requests

url = "https://api.postfa.st/social-posts/8d9e0f1a-2b3c-4567-8901-def123456789"
headers = {
    "pf-api-key": "YOUR_API_KEY"
}

response = requests.delete(url, headers=headers)
result = response.json()
```

### GET /social-posts/analytics

Fetch published posts with their latest performance metrics. Only returns posts that have been successfully published (with a `platformPostId`).

**Rate Limit:** 350 requests per hour

**Query params:** `startDate` (required, ISO 8601), `endDate` (required, ISO 8601), `socialMediaIds` (optional, comma-separated UUIDs).

**Notes:** only PUBLISHED posts with a `platformPostId`; LinkedIn personal accounts excluded; count metrics are strings (bigint), e.g. `"1234"`; `latestMetric` is null until fetched; no pagination (keep date ranges reasonable); `extras` holds platform-specific metrics.

**Video watch time:** when available, `latestMetric` also includes `avgWatchTimeSeconds`, `totalWatchTimeSeconds` (seconds, rounded to 2 decimals), and `videoViews` as plain JSON numbers (not bigint strings). Coverage: Facebook, Instagram Reels, YouTube, Pinterest, LinkedIn organization Pages, and TikTok; `videoViews` only where the platform reports it separately from `impressions` (omitted on Instagram, YouTube, and TikTok). TikTok watch-time arrives about 24-48h after publishing and also exposes raw `total_time_watched`, `average_time_watched`, `full_video_watched_rate`, and `favorites` in `extras`. Omitted on Threads, X, and personal accounts, and omitted per-field when there is no data.

**Instagram save rate and reel skip rate:** on Instagram, `latestMetric` also carries two derived fields as plain JSON numbers (rounded to 2 decimals): `saveRate` (`saves / reach` as a percentage, on Instagram feed posts, reels, and carousels) and `reelsSkipRate` (Instagram Reels only: the share of viewers who skipped the reel in the first 3 seconds; the raw `reels_skip_rate` also appears in `extras`). Both are Instagram-only and omitted on every other platform.

#### Example: Basic Usage

Fetch analytics for all posts in a date range

**Node.js:**

```javascript
const response = await fetch('https://api.postfa.st/social-posts/analytics?startDate=2026-01-01T00:00:00.000Z&endDate=2026-01-31T23:59:59.999Z', {
  method: 'GET',
  headers: {
    'pf-api-key': 'YOUR_API_KEY'
  }
});

const data = await response.json();
// Returns all published posts with metrics in January 2026
// latestMetric may be null if metrics haven't been fetched yet
```

**cURL:**

```bash
curl -G "https://api.postfa.st/social-posts/analytics" \
  --data-urlencode "startDate=2026-01-01T00:00:00.000Z" \
  --data-urlencode "endDate=2026-01-31T23:59:59.999Z" \
  -H "pf-api-key: YOUR_API_KEY"
```

**Python:**

```python
import requests

url = "https://api.postfa.st/social-posts/analytics"
params = {
    "startDate": "2026-01-01T00:00:00.000Z",
    "endDate": "2026-01-31T23:59:59.999Z"
}
headers = {
    "pf-api-key": "YOUR_API_KEY"
}

response = requests.get(url, params=params, headers=headers)
result = response.json()

for post in result["data"]:
    print(f"Post: {post['content'][:50]}...")
    if post.get("latestMetric"):
        print(f"  Likes: {post['latestMetric']['likes']}")
        print(f"  Views: {post['latestMetric']['impressions']}")
```

---

#### Example: Filter by Accounts

Fetch analytics for specific social media accounts

**Node.js:**

```javascript
const response = await fetch('https://api.postfa.st/social-posts/analytics?startDate=2026-01-01T00:00:00.000Z&endDate=2026-01-31T23:59:59.999Z&socialMediaIds=account-uuid-1,account-uuid-2', {
  method: 'GET',
  headers: {
    'pf-api-key': 'YOUR_API_KEY'
  }
});

const data = await response.json();
// Returns published posts only for the specified accounts
// Get account IDs from GET /social-media/my-social-accounts
```

**cURL:**

```bash
curl -G "https://api.postfa.st/social-posts/analytics" \
  --data-urlencode "startDate=2026-01-01T00:00:00.000Z" \
  --data-urlencode "endDate=2026-01-31T23:59:59.999Z" \
  --data-urlencode "socialMediaIds=account-uuid-1,account-uuid-2" \
  -H "pf-api-key: YOUR_API_KEY"
```

**Python:**

```python
import requests

url = "https://api.postfa.st/social-posts/analytics"
params = {
    "startDate": "2026-01-01T00:00:00.000Z",
    "endDate": "2026-01-31T23:59:59.999Z",
    "socialMediaIds": "account-uuid-1,account-uuid-2"
}
headers = {
    "pf-api-key": "YOUR_API_KEY"
}

response = requests.get(url, params=params, headers=headers)
result = response.json()
```

---

#### Example: Process Metrics

Aggregate engagement metrics across posts

**Node.js:**

```javascript
const response = await fetch('https://api.postfa.st/social-posts/analytics?startDate=2026-01-01T00:00:00.000Z&endDate=2026-01-31T23:59:59.999Z', {
  method: 'GET',
  headers: {
    'pf-api-key': 'YOUR_API_KEY'
  }
});

const { data } = await response.json();

// Aggregate metrics (values are strings — parse to numbers)
const totals = data.reduce((acc, post) => {
  const m = post.latestMetric;
  if (!m) return acc;
  return {
    likes: acc.likes + parseInt(m.likes || "0"),
    comments: acc.comments + parseInt(m.comments || "0"),
    shares: acc.shares + parseInt(m.shares || "0"),
    impressions: acc.impressions + parseInt(m.impressions || "0"),
  };
}, { likes: 0, comments: 0, shares: 0, impressions: 0 });

console.log(`Total engagement: ${totals.likes + totals.comments + totals.shares}`);
console.log(`Total impressions: ${totals.impressions}`);
```

**cURL:**

```bash
# Fetch analytics and process with jq
curl -G "https://api.postfa.st/social-posts/analytics" \
  --data-urlencode "startDate=2026-01-01T00:00:00.000Z" \
  --data-urlencode "endDate=2026-01-31T23:59:59.999Z" \
  -H "pf-api-key: YOUR_API_KEY" | jq '.data[] | select(.latestMetric != null) | {content: .content[:50], likes: .latestMetric.likes, impressions: .latestMetric.impressions}'
```

**Python:**

```python
import requests

url = "https://api.postfa.st/social-posts/analytics"
params = {
    "startDate": "2026-01-01T00:00:00.000Z",
    "endDate": "2026-01-31T23:59:59.999Z"
}
headers = {
    "pf-api-key": "YOUR_API_KEY"
}

response = requests.get(url, params=params, headers=headers)
result = response.json()

# Aggregate metrics (values are strings — parse to int)
totals = {"likes": 0, "comments": 0, "shares": 0, "impressions": 0}

for post in result["data"]:
    m = post.get("latestMetric")
    if not m:
        continue
    totals["likes"] += int(m.get("likes") or "0")
    totals["comments"] += int(m.get("comments") or "0")
    totals["shares"] += int(m.get("shares") or "0")
    totals["impressions"] += int(m.get("impressions") or "0")

engagement = totals["likes"] + totals["comments"] + totals["shares"]
print(f"Total engagement: {engagement}")
print(f"Total impressions: {totals['impressions']}")
```

---

## Social Inbox

A comments inbox for the workspace's own published posts, on TikTok, Instagram, Facebook Pages, and Threads. Comments arrive from the moment an account is connected onward and typically within seconds of being posted; comments made before that are not imported. Every route uses the same `pf-api-key` header, GETs return `200 OK`, and every POST returns `201 Created`.

**Reply capability is server-computed.** Each conversation carries `canReply`, `maxReplyLength`, `maxPrivateReplyLengthBytes`, `windowState`, and `disabledReason`. Gate your UI and your validation on those fields, never on hardcoded platform rules. For context the current public reply caps are TikTok 1,200, Instagram 2,200, Facebook 8,000, and Threads 500 characters, but `maxReplyLength` is the authoritative value.

**List envelope** (both list endpoints): `{ "data": [...], "totalCount": <number>, "pageInfo": { "hasNextPage": <boolean>, "page": <number>, "perPage": <number> } }`. `pageInfo.page` is a 1-based display number while the `page` query param is 0-based, so `page=0` returns `page: 1`.

**Conversation object:** `id`, `socialMediaId`, `platform` (TIKTOK | INSTAGRAM | FACEBOOK | THREADS), `kind` (always `COMMENT_THREAD` today), `externalConversationId`, `contactId`, `socialPostId` (set when the post was published through PostFast), `externalPostId`, `status` (OPEN | SNOOZED | CLOSED), `assignedToUserId`, `lastItemAt`, `lastInboundAt`, `lastReadAt`, `unreadCount`, `lastItemPreview` (~140-char snippet), `participantUsername`, `participantDisplayName`, `participantAvatarUrl`, `windowState` (always `NOT_APPLICABLE` today), `canReply`, `maxReplyLength`, `maxPrivateReplyLengthBytes` (1000 on Instagram, null elsewhere), `disabledReason`, and `postPreview` (`caption`, `thumbnailUrl`, `permalink`).

**Item object:** `id`, `conversationId`, `kind` (`COMMENT`), `direction` (INBOUND | OUTBOUND), `externalItemId` (null on an outbound reply still sending), `parentExternalItemId`, `authorUsername`, `text`, `state` (VISIBLE | HIDDEN | DELETED), `deliveryStatus` (outbound only: PENDING | SENT | FAILED), `authoredByUserId` (null for API-key replies, because keys are workspace-scoped rather than user-scoped), `platformCreatedAt`, `createdAt`, and `canPrivateReply` (Instagram inbound comments only).

**Errors** come back as `{ "statusCode": <number>, "message": "<code>" }`, where `message` is usually a stable `inbox.*` code: `inbox.readForbidden`, `inbox.replyForbidden`, `inbox.moderateForbidden`, `inbox.conversationNotFound`, `inbox.itemNotFound`, `inbox.accountNotFound`, `inbox.replyEmpty`, `inbox.replyTooLong`, `inbox.replyNotSupported`, `inbox.replyNotSupportedOnPlatform`, `inbox.replyInProgress`, `inbox.repetitiveReply`, `inbox.replyFailed`, `inbox.rateLimited`, `inbox.privateReplyNotSupported`, `inbox.privateReplyWindowExpired`, `inbox.privateReplyAlreadySent`, `inbox.privateReplyFailed`, `inbox.hideNotSupported`, `inbox.deleteNotSupported`, `inbox.assigneeNotMember`, `inbox.unsupportedAction`. A throttled request returns `{ "statusCode": 429, "message": "Too many requests. Please try again later." }`.

### GET /social-inbox/conversations

Lists comment conversations across your connected accounts, newest activity first. Each row carries the server-computed reply capability, unread count, and triage state.

**Rate Limit:** 300 requests per hour

**Query params:** `page` (0-based, default 0), `limit` (1-50, default 20), `platforms` (comma-separated: TIKTOK, INSTAGRAM, FACEBOOK, THREADS), `socialMediaIds` (comma-separated UUIDs), `statuses` (comma-separated: OPEN, SNOOZED, CLOSED), `unreadOnly` (boolean), `assignedToUserId` (UUID). **Response:** the list envelope with `data[]` of Conversation, newest activity first.

**Node.js:**

```javascript
const params = new URLSearchParams({
  platforms: 'INSTAGRAM',
  unreadOnly: 'true',
  limit: '20'
});

const response = await fetch(
  `https://api.postfa.st/social-inbox/conversations?${params}`,
  {
    method: 'GET',
    headers: {
      'pf-api-key': 'YOUR_API_KEY'
    }
  }
);

const { data, totalCount, pageInfo } = await response.json();

// canReply / maxReplyLength are computed per conversation - use them
// instead of hardcoding per-platform rules.
for (const conversation of data) {
  console.log(conversation.id, conversation.canReply, conversation.maxReplyLength);
}
```

**cURL:**

```bash
curl -X GET "https://api.postfa.st/social-inbox/conversations?platforms=INSTAGRAM&unreadOnly=true&limit=20" \
  -H "pf-api-key: YOUR_API_KEY"
```

**Python:**

```python
import requests

url = "https://api.postfa.st/social-inbox/conversations"
headers = {
    "pf-api-key": "YOUR_API_KEY"
}
params = {
    "platforms": "INSTAGRAM",
    "unreadOnly": "true",
    "limit": 20,
}

response = requests.get(url, headers=headers, params=params)
result = response.json()

# Gate your UI on the server-computed capability fields
for conversation in result["data"]:
    print(conversation["id"], conversation["canReply"], conversation["maxReplyLength"])
```

### GET /social-inbox/conversations/:id

Fetches one conversation with its full reply capability.

**Rate Limit:** 300 requests per hour

**Path params:** `id` (conversation UUID). **Response:** one Conversation. An id that is not in the workspace tied to the API key returns `200` with a `null` body, not a `404`.

**Node.js:**

```javascript
const response = await fetch(
  'https://api.postfa.st/social-inbox/conversations/9f4c1a2e-5b6d-4c7e-8f90-123456789abc',
  {
    method: 'GET',
    headers: {
      'pf-api-key': 'YOUR_API_KEY'
    }
  }
);

const conversation = await response.json();

// An id outside your workspace comes back as null, not a 404
if (conversation === null) {
  throw new Error('Conversation not found in this workspace');
}
```

**cURL:**

```bash
curl -X GET "https://api.postfa.st/social-inbox/conversations/9f4c1a2e-5b6d-4c7e-8f90-123456789abc" \
  -H "pf-api-key: YOUR_API_KEY"
```

**Python:**

```python
import requests

url = "https://api.postfa.st/social-inbox/conversations/9f4c1a2e-5b6d-4c7e-8f90-123456789abc"
headers = {
    "pf-api-key": "YOUR_API_KEY"
}

response = requests.get(url, headers=headers)
conversation = response.json()

# An id outside your workspace comes back as null, not a 404
if conversation is None:
    raise RuntimeError("Conversation not found in this workspace")
```

### GET /social-inbox/conversations/:id/items

Lists a conversation's comments and your replies, oldest first by default.

**Rate Limit:** 300 requests per hour

**Path params:** `id` (conversation UUID). **Query params:** `page`, `limit` (as above), `order` (ASC default | DESC). **Response:** the list envelope with `data[]` of Item. Replies sent through PostFast appear exactly once (the platform's echo is deduplicated), and edits, hides, and deletes made on the platform sync back automatically.

**Node.js:**

```javascript
const response = await fetch(
  'https://api.postfa.st/social-inbox/conversations/9f4c1a2e-5b6d-4c7e-8f90-123456789abc/items?order=ASC&limit=50',
  {
    method: 'GET',
    headers: {
      'pf-api-key': 'YOUR_API_KEY'
    }
  }
);

const { data } = await response.json();

// data[].id is the item id the reply and moderation routes expect
const latestInbound = [...data].reverse().find((item) => item.direction === 'INBOUND');
```

**cURL:**

```bash
curl -X GET "https://api.postfa.st/social-inbox/conversations/9f4c1a2e-5b6d-4c7e-8f90-123456789abc/items?order=ASC&limit=50" \
  -H "pf-api-key: YOUR_API_KEY"
```

**Python:**

```python
import requests

url = "https://api.postfa.st/social-inbox/conversations/9f4c1a2e-5b6d-4c7e-8f90-123456789abc/items"
headers = {
    "pf-api-key": "YOUR_API_KEY"
}
params = {"order": "ASC", "limit": 50}

response = requests.get(url, headers=headers, params=params)
items = response.json()["data"]

# item["id"] is what the reply and moderation routes expect
inbound = [item for item in items if item["direction"] == "INBOUND"]
```

### GET /social-inbox/unread-count

Total unread comments across all conversations in the workspace.

**Rate Limit:** 300 requests per hour

No parameters. **Response:** `{ "unreadCount": 7 }`, summed across every conversation in the workspace.

**Node.js:**

```javascript
const response = await fetch('https://api.postfa.st/social-inbox/unread-count', {
  method: 'GET',
  headers: {
    'pf-api-key': 'YOUR_API_KEY'
  }
});

const { unreadCount } = await response.json();
```

**cURL:**

```bash
curl -X GET "https://api.postfa.st/social-inbox/unread-count" \
  -H "pf-api-key: YOUR_API_KEY"
```

**Python:**

```python
import requests

url = "https://api.postfa.st/social-inbox/unread-count"
headers = {
    "pf-api-key": "YOUR_API_KEY"
}

response = requests.get(url, headers=headers)
unread_count = response.json()["unreadCount"]
```

### POST /social-inbox/items/:id/reply

Replies publicly under a comment. Pass the comment item id, not the conversation id.

**Rate Limit:** 350 requests per day, 150 per minute

**Path params:** `id` (the comment ITEM UUID, not the conversation id). **Body:** `text` (required, must fit the conversation's `maxReplyLength`), `idempotencyKey` (optional; a retry with the same key will not double-send). **Response 201:** the created OUTBOUND Item, `deliveryStatus` `SENT` on success. A second reply request while one is still in flight returns `inbox.replyInProgress`. Sending the same reply text repeatedly across a workspace returns `inbox.repetitiveReply` (case- and spacing-insensitive, rolling 24-hour window, short courtesy replies exempt); vary the wording rather than retrying identical text.

**Node.js:**

```javascript
// The path takes the comment ITEM id, not the conversation id
const response = await fetch(
  'https://api.postfa.st/social-inbox/items/3c2b1a09-8765-4321-fedc-ba9876543210/reply',
  {
    method: 'POST',
    headers: {
      'pf-api-key': 'YOUR_API_KEY',
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({
      text: 'Thanks! Yes, we ship EU-wide.',
      idempotencyKey: 'reply-3c2b1a09-1'
    })
  }
);

const item = await response.json();
// 201 Created -> item.direction === 'OUTBOUND', item.deliveryStatus === 'SENT'
```

**cURL:**

```bash
curl -X POST "https://api.postfa.st/social-inbox/items/3c2b1a09-8765-4321-fedc-ba9876543210/reply" \
  -H "pf-api-key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"text": "Thanks! Yes, we ship EU-wide."}'
```

**Python:**

```python
import requests

# The path takes the comment ITEM id, not the conversation id
url = "https://api.postfa.st/social-inbox/items/3c2b1a09-8765-4321-fedc-ba9876543210/reply"
headers = {
    "pf-api-key": "YOUR_API_KEY",
    "Content-Type": "application/json",
}
payload = {
    "text": "Thanks! Yes, we ship EU-wide.",
    "idempotencyKey": "reply-3c2b1a09-1",
}

response = requests.post(url, headers=headers, json=payload)
item = response.json()  # 201 Created
```

### POST /social-inbox/items/:id/private-reply

Instagram only: sends one private reply to a comment. It arrives as a direct message to the commenter.

**Rate Limit:** 300 requests per day, 100 per minute

Instagram only, and only for a comment whose Item has `canPrivateReply: true`. **Path params:** `id` (comment item UUID). **Body:** `text` (required, max 1,000 BYTES, so emoji and non-Latin characters cost more than one character each), `idempotencyKey` (optional). **Response 201:** the created OUTBOUND Item. Exactly ONE private reply is allowed per comment and only within 7 days of that comment: a second attempt returns `inbox.privateReplyAlreadySent`, an expired window returns `inbox.privateReplyWindowExpired`. The reply may land in the recipient's Message Requests folder.

**Node.js:**

```javascript
// Instagram only, and only when the comment has canPrivateReply: true
const response = await fetch(
  'https://api.postfa.st/social-inbox/items/3c2b1a09-8765-4321-fedc-ba9876543210/private-reply',
  {
    method: 'POST',
    headers: {
      'pf-api-key': 'YOUR_API_KEY',
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({
      text: 'Hi Maria, here is the EU shipping table.'
    })
  }
);

const item = await response.json();
// One private reply per comment, within 7 days of that comment
```

**cURL:**

```bash
curl -X POST "https://api.postfa.st/social-inbox/items/3c2b1a09-8765-4321-fedc-ba9876543210/private-reply" \
  -H "pf-api-key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"text": "Hi Maria, here is the EU shipping table."}'
```

**Python:**

```python
import requests

# Instagram only, and only when the comment has canPrivateReply: true
url = "https://api.postfa.st/social-inbox/items/3c2b1a09-8765-4321-fedc-ba9876543210/private-reply"
headers = {
    "pf-api-key": "YOUR_API_KEY",
    "Content-Type": "application/json",
}
text = "Hi Maria, here is the EU shipping table."

# The cap is 1000 BYTES, not characters
assert len(text.encode("utf-8")) <= 1000

response = requests.post(url, headers=headers, json={"text": text})
item = response.json()
```

### POST /social-inbox/items/:id/state

Moderates a comment on the platform: hide, unhide, or delete.

**Rate Limit:** 200 requests per hour

**Path params:** `id` (comment item UUID). **Body:** `action` (required: HIDE | UNHIDE | DELETE). **Response 201:** the updated Item. `DELETE` removes the comment on the platform and cannot be undone; it is unavailable on Threads (`inbox.deleteNotSupported`). Hide and unhide work on all four platforms.

**Node.js:**

```javascript
const response = await fetch(
  'https://api.postfa.st/social-inbox/items/3c2b1a09-8765-4321-fedc-ba9876543210/state',
  {
    method: 'POST',
    headers: {
      'pf-api-key': 'YOUR_API_KEY',
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({ action: 'HIDE' })
  }
);

const item = await response.json();
// item.state === 'HIDDEN'. Use 'UNHIDE' to reverse it.
// 'DELETE' removes the comment on the platform and cannot be undone.
```

**cURL:**

```bash
curl -X POST "https://api.postfa.st/social-inbox/items/3c2b1a09-8765-4321-fedc-ba9876543210/state" \
  -H "pf-api-key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"action": "HIDE"}'
```

**Python:**

```python
import requests

url = "https://api.postfa.st/social-inbox/items/3c2b1a09-8765-4321-fedc-ba9876543210/state"
headers = {
    "pf-api-key": "YOUR_API_KEY",
    "Content-Type": "application/json",
}

# HIDE / UNHIDE / DELETE. DELETE is permanent and unavailable on Threads.
response = requests.post(url, headers=headers, json={"action": "HIDE"})
item = response.json()
```

### POST /social-inbox/conversations/:id/read

Marks a conversation read (zeroes its unread count). Internal to PostFast, so nothing changes on the platform.

**Rate Limit:** 600 requests per hour

**Path params:** `id` (conversation UUID). No body. **Response 201:** the updated Conversation with `unreadCount: 0`. This is internal to PostFast: nothing changes on the platform.

**Node.js:**

```javascript
const response = await fetch(
  'https://api.postfa.st/social-inbox/conversations/9f4c1a2e-5b6d-4c7e-8f90-123456789abc/read',
  {
    method: 'POST',
    headers: {
      'pf-api-key': 'YOUR_API_KEY'
    }
  }
);

const conversation = await response.json();
// conversation.unreadCount === 0
```

**cURL:**

```bash
curl -X POST "https://api.postfa.st/social-inbox/conversations/9f4c1a2e-5b6d-4c7e-8f90-123456789abc/read" \
  -H "pf-api-key: YOUR_API_KEY"
```

**Python:**

```python
import requests

url = "https://api.postfa.st/social-inbox/conversations/9f4c1a2e-5b6d-4c7e-8f90-123456789abc/read"
headers = {
    "pf-api-key": "YOUR_API_KEY"
}

response = requests.post(url, headers=headers)
conversation = response.json()  # unreadCount is now 0
```

### POST /social-inbox/conversations/:id/status

Triage: sets the conversation status.

**Rate Limit:** 200 requests per hour

**Path params:** `id` (conversation UUID). **Body:** `status` (required: OPEN | SNOOZED | CLOSED). **Response 201:** the updated Conversation. Triage state is PostFast state only.

**Node.js:**

```javascript
const response = await fetch(
  'https://api.postfa.st/social-inbox/conversations/9f4c1a2e-5b6d-4c7e-8f90-123456789abc/status',
  {
    method: 'POST',
    headers: {
      'pf-api-key': 'YOUR_API_KEY',
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({ status: 'CLOSED' })
  }
);

const conversation = await response.json();
```

**cURL:**

```bash
curl -X POST "https://api.postfa.st/social-inbox/conversations/9f4c1a2e-5b6d-4c7e-8f90-123456789abc/status" \
  -H "pf-api-key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"status": "CLOSED"}'
```

**Python:**

```python
import requests

url = "https://api.postfa.st/social-inbox/conversations/9f4c1a2e-5b6d-4c7e-8f90-123456789abc/status"
headers = {
    "pf-api-key": "YOUR_API_KEY",
    "Content-Type": "application/json",
}

# OPEN / SNOOZED / CLOSED
response = requests.post(url, headers=headers, json={"status": "CLOSED"})
conversation = response.json()
```

### POST /social-inbox/conversations/:id/assign

Assigns the conversation to a workspace member for follow-up, or unassigns it.

**Rate Limit:** 200 requests per hour

**Path params:** `id` (conversation UUID). **Body:** `assigneeUserId` (optional UUID; omit it, i.e. send `{}`, to unassign). **Response 201:** the updated Conversation. A user who is not a member of the workspace returns `inbox.assigneeNotMember`.

**Node.js:**

```javascript
const response = await fetch(
  'https://api.postfa.st/social-inbox/conversations/9f4c1a2e-5b6d-4c7e-8f90-123456789abc/assign',
  {
    method: 'POST',
    headers: {
      'pf-api-key': 'YOUR_API_KEY',
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({
      assigneeUserId: 'c7d8e9f0-1234-5678-9abc-def012345678'
    })
  }
);

const conversation = await response.json();
// Send {} instead to unassign
```

**cURL:**

```bash
curl -X POST "https://api.postfa.st/social-inbox/conversations/9f4c1a2e-5b6d-4c7e-8f90-123456789abc/assign" \
  -H "pf-api-key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"assigneeUserId": "c7d8e9f0-1234-5678-9abc-def012345678"}'

# Unassign by sending an empty object
# -d '{}'
```

**Python:**

```python
import requests

url = "https://api.postfa.st/social-inbox/conversations/9f4c1a2e-5b6d-4c7e-8f90-123456789abc/assign"
headers = {
    "pf-api-key": "YOUR_API_KEY",
    "Content-Type": "application/json",
}

# Omit assigneeUserId (send {}) to unassign
payload = {"assigneeUserId": "c7d8e9f0-1234-5678-9abc-def012345678"}

response = requests.post(url, headers=headers, json=payload)
conversation = response.json()
```

## Error Responses

- `400 Bad Request` - Malformed request, or scheduling a post to a DISABLED account (reconnect it in the app first).
- `401 Unauthorized` - API key missing, invalid, or not authorized for the workspace.
- `403 Forbidden` - Key is valid but lacks permission for the action (e.g. another workspace's resources).
- `404 Not Found` - Resource (e.g. a post to delete) not found.
- `429 Too Many Requests` - Rate limit exceeded for the endpoint.
- `500 Internal Server Error` - Unexpected server error.

---

Full endpoint list: POST /file/get-signed-upload-urls, GET /social-media/my-social-accounts, GET /social-media/:id/follower-history, GET /social-media/search-places, GET /social-media/:id/pinterest-boards, GET /social-media/:id/youtube-playlists, GET /social-media/:id/gbp-locations, GET /social-media/:id/tiktok-sounds, POST /social-media/connect-link, GET /social-posts, POST /social-posts, DELETE /social-posts/:id, GET /social-posts/analytics, GET /social-inbox/conversations, GET /social-inbox/conversations/:id, GET /social-inbox/conversations/:id/items, GET /social-inbox/unread-count, POST /social-inbox/items/:id/reply, POST /social-inbox/items/:id/private-reply, POST /social-inbox/items/:id/state, POST /social-inbox/conversations/:id/read, POST /social-inbox/conversations/:id/status, POST /social-inbox/conversations/:id/assign.