# Delete Account
Source: https://docs.postsyncer.com/api-reference/accounts/delete
DELETE accounts/{id}
Delete a social media account from your workspace
## Delete Account
Deletes a social media account from your workspace. This action cannot be undone and will remove the account from all future posts.
### Request
The ID of the account to delete
```bash theme={null}
curl -X DELETE "https://postsyncer.com/api/v1/accounts/123" \
-H "Authorization: Bearer YOUR_API_TOKEN"
```
### Response
Account deleted successfully
```json theme={null}
{
"message": "Account deleted successfully"
}
```
### Code Examples
```bash cURL theme={null}
curl -X DELETE "https://postsyncer.com/api/v1/accounts/123" \
-H "Authorization: Bearer YOUR_API_TOKEN"
```
```javascript Node.js theme={null}
const axios = require('axios');
const deleteAccount = async (accountId) => {
try {
const response = await axios.delete(`https://postsyncer.com/api/v1/accounts/${accountId}`, {
headers: {
'Authorization': 'Bearer YOUR_API_TOKEN'
}
});
console.log('Account deleted:', response.data.message);
} catch (error) {
console.error('Error deleting account:', error.response?.data || error.message);
}
};
deleteAccount(123);
```
```php PHP theme={null}
```
```python Python theme={null}
import requests
account_id = 123
headers = {
'Authorization': 'Bearer YOUR_API_TOKEN'
}
response = requests.delete(
f'https://postsyncer.com/api/v1/accounts/{account_id}',
headers=headers
)
if response.status_code == 200:
result = response.json()
print(result['message'])
else:
print(f"Error: {response.status_code}")
print(response.text)
```
### Error Codes
Missing or invalid API token
Token does not have 'accounts' permission
Account not found or user does not have access to the account's workspace
Invalid account ID provided
# List Accounts
Source: https://docs.postsyncer.com/api-reference/accounts/list
GET accounts
Retrieve all social media accounts from your workspaces
## List Accounts
Retrieves all social media accounts from workspaces that the authenticated user has access to.
### Request
```bash theme={null}
curl -X GET "https://postsyncer.com/api/v1/accounts" \
-H "Authorization: Bearer YOUR_API_TOKEN"
```
### Response
Array of account objects
```json theme={null}
{
"data": [
{
"id": 136,
"workspace_id": 12,
"platform": "twitter",
"username": "heyabdulmejid",
"name": "Abdul",
"avatar": "https://pbs.twimg.com/profile_images/1878383928995684352/3Xw5HTsk_400x400.jpg"
},
{
"id": 245,
"workspace_id": 12,
"platform": "twitter",
"username": "username1",
"name": "Account Name",
"avatar": "https://pbs.twimg.com/profile_images/188258dddddddd5070692626432/3oMtYxt5_normal.png"
}
]
}
```
### Code Examples
```bash cURL theme={null}
curl -X GET "https://postsyncer.com/api/v1/accounts" \
-H "Authorization: Bearer YOUR_API_TOKEN"
```
```javascript Node.js theme={null}
const axios = require('axios');
const getAccounts = async () => {
try {
const response = await axios.get('https://postsyncer.com/api/v1/accounts', {
headers: {
'Authorization': 'Bearer YOUR_API_TOKEN'
}
});
console.log('Accounts:', response.data);
return response.data;
} catch (error) {
console.error('Error fetching accounts:', error.response?.data || error.message);
}
};
getAccounts();
```
```php PHP theme={null}
```
```python Python theme={null}
import requests
headers = {
'Authorization': 'Bearer YOUR_API_TOKEN'
}
response = requests.get(
'https://postsyncer.com/api/v1/accounts',
headers=headers
)
if response.status_code == 200:
accounts = response.json()
print(f"Found {len(accounts['data'])} accounts")
for account in accounts['data']:
print(f"Account {account['id']}: {account['platform']} - {account['username']}")
print(f" Active: {account['is_active']}")
else:
print(f"Error: {response.status_code}")
print(response.text)
```
### Error Codes
Missing or invalid API token
Token does not have 'accounts' permission
# Analytics - by account
Source: https://docs.postsyncer.com/api-reference/analytics/account
GET analytics/accounts/{account}
Cumulative metrics for one social account, plus each platform post row
## By account
Returns **`total`** across all `platform_posts` for the account, and **`platform_posts`**: each publication row with `post_id`, `platform`, `status`, `post_url`, and `analytics`.
### Request
Social account id (same as in [list accounts](/api-reference/accounts/list)).
```bash theme={null}
curl -X GET "https://postsyncer.com/api/v1/analytics/accounts/136" \
-H "Authorization: Bearer YOUR_API_TOKEN"
```
### Response
Account id from the path
Aggregated metrics for this account
One object per platform post row for this account, **newest first** (`platform_post_id` descending). Each item: `platform_post_id`, `post_id`, `platform`, `status`, `post_url`, `analytics` (same metric keys as `total` when published, or `{}`).
```json theme={null}
{
"account_id": 136,
"total": {
"comments": 16,
"likes": 84,
"shares": 12,
"impressions": 2500,
"quotes": 4,
"saves": 20,
"engagement_rate": 4.25
},
"platform_posts": [
{
"platform_post_id": 501,
"post_id": 743,
"platform": "twitter",
"status": "PUBLISHED",
"post_url": "https://twitter.com/user/status/1234567890",
"analytics": {
"comments": 8,
"likes": 42,
"shares": 8,
"impressions": 1250,
"quotes": 2,
"saves": 12,
"engagement_rate": 4.4
}
},
{
"platform_post_id": 498,
"post_id": 740,
"platform": "twitter",
"status": "SCHEDULED",
"post_url": null,
"analytics": {}
}
]
}
```
With no rows, **`platform_posts`** is `[]` and **`total`** uses zeros for every metric (including `engagement_rate`).
### Permissions
Requires the **`posts`** ability. The account must belong to a workspace you can access.
### Errors
`401` · `403` · `404` if the account is missing or not accessible
See also: [Analytics overview](/api-reference/analytics/overview).
# Analytics - all workspaces
Source: https://docs.postsyncer.com/api-reference/analytics/all-workspaces
GET analytics
Cumulative metrics across all accessible workspaces, with per-workspace breakdown
## All workspaces
Returns **`total`** (sum of published metrics across every platform post in workspaces you belong to) and **`workspaces`** (the same metrics broken down by `workspace_id`).
No query or path parameters.
```bash theme={null}
curl -X GET "https://postsyncer.com/api/v1/analytics" \
-H "Authorization: Bearer YOUR_API_TOKEN"
```
### Response
`comments`, `likes`, `shares`, `impressions`, `quotes`, `saves`, `engagement_rate`
Each item: `workspace_id`, `total` (same metric keys). Sorted by `workspace_id`. Empty when you have no active workspaces or no platform posts.
```json theme={null}
{
"total": {
"comments": 42,
"likes": 310,
"shares": 28,
"impressions": 12000,
"quotes": 6,
"saves": 55,
"engagement_rate": 3.85
},
"workspaces": [
{
"workspace_id": 10,
"total": {
"comments": 12,
"likes": 90,
"shares": 8,
"impressions": 4000,
"quotes": 2,
"saves": 15,
"engagement_rate": 3.9
}
},
{
"workspace_id": 12,
"total": {
"comments": 30,
"likes": 220,
"shares": 20,
"impressions": 8000,
"quotes": 4,
"saves": 40,
"engagement_rate": 3.82
}
}
]
}
```
When there is no data, **`total`** still includes every key with numeric zeros (e.g. `"comments": 0`, …, `"engagement_rate": 0`) and **`workspaces`** is `[]`.
### Permissions
Requires the **`posts`** ability.
### Errors
`401` Unauthorized · `403` Forbidden
See also: [Analytics overview](/api-reference/analytics/overview).
# Analytics overview
Source: https://docs.postsyncer.com/api-reference/analytics/overview
Engagement metrics across workspaces, a single workspace, a post, or an account
## Analytics API
Most endpoints return the same core metrics as the `analytics` object on each platform row when you [get a post](/api-reference/posts/get): `comments`, `likes`, `shares`, `impressions`, `quotes`, `saves`, and `engagement_rate`. **Queue sync** (`POST …/sync`) only schedules background jobs; it does not return metrics.
Only **published** platform rows contribute non-zero metrics; other rows appear in breakdowns with `analytics: {}`.
**`engagement_rate` aggregation** (for any `total` object): weighted by impressions when impressions exist; otherwise a simple average of non-zero rates; `0` when there is no data.
All routes require the **`posts`** API ability and live under `/api/v1/analytics/…`.
| Endpoint | Purpose |
| ---------------------------------------------------------------------------- | ----------------------------------------------------------------------- |
| [GET /analytics](/api-reference/analytics/all-workspaces) | Totals across every workspace you can access, plus per-workspace totals |
| [GET /analytics/workspaces/\{workspace}](/api-reference/analytics/workspace) | Totals for one workspace |
| [GET /analytics/posts/\{post}](/api-reference/analytics/post) | Totals for one post, plus per connected account |
| [POST /analytics/posts/\{post}/sync](/api-reference/analytics/sync-post) | Queue jobs to refresh analytics for that post’s published rows |
| [GET /analytics/accounts/\{account}](/api-reference/analytics/account) | Totals for one social account, plus each platform post row |
These endpoints count toward the same [rate limit](/rate-limits) as the rest of API v1 (60 requests per minute per user).
# Analytics - by post
Source: https://docs.postsyncer.com/api-reference/analytics/post
GET analytics/posts/{post}
Aggregated analytics for one post across all accounts, plus per-account metrics
## By post
Returns **cumulative** engagement metrics for a single post (summed across every connected account / platform row) and a **per-account** breakdown. The only input is the **post ID** in the path (no query parameters).
Metrics match the `analytics` object on each item under `platforms` when you [get a post](/api-reference/posts/get): `comments`, `likes`, `shares`, `impressions`, `quotes`, `saves`, and `engagement_rate`.
For **published** platform rows, `analytics` uses the same values as the dashboard sync. For non-published rows, `analytics` is an empty object `{}`.
The **`total.engagement_rate`** value is a **weighted average** by impressions across published rows when any row has impressions; otherwise it falls back to a simple average of non-zero rates.
### Request
Post primary key (same id as in `/posts/{id}`).
```bash theme={null}
curl -X GET "https://postsyncer.com/api/v1/analytics/posts/743" \
-H "Authorization: Bearer YOUR_API_TOKEN"
```
### Response
The post id from the path
Cumulative metrics: `comments`, `likes`, `shares`, `impressions`, `quotes`, `saves`, `engagement_rate`
One entry per platform post row: `account_id`, `platform`, `status`, `post_url`, `analytics`, and `account` (same shape as list-accounts when present)
```json theme={null}
{
"post_id": 743,
"total": {
"comments": 16,
"likes": 84,
"shares": 12,
"impressions": 2500,
"quotes": 4,
"saves": 20,
"engagement_rate": 4.25
},
"accounts": [
{
"account_id": 55,
"platform": "twitter",
"status": "PUBLISHED",
"post_url": "https://twitter.com/user/status/123",
"analytics": {
"comments": 8,
"likes": 42,
"shares": 8,
"impressions": 1250,
"quotes": 2,
"saves": 12,
"engagement_rate": 4.4
},
"account": {
"id": 55,
"workspace_id": 12,
"platform": "twitter",
"username": "example",
"name": "Example",
"avatar": "https://…",
"has_expired": false,
"is_default": false,
"support_threads": true,
"is_verified": false
}
}
]
}
```
### Permissions
Requires the same **`posts`** ability as other post endpoints.
### Error codes
Missing or invalid API token
Token does not have the `posts` permission
Post does not exist or is not in any workspace the user can access
### Code examples
```bash cURL theme={null}
curl -X GET "https://postsyncer.com/api/v1/analytics/posts/743" \
-H "Authorization: Bearer YOUR_API_TOKEN"
```
```javascript Node.js theme={null}
const axios = require('axios');
const getPostAnalytics = async (postId) => {
const { data } = await axios.get(
`https://postsyncer.com/api/v1/analytics/posts/${postId}`,
{ headers: { Authorization: 'Bearer YOUR_API_TOKEN' } }
);
return data;
};
```
```python Python theme={null}
import requests
r = requests.get(
'https://postsyncer.com/api/v1/analytics/posts/743',
headers={'Authorization': 'Bearer YOUR_API_TOKEN'},
)
r.raise_for_status()
print(r.json()['total'])
```
See also: [Analytics overview](/api-reference/analytics/overview).
# Analytics - queue sync for post
Source: https://docs.postsyncer.com/api-reference/analytics/sync-post
POST analytics/posts/{post}/sync
Queue background jobs to refresh analytics for a post’s published platform rows
## Queue analytics sync
Triggers the same logic as `Post::syncAnalytics()` in the app: for each **published** platform row that has `platform_post_id`, `post_id`, and `account_id`, a job is dispatched to fetch the latest insights from the network.
**Not immediate:** responses describe how many jobs were queued. Poll [`GET /analytics/posts/{post}`](/api-reference/analytics/post) (or the dashboard) after the queue workers finish.
### Eligibility
* If the **post owner** does not have an active, trialing, or grace-period subscription, the request still returns **200** with `skipped: true`, `jobs_dispatched: 0`, and `skip_reason: "post_owner_subscription_inactive"`.
* You must belong to the post’s workspace (same access rules as reading analytics).
### Request
Post id (same as in `/posts/{id}`).
No body.
```bash theme={null}
curl -X POST "https://postsyncer.com/api/v1/analytics/posts/743/sync" \
-H "Authorization: Bearer YOUR_API_TOKEN"
```
### Response
Post id from the path
Number of insight fetch jobs queued (one per eligible published platform row)
`true` when no jobs were queued because of subscription rules on the post owner
`post_owner_subscription_inactive` when skipped; otherwise `null`
```json theme={null}
{
"post_id": 743,
"jobs_dispatched": 2,
"skipped": false,
"skip_reason": null
}
```
When the post owner’s subscription does not allow sync:
```json theme={null}
{
"post_id": 743,
"jobs_dispatched": 0,
"skipped": true,
"skip_reason": "post_owner_subscription_inactive"
}
```
### Permissions
Requires the **`posts`** ability.
### Errors
`401` · `403` · `404` if the post is missing or not in a workspace you can access
See also: [Analytics overview](/api-reference/analytics/overview).
# Analytics - by workspace
Source: https://docs.postsyncer.com/api-reference/analytics/workspace
GET analytics/workspaces/{workspace}
Cumulative metrics for all posts in one workspace
## By workspace
Returns **`total`** for every platform post row whose parent post belongs to the given workspace.
### Request
Workspace id (same as in [list workspaces](/api-reference/workspaces/list)).
```bash theme={null}
curl -X GET "https://postsyncer.com/api/v1/analytics/workspaces/12" \
-H "Authorization: Bearer YOUR_API_TOKEN"
```
### Response
Workspace id from the path
`comments`, `likes`, `shares`, `impressions`, `quotes`, `saves`, `engagement_rate` - same shape as the all-workspaces `total` object.
```json theme={null}
{
"workspace_id": 12,
"total": {
"comments": 24,
"likes": 180,
"shares": 14,
"impressions": 6500,
"quotes": 3,
"saves": 28,
"engagement_rate": 4.1
}
}
```
With no published analytics in that workspace, **`total`** uses `0` for every count and `0` for `engagement_rate`.
### Permissions
Requires the **`posts`** ability. You must have access to the workspace.
### Errors
`401` · `403` · `404` if the workspace is missing or not accessible
See also: [Analytics overview](/api-reference/analytics/overview).
# Create comment
Source: https://docs.postsyncer.com/api-reference/comments/create
POST /comments
Create a comment or reply on a post
## Create comment
Send **`content`** and/or **`media`** (at least one resolved media item or non-empty text required). The **`media`** array matches post threads: each entry is an **integer** library id (from `POST /api/v1/media/upload/file` or `POST /api/v1/media/upload/url`) and/or an **HTTPS URL** to import (direct image/video, supported social post URLs, Unsplash-same as the media library URL flow). If you send **only** `media` and **none** of the entries resolve, the request returns **422**. For a **reply**, set **`parent_comment_id`**; `post_id` must still refer to the same post as the parent (validated server-side).
### Body
Post id.
Parent comment id for a threaded reply.
Text (max 65535 chars) if not using media only.
Up to 10 items: **integers** (media ids in the post’s workspace) and/or **strings** (URLs to import). Mix ids and URLs in one array if needed.
Optional initial resolved flag.
Optional initial approval flag.
```bash theme={null}
curl -X POST "https://postsyncer.com/api/v1/comments" \
-H "Authorization: Bearer YOUR_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{"post_id":123,"content":"Thanks for the feedback!"}'
```
### Response
`201` with `data` (comment) and `message`.
### Errors
`401` · `403` · `422` validation
See [Comments overview](/api-reference/comments/overview).
# Delete comment
Source: https://docs.postsyncer.com/api-reference/comments/delete
DELETE /comments/{comment}
Delete a comment and sync removal to the network when applicable
## Delete comment
Rules match the product: internal/public-share comments you own can be removed; social threads use the same platform delete flow as the UI.
### Path
Numeric id of the comment to delete.
```bash theme={null}
curl -X DELETE "https://postsyncer.com/api/v1/comments/456" \
-H "Authorization: Bearer YOUR_API_TOKEN"
```
### Response
`200` with `{ "message": "Comment deleted successfully" }`.
### Errors
`401` · `403` · `404`
See [Comments overview](/api-reference/comments/overview).
# Get comment
Source: https://docs.postsyncer.com/api-reference/comments/get
GET /comments/{comment}
Retrieve a single comment with nested replies
## Get comment
### Request
Numeric id of the comment (`comments.id`).
```bash theme={null}
curl -X GET "https://postsyncer.com/api/v1/comments/456" \
-H "Authorization: Bearer YOUR_API_TOKEN"
```
### Response
Comment resource: `id`, `post_id`, `parent_comment_id`, `source`, `platform`, `content`, `is_resolved`, `is_approved`, `replies`, `media`, `user`, etc.
### Errors
`401` · `404` (comment not in an accessible workspace)
See [Comments overview](/api-reference/comments/overview).
# Hide comment on platform
Source: https://docs.postsyncer.com/api-reference/comments/hide
PATCH /comments/{comment}/hide
Hide a social reply via the platform API
## Hide on platform
For **social** comments, or comments you authored. Requires a linked **`platform_post`**.
### Path
Numeric id of the comment to hide on the network.
```bash theme={null}
curl -X PATCH "https://postsyncer.com/api/v1/comments/456/hide" \
-H "Authorization: Bearer YOUR_API_TOKEN"
```
### Response
`200` with success message.
### Errors
`401` · `403` · `404` · `422` if the comment cannot be hidden.
See [Comments overview](/api-reference/comments/overview).
# List comments
Source: https://docs.postsyncer.com/api-reference/comments/list
GET /comments
Paginated comments for a post, with optional nested replies and filters
## List comments
### Request
Post id (must belong to a workspace you can access).
Page size (max 100).
Page number.
Comment source: e.g. `SOCIAL`, `INTERNAL` (includes internal, approval, public-share).
Filter by platform key (e.g. `twitter`).
`true` or `false`.
`true` or `false`.
`true` to nest replies (top-level comments only in the root list).
```bash theme={null}
curl -G "https://postsyncer.com/api/v1/comments" \
--data-urlencode "post_id=123" \
--data-urlencode "per_page=20" \
--data-urlencode "include_replies=true" \
-H "Authorization: Bearer YOUR_API_TOKEN"
```
### Response
`data`: array of comment objects (same shape as [get comment](/api-reference/comments/get)).\
`pagination`: `current_page`, `last_page`, `per_page`, `total`, `has_more_pages`.
### Errors
`401` · `403` · `404` (invalid `post_id` or no access)
See [Comments overview](/api-reference/comments/overview).
# Comments overview
Source: https://docs.postsyncer.com/api-reference/comments/overview
Manage post comments and replies via API v1
## Comments API
Use these endpoints to work with the comment inbox: list threads, **reply** (create with `parent_comment_id`), edit your own comments (`content` / `media` - ids and/or URLs like post `content[].media`), delete, and hide replies on the network when supported.
All routes require the **`posts`** ability and a post in a workspace you can access.
| Method | Path | Purpose |
| ------ | ----------------------------------------------------- | --------------------------------------------------------------------- |
| GET | [`/comments`](/api-reference/comments/list) | List comments for a post (paginated, optional nested replies) |
| POST | [`/comments/sync`](/api-reference/comments/sync) | Pull comments from networks for **published** platform rows on a post |
| POST | [`/comments`](/api-reference/comments/create) | Create comment or **reply** |
| GET | [`/comments/{id}`](/api-reference/comments/get) | Get one comment with replies |
| PUT | [`/comments/{id}`](/api-reference/comments/update) | Update text and/or media URLs (author only) |
| DELETE | [`/comments/{id}`](/api-reference/comments/delete) | Delete (rules match dashboard) |
| PATCH | [`/comments/{id}/hide`](/api-reference/comments/hide) | Hide on platform (social replies) |
### Sources & filters
* Query `source` defaults to **`SOCIAL`**. Use **`INTERNAL`** to include internal, approval, and public-share thread comments (same grouping as the UI).
* **`SOCIAL`** + first page: optionally triggers a **sync** from connected networks (same as opening the thread in the app). You can also call **`POST /comments/sync`** with `post_id` to sync on demand.
* Filter with `provider` (platform key), `is_resolved`, `is_approved`, `include_replies`.
### Replies
Set **`parent_comment_id`** on **POST** `/comments`. The API copies `source`, `platform`, `post_id`, and `platform_post_id` from the parent (with the same public-share → internal adjustment as the dashboard).
See also: [Posts - get](/api-reference/posts/get) with `include_comments=true` for comments embedded on a post payload.
# Sync comments from platforms
Source: https://docs.postsyncer.com/api-reference/comments/sync
POST /comments/sync
Pull social comments from networks for a post’s published platform rows
## Sync comments from platforms
Triggers the same **network fetch** as when you open **SOCIAL** comments in the app (first page of the list): for each **published** `platform_posts` row on the post, the server calls the provider’s comment sync when supported (e.g. Twitter/X, LinkedIn, Bluesky, YouTube, Mastodon).
Rows that are not **published** or lack a `platform_post_id` are skipped (`synced: false` in the response).
### Body
Post id (must be in a workspace you can access).
```bash theme={null}
curl -X POST "https://postsyncer.com/api/v1/comments/sync" \
-H "Authorization: Bearer YOUR_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{"post_id":123}'
```
### Response
`200` - `message` and `data`:
* `post_id` - same as request
* `platform_posts` - array of `{ platform_post_id, provider, synced }` where **`synced`** is `true` only if that row was **published** and a sync was attempted.
### Errors
`401` · `403` · `404` · `422`
See [Comments overview](/api-reference/comments/overview).
# Update comment
Source: https://docs.postsyncer.com/api-reference/comments/update
PUT /comments/{comment}
Update your own comment text and/or media
## Update comment
Only the **author** (`user_id` matches token user) may update.
**Allowed JSON fields:** `content` and/or `media` (same rules as [create comment](/api-reference/comments/create)). Omit a field to leave it unchanged. Send **`media`: `[]`** to detach all media from the comment.
### Path
Numeric id of the comment to update.
### Body
Comment text (max 65535 characters). Empty string clears text to null.
Up to 10 items (integer ids and/or URLs) to **replace** attached media. When this key is present, attachments are re-synced from the new list (or cleared if the array is empty).
```bash theme={null}
curl -X PUT "https://postsyncer.com/api/v1/comments/456" \
-H "Authorization: Bearer YOUR_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{"content":"Updated text"}'
```
### Errors
`401` · `403` (not author) · `404` · `422` (e.g. all `media` entries failed to resolve)
See [Comments overview](/api-reference/comments/overview).
# Create folder
Source: https://docs.postsyncer.com/api-reference/folders/create
POST folders
Create a media library folder (optional parent, color)
## Create folder
Creates a folder in the given workspace. Use the returned **`id`** as **`folder_id`** on [`POST /media/upload/file`](/api-reference/media/upload-file) or [`POST /media/upload/url`](/api-reference/media/upload-url).
### Body
Workspace that will own the folder
Display name
Hex color (e.g. `#4F46E5`). Defaults to `#4F46E5` if omitted.
Optional parent folder id (must be in the same workspace)
Requires the **`posts`** ability.
### See also
# Delete folder
Source: https://docs.postsyncer.com/api-reference/folders/delete
DELETE folders/{folder_id}
Delete a media library folder
## Delete folder
Deletes the folder. Depending on database rules, **nested folders and media** tied to this folder may be removed as well (cascade). Ensure nothing important remains in the folder.
### Request
The id of the folder to delete (same as the **`id`** field on the folder)
Requires the **`posts`** ability.
```bash theme={null}
curl -X DELETE "https://postsyncer.com/api/v1/folders/42" \
-H "Authorization: Bearer YOUR_API_TOKEN"
```
### Response
Success message
# Get folder
Source: https://docs.postsyncer.com/api-reference/folders/get
GET folders/{folder_id}
Retrieve one media folder by id
## Get folder
Returns a single folder if it belongs to a workspace you can access. Includes **`parent`** when loaded.
### Request
The id of the folder to retrieve (same as the **`id`** field returned by [list folders](/api-reference/folders/list) or [create folder](/api-reference/folders/create))
Requires the **`posts`** ability.
```bash theme={null}
curl -X GET "https://postsyncer.com/api/v1/folders/42" \
-H "Authorization: Bearer YOUR_API_TOKEN"
```
# List folders
Source: https://docs.postsyncer.com/api-reference/folders/list
GET folders
List media library folders for your workspaces (optional filters)
## List folders
Returns folders from every workspace you can access, unless you narrow with query parameters.
### Query parameters
* **`workspace_id`** - only folders in that workspace (must be one of your active workspaces).
* **`root=true`** - only top-level folders (`parent_id` is null).
* **`parent_id`** - only direct children of that folder (folder must belong to a workspace you can access).
Requires the **`posts`** token ability (same as media upload).
### Request
```bash theme={null}
curl -G "https://postsyncer.com/api/v1/folders" \
-H "Authorization: Bearer YOUR_API_TOKEN" \
--data-urlencode "workspace_id=12" \
--data-urlencode "root=true"
```
### See also
# Update folder
Source: https://docs.postsyncer.com/api-reference/folders/update
PUT folders/{folder_id}
Update folder name, color, and/or parent
## Update folder
Send at least one of **`name`**, **`color`**, or **`parent_id`**. You cannot set **`parent_id`** to this folder or to any of its subfolders.
### Request
The id of the folder to update (same as the **`id`** field on the folder)
New name
Hex color
New parent folder id in the same workspace, or `null` to move to root
Requires the **`posts`** ability.
```bash theme={null}
curl -X PUT "https://postsyncer.com/api/v1/folders/42" \
-H "Authorization: Bearer YOUR_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{"name":"Campaign assets","color":"#2563EB"}'
```
# API reference overview
Source: https://docs.postsyncer.com/api-reference/introduction
What the PostSyncer REST API includes-platforms, versioning, and links to endpoint docs
This page is the **hub** for the REST API: what it does, where it lives, and how responses work. **Step-by-step auth** lives in [Authentication](/essentials/authentication). **Errors and status codes** are in [Error handling](/essentials/error-handling). **Request quotas** are in [Fair usage & limits](/rate-limits).
## What you can do
PostSyncer offers a RESTful API to manage scheduling workflows: **upload or import media** (multipart file upload and URL import), then create, update, and delete **posts**; **analyze public X/Twitter posts and replies with AI**; list, reply to, update, and delete **comments**; list **workspaces**, **accounts**, and **labels**; read **analytics** (all workspaces, one workspace, one post, or one account); and queue **analytics sync** for a post. Endpoint pages live in the sidebar under **Media**, **Folders**, **Posts**, **Comments**, **Analytics**, **Workspaces**, **Accounts**, and **Labels**.
## Supported platforms
PostSyncer supports scheduling and publishing to:
## Version & base URL
| Version | Status | Base URL |
| ------- | ------- | ------------------------------- |
| v1 | Current | `https://postsyncer.com/api/v1` |
All endpoint paths in this docs site are relative to that base URL **except** [upload from file](/api-reference/media/upload-file), which uses **`https://upload.postsyncer.com/api/v1`** for the same versioned paths (dedicated upload infrastructure; same Bearer token).
## Authentication (summary)
Every request needs a **Bearer** token from [Settings → API integrations](https://app.postsyncer.com/dashboard?action=settings\§ion=api-integrations). Scopes must match what you call (e.g. `posts` for posts and analytics).
```bash theme={null}
Authorization: Bearer YOUR_API_TOKEN
```
Keys, scopes, and security practices
## JSON responses
Responses are **JSON** with standard HTTP status codes. Success is typically `2xx`; validation and permission issues are `4xx`. See [Error handling](/essentials/error-handling) for patterns, status codes, and rate-limit (`429`) behavior.
## Next steps
Make your first request
File upload, URL import, and using ids on posts
List, create, update, delete, and hide on platform
All analytics endpoints in one place
# Create Label
Source: https://docs.postsyncer.com/api-reference/labels/create
POST labels
Create a new label to categorize your social media posts
## Create Label
Creates a new label to help categorize and organize your social media posts. Labels can be used to group related content together for better organization and filtering.
### Request
The ID of the workspace where the label will be created
The name of the label
Hex color code for the label (e.g., "#ff0000" for red)
```json theme={null}
{
"workspace_id": 1,
"name": "Product Launch",
"color": "#ff0000"
}
```
### Response
Unique identifier for the created label
The name of the label
The hex color code of the label
Information about the label's workspace
Creation timestamp in workspace timezone
Last update timestamp in workspace timezone
```json theme={null}
{
"id": 16,
"name": "Product Launch",
"color": "#ff0000",
"workspace": {
"id": 12,
"name": "abdulmejidshemsuawel",
"slug": "abdulmejidshemsuawel",
"type": "PERSONAL",
"logo": null,
"timezone": "Africa/Addis_Ababa",
"language": "en"
}
}
```
### Code Examples
```bash cURL theme={null}
curl -X POST "https://postsyncer.com/api/v1/labels" \
-H "Authorization: Bearer YOUR_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"workspace_id": 1,
"name": "Product Launch",
"color": "#ff0000"
}'
```
```javascript Node.js theme={null}
const axios = require('axios');
const createLabel = async () => {
try {
const response = await axios.post('https://postsyncer.com/api/v1/labels', {
workspace_id: 1,
name: 'Product Launch',
color: '#ff0000'
}, {
headers: {
'Authorization': 'Bearer YOUR_API_TOKEN',
'Content-Type': 'application/json'
}
});
console.log('Label created:', response.data);
} catch (error) {
console.error('Error creating label:', error.response?.data || error.message);
}
};
createLabel();
```
```php PHP theme={null}
1,
'name' => 'Product Launch',
'color' => '#ff0000'
];
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, 'https://postsyncer.com/api/v1/labels');
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data));
curl_setopt($ch, CURLOPT_HTTPHEADER, [
'Authorization: Bearer YOUR_API_TOKEN',
'Content-Type: application/json'
]);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($httpCode === 201) {
$label = json_decode($response, true);
echo "Label created: " . $label['id'];
} else {
echo "Error: " . $response;
}
?>
```
```python Python theme={null}
import requests
import json
data = {
'workspace_id': 1,
'name': 'Product Launch',
'color': '#ff0000'
}
headers = {
'Authorization': 'Bearer YOUR_API_TOKEN',
'Content-Type': 'application/json'
}
response = requests.post(
'https://postsyncer.com/api/v1/labels',
json=data,
headers=headers
)
if response.status_code == 201:
label = response.json()
print(f"Label created: {label['id']}")
else:
print(f"Error: {response.status_code}")
print(response.text)
```
### Error Codes
Validation errors or invalid parameters
Missing or invalid API token
Token does not have 'labels' permission
Workspace not found or user does not have access to the workspace.
Validation errors in request data
# Delete Label
Source: https://docs.postsyncer.com/api-reference/labels/delete
DELETE labels/{id}
Delete a label from your workspace
## Delete Label
Deletes a label from your workspace. This action cannot be undone.
### Request
The ID of the label to delete
```bash theme={null}
curl -X DELETE "https://postsyncer.com/api/v1/labels/123" \
-H "Authorization: Bearer YOUR_API_TOKEN"
```
### Response
Success message confirming the label was deleted
```json theme={null}
{
"message": "Label deleted successfully"
}
```
### Code Examples
```bash cURL theme={null}
curl -X DELETE "https://postsyncer.com/api/v1/labels/123" \
-H "Authorization: Bearer YOUR_API_TOKEN"
```
```javascript Node.js theme={null}
const axios = require('axios');
const deleteLabel = async (labelId) => {
try {
const response = await axios.delete(`https://postsyncer.com/api/v1/labels/${labelId}`, {
headers: {
'Authorization': 'Bearer YOUR_API_TOKEN'
}
});
console.log('Label deleted:', response.data.message);
} catch (error) {
console.error('Error deleting label:', error.response?.data || error.message);
}
};
deleteLabel(123);
```
```php PHP theme={null}
```
```python Python theme={null}
import requests
label_id = 123
headers = {
'Authorization': 'Bearer YOUR_API_TOKEN'
}
response = requests.delete(
f'https://postsyncer.com/api/v1/labels/{label_id}',
headers=headers
)
if response.status_code == 200:
result = response.json()
print(result['message'])
else:
print(f"Error: {response.status_code}")
print(response.text)
```
### Error Codes
Missing or invalid API token
Token does not have 'labels' permission
Label not found or user does not have access to the label's workspace
Invalid label ID provided
# Get Label
Source: https://docs.postsyncer.com/api-reference/labels/get
GET labels/{id}
Retrieve a specific label by ID
## Get Label
Retrieves detailed information about a specific label, including its name, color, and associated workspace.
### Request
The ID of the label to retrieve
```bash theme={null}
curl -X GET "https://postsyncer.com/api/v1/labels/123" \
-H "Authorization: Bearer YOUR_API_TOKEN"
```
### Response
Unique identifier for the label
The name of the label
The hex color code of the label
Information about the label's workspace
Creation timestamp in workspace timezone
Last update timestamp in workspace timezone
```json theme={null}
{
"id": 16,
"name": "Product Launch",
"color": "#ff0000",
"workspace": {
"id": 12,
"name": "abdulmejidshemsuawel",
"slug": "abdulmejidshemsuawel",
"type": "PERSONAL",
"logo": null,
"timezone": "Africa/Addis_Ababa",
"language": "en"
}
}
```
### Code Examples
```bash cURL theme={null}
curl -X GET "https://postsyncer.com/api/v1/labels/123" \
-H "Authorization: Bearer YOUR_API_TOKEN"
```
```javascript Node.js theme={null}
const axios = require('axios');
const getLabel = async (labelId) => {
try {
const response = await axios.get(`https://postsyncer.com/api/v1/labels/${labelId}`, {
headers: {
'Authorization': 'Bearer YOUR_API_TOKEN'
}
});
console.log('Label:', response.data);
return response.data;
} catch (error) {
console.error('Error fetching label:', error.response?.data || error.message);
}
};
getLabel(123);
```
```php PHP theme={null}
```
```python Python theme={null}
import requests
label_id = 123
headers = {
'Authorization': 'Bearer YOUR_API_TOKEN'
}
response = requests.get(
f'https://postsyncer.com/api/v1/labels/{label_id}',
headers=headers
)
if response.status_code == 200:
label = response.json()
print(f"Label {label['id']}: {label['name']} ({label['color']})")
else:
print(f"Error: {response.status_code}")
print(response.text)
```
### Error Codes
Missing or invalid API token
Token does not have 'labels' permission
Label not found or user does not have access to the label's workspace
# List Labels
Source: https://docs.postsyncer.com/api-reference/labels/list
GET labels
Retrieve all labels from your workspaces
## List Labels
Retrieves all labels from workspaces that the authenticated user has access to.
### Request
```bash theme={null}
curl -X GET "https://postsyncer.com/api/v1/labels" \
-H "Authorization: Bearer YOUR_API_TOKEN"
```
### Response
Array of label objects
```json theme={null}
{
"data": [
{
"id": 1,
"name": "Product Launch",
"color": "#ff0000",
"workspace": {
"id": 12,
"name": "abdulmejidshemsuawel",
"slug": "abdulmejidshemsuawel",
"type": "PERSONAL",
"logo": null,
"timezone": "Africa/Addis_Ababa",
"language": "en"
}
},
{
"id": 2,
"name": "Winter",
"color": "#0000ff",
"workspace": {
"id": 12,
"name": "abdulmejidshemsuawel",
"slug": "abdulmejidshemsuawel",
"type": "PERSONAL",
"logo": null,
"timezone": "Africa/Addis_Ababa",
"language": "en"
}
}
]
}
```
### Code Examples
```bash cURL theme={null}
curl -X GET "https://postsyncer.com/api/v1/labels" \
-H "Authorization: Bearer YOUR_API_TOKEN"
```
```javascript Node.js theme={null}
const axios = require('axios');
const getLabels = async () => {
try {
const response = await axios.get('https://postsyncer.com/api/v1/labels', {
headers: {
'Authorization': 'Bearer YOUR_API_TOKEN'
}
});
console.log('Labels:', response.data);
return response.data;
} catch (error) {
console.error('Error fetching labels:', error.response?.data || error.message);
}
};
getLabels();
```
```php PHP theme={null}
```
```python Python theme={null}
import requests
headers = {
'Authorization': 'Bearer YOUR_API_TOKEN'
}
response = requests.get(
'https://postsyncer.com/api/v1/labels',
headers=headers
)
if response.status_code == 200:
labels = response.json()
print(f"Found {len(labels['data'])} labels")
for label in labels['data']:
print(f"Label {label['id']}: {label['name']} ({label['color']})")
else:
print(f"Error: {response.status_code}")
print(response.text)
```
### Error Codes
Missing or invalid API token
Token does not have 'labels' permission
# Update Label
Source: https://docs.postsyncer.com/api-reference/labels/update
PUT labels/{id}
Update an existing label with new information
## Update Label
Updates an existing label with new name or color information.
### Request
The ID of the label to update
The name of the label
Hex color code for the label (e.g., "#ff0000" for red)
```json theme={null}
{
"name": "Updated Product Launch",
"color": "#00ff00"
}
```
### Response
Unique identifier for the updated label
The updated name of the label
The updated hex color code of the label
Information about the label's workspace
Creation timestamp in workspace timezone
Last update timestamp in workspace timezone
```json theme={null}
{
"id": 16,
"name": "Updated Product Launch",
"color": "#00ff00"
"workspace": {
"id": 12,
"name": "abdulmejidshemsuawel",
"slug": "abdulmejidshemsuawel",
"type": "PERSONAL",
"logo": null,
"timezone": "Africa/Addis_Ababa",
"language": "en"
}
}
```
### Code Examples
```bash cURL theme={null}
curl -X PUT "https://postsyncer.com/api/v1/labels/123" \
-H "Authorization: Bearer YOUR_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "Updated Product Launch",
"color": "#00ff00"
}'
```
```javascript Node.js theme={null}
const axios = require('axios');
const updateLabel = async (labelId) => {
try {
const response = await axios.put(`https://postsyncer.com/api/v1/labels/${labelId}`, {
name: 'Updated Product Launch',
color: '#00ff00'
}, {
headers: {
'Authorization': 'Bearer YOUR_API_TOKEN',
'Content-Type': 'application/json'
}
});
console.log('Label updated:', response.data);
} catch (error) {
console.error('Error updating label:', error.response?.data || error.message);
}
};
updateLabel(123);
```
```php PHP theme={null}
'Updated Product Launch',
'color' => '#00ff00'
];
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, "https://postsyncer.com/api/v1/labels/{$labelId}");
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'PUT');
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data));
curl_setopt($ch, CURLOPT_HTTPHEADER, [
'Authorization: Bearer YOUR_API_TOKEN',
'Content-Type: application/json'
]);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($httpCode === 200) {
$label = json_decode($response, true);
echo "Label updated: " . $label['id'];
} else {
echo "Error: " . $response;
}
?>
```
```python Python theme={null}
import requests
import json
label_id = 123
data = {
'name': 'Updated Product Launch',
'color': '#00ff00'
}
headers = {
'Authorization': 'Bearer YOUR_API_TOKEN',
'Content-Type': 'application/json'
}
response = requests.put(
f'https://postsyncer.com/api/v1/labels/{label_id}',
json=data,
headers=headers
)
if response.status_code == 200:
label = response.json()
print(f"Label updated: {label['id']}")
else:
print(f"Error: {response.status_code}")
print(response.text)
```
### Error Codes
Validation errors or invalid parameters
Missing or invalid API token
Token does not have 'labels' permission
Label not found or user does not have access to the label's workspace
Validation errors in request data
# Delete media
Source: https://docs.postsyncer.com/api-reference/media/delete
DELETE media/{media_id}
Remove a media library item and its stored file
## Delete media
Deletes the media record and removes the file from storage. Associations with posts and comments are removed via database cascades.
### Request
The media row **`id`** to delete.
```bash theme={null}
curl -X DELETE "https://postsyncer.com/api/v1/media/99" \
-H "Authorization: Bearer YOUR_API_TOKEN"
```
### Response
`200` - `{ "message": "Media deleted successfully" }`.
### Errors
`401` · `403` · `404`
See [Media overview](/api-reference/media/overview).
# Get media
Source: https://docs.postsyncer.com/api-reference/media/get
GET media/{media_id}
Retrieve one media library item by id
## Get media
Returns a single library item if it belongs to a workspace you can access.
### Request
The media row **`id`** (same as returned from list or upload endpoints).
```bash theme={null}
curl -X GET "https://postsyncer.com/api/v1/media/99" \
-H "Authorization: Bearer YOUR_API_TOKEN"
```
### Response
`200` - one `APIMedia` object (`id`, `workspace_id`, `folder_id`, `url`, …).
### Errors
`401` · `403` · `404`
See [Media overview](/api-reference/media/overview).
# List media
Source: https://docs.postsyncer.com/api-reference/media/list
GET media
Paginated media library items across workspaces you can access
## List media
Returns a **paginated** list of media (`data`, `links`, `meta` - same shape as [List posts](/api-reference/posts/list)). Items include `workspace_id`, `folder_id`, `id`, `url`, etc.
### Request
Only media in this workspace (must be one you have access to).
Only media in this folder (folder must belong to an accessible workspace). Do not use with `root_only`.
When true, only items **not** in any folder (`folder_id` null). Do not use with `folder_id`.
Page number (default 1).
Page size (default 50, max 100).
```bash theme={null}
curl -X GET "https://postsyncer.com/api/v1/media?workspace_id=12&per_page=25" \
-H "Authorization: Bearer YOUR_API_TOKEN"
```
### Response
`200` - paginated `APIMedia` objects in `data`.
### Errors
`401` · `403` · `422` (invalid filters, e.g. wrong workspace or `folder_id` + `root_only` together)
See [Media overview](/api-reference/media/overview).
# Media overview
Source: https://docs.postsyncer.com/api-reference/media/overview
List, get, delete, upload files, or import URLs into your workspace library, then attach media by id or URL when creating posts
## Media API
Media endpoints manage the **workspace media library**. Each saved item has an **`id`** you can reference when you [create](/api-reference/posts/create) or [update](/api-reference/posts/update) a post, or in [comment `media`](/api-reference/comments/create).
All routes require the **`posts`** ability (same token scope as post CRUD).
| Method | Path | Purpose |
| ------ | -------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| GET | [`/media`](/api-reference/media/list) | **Paginated list** (optional `workspace_id`, `folder_id`, or `root_only`) |
| GET | [`/media/{media_id}`](/api-reference/media/get) | **One item** by id |
| DELETE | [`/media/{media_id}`](/api-reference/media/delete) | **Delete** library item and stored file |
| POST | [`/media/upload/file`](/api-reference/media/upload-file) | **One multipart request** with `file` + `workspace_id` (optional `folder_id`); max **500MB** per file (same as in-app standard upload). **Host:** `https://upload.postsyncer.com/api/v1` (not `postsyncer.com`) - see that page for details and playground behavior. |
| POST | [`/media/upload/url`](/api-reference/media/upload-url) | **JSON** body: import one or more **URLs** (direct image/video links, supported social links, Unsplash, etc.) |
### Using media on posts
In `content[].media`, each element may be either:
* **Integer** - library `id` from list, get, `upload/file`, or `upload/url` (must belong to the same `workspace_id` as the post).
* **String (URL)** - public HTTPS link to an image or video; the API validates and processes it when the post is saved.
You can **mix** ids and URLs in the same `media` array.
Typical flows:
1. **File on disk** → `POST /media/upload/file` with the file → read `media.id` → include that id in `POST /posts` … `content[].media`.
2. **Already on the web** → either paste the URL directly in `content[].media`, or call `POST /media/upload/url` first to store a copy in the library and then use the returned `id`.
3. **Browse library** → `GET /media?workspace_id=…` to discover ids.
### Limits and infrastructure
* **File upload** is limited to **500MB** per request.
* **URL import** may return **multiple** `media` objects for a single input URL (e.g. some social posts). Check `count_stored` and each item’s `id`.
### Folders
Create and list folders with [**Folders API**](/api-reference/folders/list), then pass **`folder_id`** on upload endpoints to file media into the right library folder. Use **`folder_id`** or **`root_only`** on [List media](/api-reference/media/list) to scope results.
### See also
# Upload media from file
Source: https://docs.postsyncer.com/api-reference/media/upload-file
POST https://upload.postsyncer.com/api/v1/media/upload/file
Single multipart request: send the file plus workspace_id (optional folder_id). MIME type is detected from the file.
## Upload media from file
**Do not call this on the standard API host.** Use **`https://upload.postsyncer.com/api/v1/media/upload/file`**, not `https://postsyncer.com/api/v1/...`. Same Bearer token as every other endpoint; only the **hostname** changes so large multipart uploads hit dedicated infrastructure ([overview](/api-reference/introduction)).
Upload **one** image or video with `multipart/form-data`. Fields:
* **`workspace_id`** (required) - workspace that will own the file
* **`file`** (required) - the binary upload
* **`folder_id`** (optional) - media library folder in that workspace
The API reads the **MIME type from the file** (you do not send `mimeType`). Allowed types are the same as in the app (JPEG, PNG, GIF, WebP, MP4, MOV, etc.). Maximum size per request is **500MB** (same as the in-app standard upload).
Response **`201`**: `{ "media": { "id", "url", "mime_type", ... } }` - use **`media.id`** in [Create post](/api-reference/posts/create) `content[].media`.
Do **not** use this endpoint for URL import - use [Import from URL](/api-reference/media/upload-url).
### Request
Workspace that will own the file
Image or video file
Optional media library folder id belonging to `workspace_id`
### Example
```bash theme={null}
curl -X POST "https://upload.postsyncer.com/api/v1/media/upload/file" \
-H "Authorization: Bearer YOUR_API_TOKEN" \
-F "workspace_id=12" \
-F "file=@/path/to/photo.jpg"
```
```json theme={null}
{
"media": {
"id": 1842,
"name": "photo.jpg",
"url": "https://…",
"mime_type": "image/jpeg",
"size": 240512,
"is_processing": false,
"type": "image",
"ai_generated_id": null,
"is_reference": false,
"progress": 100,
"alt": null
}
}
```
Use on a post:
```json theme={null}
"content": [{ "text": "Hello", "media": [1842] }]
```
### See also
* [Media overview](/api-reference/media/overview)
* [Import from URL](/api-reference/media/upload-url)
# Import media from URL
Source: https://docs.postsyncer.com/api-reference/media/upload-url
POST media/upload/url
Store remote images/videos into the workspace library from JSON - direct URLs, social links, Unsplash, etc.
## Import media from URL
Send **`application/json`** with a list of URLs. The API downloads or resolves each URL (same pipeline as the in-app URL importer: direct image/video links, supported social post URLs, Unsplash, etc.) and creates **media library** rows in the given workspace.
Response is **`200`** with `media` (array of created items) and `count_stored`. Some inputs produce **more than one** `media` object. URLs that fail validation or fetch may be skipped - `media` can be empty while still returning `200`.
For **binary uploads** from disk, use [Upload from file](/api-reference/media/upload-file) instead.
### Request
Workspace that will own the imported files
List of URLs to import (each must be a valid URL, max 2048 characters)
Optional media library folder id belonging to `workspace_id`
### Response
Created library items; each includes **`id`** for use in `POST /posts` … `content[].media`
Number of items in `media`
```bash theme={null}
curl -X POST "https://postsyncer.com/api/v1/media/upload/url" \
-H "Authorization: Bearer YOUR_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"workspace_id": 12,
"urls": [
"https://example.com/assets/hero.png"
]
}'
```
```json theme={null}
{
"media": [
{
"id": 915,
"name": "hero.png",
"url": "https://…",
"mime_type": "image/png",
"size": 98234,
"is_processing": false,
"type": "image",
"ai_generated_id": null,
"is_reference": false,
"progress": 100,
"alt": null
}
],
"count_stored": 1
}
```
### See also
* [Media overview](/api-reference/media/overview)
* [Create post](/api-reference/posts/create)
# Analyze X/Twitter Post
Source: https://docs.postsyncer.com/api-reference/posts/analyze-twitter
POST posts/analyze-twitter
Fetch a public X/Twitter post and replies, then answer a question with AI
## Analyze X/Twitter Post
This works on **any** public tweet URL - the post does not need to exist in your PostSyncer workspace.
### Body
Full public X/Twitter status URL (e.g. `https://x.com/username/status/1234567890` or `https://twitter.com/username/status/1234567890`).
Your question about the post and its replies (3–2000 characters). Examples: overall sentiment, common objections, feature requests, or whether people agree with the author.
```bash theme={null}
curl -X POST "https://postsyncer.com/api/v1/posts/analyze-twitter" \
-H "Authorization: Bearer YOUR_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"url": "https://x.com/postsyncer/status/1234567890",
"question": "What is the overall sentiment in the replies?"
}'
```
### Response
`200` - JSON object:
AI-generated answer based on the post and fetched replies.
Normalized post metadata: `id`, `url`, `text`, `author`, `created_at`, and `metrics` (`likes`, `replies`, `reposts`, `views`, `quotes`, `bookmarks`).
```json theme={null}
{
"answer": "Most replies are positive, with users praising the launch timeline...",
"tweet": {
"id": "1234567890",
"url": "https://x.com/postsyncer/status/1234567890",
"text": "We just shipped a new feature...",
"author": "@postsyncer",
"created_at": "2026-05-20T12:00:00.000Z",
"metrics": {
"likes": 120,
"replies": 45,
"reposts": 18,
"views": 12000,
"quotes": 3,
"bookmarks": 9
}
}
}
```
### Error Codes
Missing or invalid API token
Token does not have the `posts` ability
Invalid URL, unsupported link, tweet could not be fetched, or AI could not generate an answer
### MCP
The same action is available as the MCP tool **`analyze-twitter-post`** - see [MCP tools reference](/mcp/tools-reference).
# Update post auto plug
Source: https://docs.postsyncer.com/api-reference/posts/auto-plug
PATCH posts/{post}/auto-plug
Enable or disable automatic replies when engagement thresholds (likes, reposts, replies, views) are met.
## Update post auto plug
Configures **auto plug** on an existing post: when enabled, replies automatically after configured engagement is reached. Disabled posts return `auto_plug: null` on [get](/api-reference/posts/get) / [list](/api-reference/posts/list).
Repeat scheduling is **not** handled here; use [`repeatable` fields on create/update](/api-reference/posts/create) or [update post](/api-reference/posts/update).
Requires bearer token with the **`posts`** ability. Returns the full post object (same shape as `GET /posts/{post}`).
### Request
Post ID
`false` clears auto plug. `true` requires the fields below.
Optional rule identifier; generated if omitted when enabling.
Required when `enabled` is `true`. Label for this rule.
Required when `enabled` is `true`.
Required when `enabled` is `true`. Text of the automatic reply.
Required when `enabled` is `true`. `{ "enabled": boolean, "count": integer (min 0) }` - trigger when likes reach `count` (if `enabled`).
Same shape as `likes`.
Same shape as `likes`.
Same shape as `likes`.
```bash theme={null}
curl -X PATCH "https://postsyncer.com/api/v1/posts/123/auto-plug" \
-H "Authorization: Bearer YOUR_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"enabled": true,
"name": "Thank-you",
"is_default": false,
"reply_text": "Thanks for engaging!",
"likes": { "enabled": true, "count": 10 },
"reposts": { "enabled": false, "count": 0 },
"replies": { "enabled": false, "count": 0 },
"views": { "enabled": false, "count": 0 }
}'
```
### Response
`200` - full post resource including `auto_plug` (object or `null`).
`404` - post not found or not in a workspace you can access.
`422` - validation error.
See also: [Comment moderation](/api-reference/posts/comment-moderation), [Contact collection](/api-reference/posts/contact-collection).
# Update post comment moderation
Source: https://docs.postsyncer.com/api-reference/posts/comment-moderation
PATCH posts/{post}/comment-moderation
Per-post comment moderation (filters, keywords, optional AI reply) aligned with the dashboard.
## Update post comment moderation
Sets **`comments_settings`** on a post: rules for handling incoming comments (profanity, negativity, custom topics, URLs, mentions, hashtags, keywords, AI reply). When `enabled` is `false`, settings are stored with moderation off.
Requires bearer token with the **`posts`** ability. Returns the full post object (same shape as `GET /posts/{post}`).
### Request
Post ID
Master switch for moderation on this post.
Required when `enabled` is `true`. All boolean flags below are required when moderation is enabled.
Required when `remove_custom_topics` is `true`.
Required when `remove_keywords` is `true`.
```bash theme={null}
curl -X PATCH "https://postsyncer.com/api/v1/posts/123/comment-moderation" \
-H "Authorization: Bearer YOUR_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"enabled": true,
"settings": {
"remove_profanity": true,
"remove_negativity": false,
"remove_all_comments": false,
"remove_custom_topics": false,
"custom_topics_list": [],
"remove_urls": false,
"remove_emails": false,
"remove_phones": false,
"remove_mentions": false,
"mentions_list": [],
"remove_hashtags": false,
"hashtags_list": [],
"remove_keywords": false,
"keywords_list": [],
"ai_reply": false
}
}'
```
### Response
`200` - full post resource including `comments_settings`.
`404` - post not found or not accessible.
`422` - validation error.
See also: [Comments API overview](/api-reference/comments/overview), [Auto plug](/api-reference/posts/auto-plug), [Contact collection](/api-reference/posts/contact-collection).
# Update post contact collection
Source: https://docs.postsyncer.com/api-reference/posts/contact-collection
PATCH posts/{post}/contact-collection
Add commenters to CRM lists via keyword rules or add-all mode.
## Update post contact collection
Configures **`contact_collection_settings`** so replies can be added to workspace CRM lists: either **keyword rules** (each keyword maps to a list) or **add all** (single list). List IDs must belong to the post’s workspace.
Requires bearer token with the **`posts`** ability. Returns the full post object (same shape as `GET /posts/{post}`).
### Request
Post ID
`false` stores collection as disabled.
Required when `enabled` is `true`.
`keyword_rules` or `add_all`.
Required when `mode` is `add_all`. CRM list id in the same workspace as the post.
When using keyword mode: each item `{ "keyword": string, "list_id": integer }`; each `list_id` must exist in the workspace.
Optional; defaults to false.
```bash theme={null}
curl -X PATCH "https://postsyncer.com/api/v1/posts/123/contact-collection" \
-H "Authorization: Bearer YOUR_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"enabled": true,
"settings": {
"mode": "keyword_rules",
"keyword_rules": [
{ "keyword": "demo", "list_id": 10 }
],
"keyword_case_sensitive": false
}
}'
```
For **add all** mode, use `"mode": "add_all"` and `"list_id": ` in `settings` (no `keyword_rules`).
### Response
`200` - full post resource including `contact_collection_settings`.
`404` - post not found or not accessible.
`422` - validation error.
See also: [Auto plug](/api-reference/posts/auto-plug), [Comment moderation](/api-reference/posts/comment-moderation).
# Create Post
Source: https://docs.postsyncer.com/api-reference/posts/create
POST posts
Create a new social media post with content, media, and scheduling options
## Create Post
Creates a new post that can be scheduled or published immediately across multiple social media platforms. The post can include text content, media attachments, and platform-specific settings.
### Request
The ID of the workspace where the post will be created
Optional array of label IDs to apply to this post
Array of content objects (threads). Each object can contain text and media. At least one content item with text or media is required.
The text content for this post
Attachments for this thread. Each item is either a **string** (public HTTPS URL to an image or video) or a (media library `id` from [`POST /media/upload/file`](/api-reference/media/upload-file) or [`POST /media/upload/url`](/api-reference/media/upload-url), same `workspace_id`). You may mix URLs and ids in one array. See [Media overview](/api-reference/media/overview).
Optional cover/thumbnail for video posts. Only the **first** content item's `cover_image` is used when publishing. **Which field you use depends on the target platform** - see [Platform requirements](#platform-requirements-for-cover-images) below.
**Custom cover image upload** - workspace media library **id** (image only) or public **HTTPS URL** to an image. Upload via [`POST /media/upload/file`](/api-reference/media/upload-file) or [`POST /media/upload/url`](/api-reference/media/upload-url). **Required for YouTube, Instagram, and Facebook video/Reels.** Not used on TikTok.
**Frame selection from the attached video** - timestamp in **milliseconds** (e.g. `2500` = 2.5 seconds). **TikTok only.** Custom thumbnail upload is not supported on TikTok.
When `true`, this content item is **not** part of the main post body on platforms that support first comments. After the post publishes successfully, PostSyncer posts this text (and optional media) as the first comment on **Instagram, Facebook, LinkedIn, and YouTube**. Only one content item may set this. You must still include at least one non-first-comment content item for the main post. On other platforms in the same post (e.g. TikTok, Threads), this flag does not schedule a first comment — see [First comments](#first-comments).
Minutes to wait after publish before posting the first comment. Defaults to `1` when `is_first_comment` is `true`. Max `10080` (7 days).
Type of scheduling. Must be one of:
publish\_now, schedule, draft
Optional scheduling object used when `schedule_type` is `schedule`. Provide `{"date": "YYYY-MM-DD", "time": "HH:MM", "timezone": "..."}` to schedule for a specific date/time, or omit/leave empty to auto-schedule to the next available time slot
The date to schedule the post (YYYY-MM-DD format)
The time to schedule the post (HH:MM format in 24-hour)
Timezone for the scheduled time (defaults to workspace timezone)
Whether the post should be repeated
Number of times to repeat the post. Required if repeatable is true.
Time gap between repeats. Required if repeatable is true.
The unit for the repeatable gap. Must be one of: `minutes`, `hours`, `days`, `weeks`, `months`. Required if repeatable is true.
Optional. Only when `repeatable` is true: account IDs (in this `workspace_id`) to limit repeat scheduling to those accounts. Omit or empty for default behavior. To turn off repeat scheduling, send `repeatable: false` on [update post](/api-reference/posts/update).
**[Auto plug](/api-reference/posts/auto-plug)**, **[comment moderation](/api-reference/posts/comment-moderation)**, and **[contact collection](/api-reference/posts/contact-collection)** each have a dedicated `PATCH` endpoint. Repeat scheduling uses `repeatable` / `repeatable_accounts` on create and update.
Array of account objects to publish to
The ID of the social media account
Platform-specific settings for this post. Optional except for Pinterest which requires a board\_id. The available settings depend on the platform:
Whether to restrict the post to super followers only (default: false)
Who can reply to the post. Options: 'everyone', 'following', 'mentioned\_users' (default: 'everyone')
ID of the tweet to quote (optional)
Reply settings object with 'in\_reply\_to\_tweet\_id' (optional)
ID of the community to post to (optional)
Share community post with followers too.
Custom text content for Twitter (optional) - (This allows overriding the platform-specific text content)
Poll settings with 'enabled', 'duration\_minutes', 'reply\_settings', and 'options' (optional)
everyone, following, mentionedUsers
Poll options, array of text
Type of post. Options: 'REELS', 'STORIES', 'POST' (default: 'REELS') (optional)
Custom caption for Instagram (optional) - (This allows overriding the platform-specific text content)
Type of post. Options: 'REELS', 'STORIES', 'POST' (default: 'REELS') (optional)
Custom title for the Facebook post (optional) - (This allows overriding the platform-specific text content)
URL to attach to the post (optional)
Post visibility. Options: 'PUBLIC', 'CONNECTIONS', 'LOGGED\_IN' (default: 'PUBLIC')
URL to attach to the post (optional)
Custom title for the LinkedIn post (optional) - (This allows overriding the platform-specific text content)
Type of video. Options: 'video', 'short' (default: 'video')
Video title (optional)
Video description (optional) - (This allows overriding the platform-specific text content)
Privacy status. Options: 'public', 'private', 'unlisted' (default: 'public')
Array of tags for the video (optional)
Whether the video can be embedded (default: true)
Whether the video is made for kids (default: false)
Whether to notify subscribers (default: true)
YouTube category ID
Privacy level for the video (optional) PUBLIC\_TO\_EVERYONE, MUTUAL\_FOLLOW\_FRIENDS, FOLLOWER\_OF\_CREATOR, SELF\_ONLY
Video title (optional)- (This allows overriding the platform-specific text content)
Video description (optional) - (Only available when posting photos)
Whether to disable comments (optional)
Whether to disable duets (optional)
Whether to disable stitches (optional)
Whether to mark as brand content (optional)
Whether to mark as organic brand content (optional)
Whether the content is AI-generated (optional)
Whether to auto-add music (optional)
Enum of (optional):
DIRECT\_POST: Directly post the content to TikTok account.
MEDIA\_UPLOAD: Upload content to TikTok to complete the post using TikTok's editing flow. Users will receive an inbox notification.
ID of the board to pin to
URL to attach to the pin (optional)
Pin title (optional)
Pin description (optional) - (This allows overriding the platform-specific text content)
Pin note/description (optional)
Whether to publish the pin immediately (default: true)
Custom title for the Threads post (optional) - (This allows overriding the platform-specific text content)
URL to attach to the post (optional)
Custom title for the Telegram message (optional) - (This allows overriding the platform-specific text content)
Whether to disable notifications (default: false)
Whether to protect the content (default: false)
Custom title for the Bluesky post (optional) - (This allows overriding the platform-specific text content)
Website card settings with 'uri', 'title', and 'description' (optional)
```json theme={null}
{
"workspace_id": 12,
"labels": [
5
],
"content": [
{
"text": "Check out our new reel!",
"media": [
1842
],
"cover_image": {
"thumbnail": 1843
}
},
{
"text": "Full lesson + tabs: https://jamfastguitar.com\n#guitar #practice",
"is_first_comment": true,
"first_comment_delay": 1
}
],
"schedule_type": "schedule",
"schedule_for": {
"date": "2025-07-05",
"time": "23:00",
"timezone": "Africa/Addis_Ababa"
},
"accounts": [
{
"id": 136,
"settings": {
"for_super_followers_only": false,
"reply_settings": "everyone",
"quote_tweet_id": null,
"reply": {
"in_reply_to_tweet_id": null
},
"community_id": null,
"share_with_followers": true,
"text": null
}
}
]
}
```
### Response
Unique identifier for the created post
Array of content objects with text, media, optional `cover_image`, and optional first-comment fields (`is_first_comment`, `first_comment_delay`). Platform cover requirements: TikTok - `video_cover_timestamp_ms` only; YouTube, Instagram, Facebook - `thumbnail` only.
Status of the post (draft, scheduled, published, failed)
Array of posted timestamps in workspace timezone
Scheduled date and time in workspace timezone
Whether the post is set to repeat
Number of times the post will repeat
Gap between repeats
Unit for the repeat gap (hours, days, weeks, months)
Number of remaining posts in the repeat sequence
Information about the post's workspace
Array of labels attached to the post
Array of platform-specific post information
Auto plug configuration when enabled, or `null` when off. Update via [Update post auto plug](/api-reference/posts/auto-plug).
Creation timestamp in workspace timezone
Last update timestamp in workspace timezone
```json theme={null}
{
"id": 749,
"content": [
{
"text": "Post Once. Publissh everywhere",
"media": [
{
"id": 1672,
"name": "banner.png",
"url": "https://postsyncer.com/images/og/banner.png?v2",
"type": "image/png",
"size": 210537
}
]
}
],
"status": "SCHEDULED",
"posted_on": [],
"scheduled_at": "2025-07-05 23:00 PM",
"repeatable": null,
"repeatable_times": null,
"repeatable_gap": null,
"repeatable_gap_unit": null,
"remaining_posts": null,
"created_at": "2025-07-05 17:04 PM",
"updated_at": "2025-07-05 17:04 PM",
"workspace": {
"id": 12,
"name": "abdulmejidshemsuawel",
"slug": "abdulmejidshemsuawel",
"type": "PERSONAL",
"logo": null,
"timezone": "Africa/Addis_Ababa",
"language": "en"
},
"labels": [
{
"id": 5,
"name": "ol pasdm",
"color": "#bf3434"
}
],
"platforms": [
{
"platform": "twitter",
"posted_on": [],
"status": "PENDING",
"settings": {
"for_super_followers_only": false,
"reply_settings": "everyone",
"quote_tweet_id": null,
"reply": {
"in_reply_to_tweet_id": null
},
"community_id": null,
"share_with_followers": true,
"text": "",
"poll": {
"enabled": false,
"duration_minutes": null,
"reply_settings": "everyone",
"options": null
}
}
}
],
"auto_plug": null
}
```
### Code Examples
```bash cURL theme={null}
curl -X POST "https://postsyncer.com/api/v1/posts" \
-H "Authorization: Bearer YOUR_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"workspace_id": 12,
"labels": [
5
],
"content": [
{
"text": "Post Once. Publissh everywhere",
"media": [
"https://postsyncer.com/images/og/banner.png?v2"
]
}
],
"schedule_type": "schedule",
"schedule_for": {
"date": "2025-07-05",
"time": "23:00",
"timezone": "Africa/Addis_Ababa"
},
"accounts": [
{
"id": 136,
"settings": {
"for_super_followers_only": false,
"reply_settings": "everyone",
"quote_tweet_id": null,
"reply": {
"in_reply_to_tweet_id": null
},
"community_id": null,
"share_with_followers": true,
"text": null
}
}
]
}'
```
```javascript Node.js theme={null}
const axios = require('axios');
const createPost = async () => {
try {
const response = await axios.post('https://postsyncer.com/api/v1/posts', {
workspace_id: 12,
labels: [
5
],
content: [
{
text: 'Post Once. Publissh everywhere',
media: [
'https://postsyncer.com/images/og/banner.png?v2'
]
}
],
schedule_type: 'schedule',
schedule_for: {
date: '2025-07-05',
time: '23:00',
timezone: 'Africa/Addis_Ababa'
},
accounts: [
{
id: 136,
settings: {
for_super_followers_only: false,
reply_settings: 'everyone',
quote_tweet_id: null,
reply: {
in_reply_to_tweet_id: null
},
community_id: null,
share_with_followers: true,
text: null
}
}
]
}, {
headers: {
'Authorization': 'Bearer YOUR_API_TOKEN',
'Content-Type': 'application/json'
}
});
console.log('Post created:', response.data);
} catch (error) {
console.error('Error creating post:', error.response?.data || error.message);
}
};
createPost();
```
```php PHP theme={null}
12,
'labels' => [
5
],
'content' => [
[
'text' => 'Post Once. Publissh everywhere',
'media' => [
'https://postsyncer.com/images/og/banner.png?v2'
]
]
],
'schedule_type' => 'schedule',
'schedule_for' => [
'date' => '2025-07-05',
'time' => '23:00',
'timezone' => 'Africa/Addis_Ababa'
],
'accounts' => [
[
'id' => 136,
'settings' => [
'for_super_followers_only' => false,
'reply_settings' => 'everyone',
'quote_tweet_id' => null,
'reply' => [
'in_reply_to_tweet_id' => null
],
'community_id' => null,
'share_with_followers': true,
'text' => null
]
]
]
];
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, 'https://postsyncer.com/api/v1/posts');
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data));
curl_setopt($ch, CURLOPT_HTTPHEADER, [
'Authorization: Bearer YOUR_API_TOKEN',
'Content-Type: application/json'
]);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($httpCode === 201) {
$post = json_decode($response, true);
echo "Post created: " . $post['id'];
} else {
echo "Error: " . $response;
}
?>
```
```python Python theme={null}
import requests
import json
data = {
'workspace_id': 12,
'labels': [
5
],
'content': [
{
'text': 'Post Once. Publissh everywhere',
'media': [
'https://postsyncer.com/images/og/banner.png?v2'
]
}
],
'schedule_type': 'schedule',
'schedule_for': {
'date': '2025-07-05',
'time': '23:00',
'timezone': 'Africa/Addis_Ababa'
},
'accounts': [
{
'id': 136,
'settings': {
'for_super_followers_only': False,
'reply_settings': 'everyone',
'quote_tweet_id': None,
'reply': {
'in_reply_to_tweet_id': None
},
'community_id': None,
'share_with_followers': true,
'text': None
}
}
]
}
headers = {
'Authorization': 'Bearer YOUR_API_TOKEN',
'Content-Type': 'application/json'
}
response = requests.post(
'https://postsyncer.com/api/v1/posts',
json=data,
headers=headers
)
if response.status_code == 201:
post = response.json()
print(f"Post created: {post['id']}")
else:
print(f"Error: {response.status_code}")
print(response.text)
```
### Platform-Specific Settings
Each social media platform supports different settings that allow you to customize how your post appears and behaves. These settings are specified in the `settings` object for each account in the `accounts` array.
#### Twitter/X Settings
* **for\_super\_followers\_only**: Restrict post visibility to super followers
* **reply\_settings**: Control who can reply ('everyone', 'following', 'mentioned\_users')
* **quote\_tweet\_id**: Quote an existing tweet
* **reply**: Reply to a specific tweet
* **community\_id**: Post to a specific community
* **share\_with\_followers**: Share community post with followers too
* **text**: Override the main text content for Twitter
* **poll**: Create a poll with options and duration
#### Instagram Settings
* **post\_type**: Choose between 'REELS', 'STORIES', or 'POST'
* **caption**: Custom caption with hashtags and mentions
#### Facebook Settings
* **title**: Custom title for the post
* **link**: Attach a URL to the post
#### LinkedIn Settings
* **visibility**: Control post visibility ('PUBLIC', 'CONNECTIONS', 'LOGGED\_IN')
* **link**: Attach a URL to the post
* **title**: Custom title for the post
#### YouTube Settings
* **video\_type**: Choose between 'video' or 'short'
* **title**: Video title
* **description**: Video description
* **privacyStatus**: Set privacy ('public', 'private', 'unlisted')
* **tags**: Array of tags for better discoverability
* **embeddable**: Allow video embedding
* **selfDeclaredMadeForKids**: Mark as content for children
* **notifySubscribers**: Send notifications to subscribers
* **category\_id**: YouTube category
#### TikTok Settings
* **privacy\_level**: Set video privacy (PUBLIC\_TO\_EVERYONE, MUTUAL\_FOLLOW\_FRIENDS, FOLLOWER\_OF\_CREATOR, SELF\_ONLY)
* **title**: Video title
* **description**: Video description
* **disable\_comment**: Turn off comments
* **disable\_duet**: Disable duet feature
* **disable\_stitch**: Disable stitch feature
* **brand\_content\_toggle**: Mark as brand content
* **brand\_organic\_toggle**: Mark as organic brand content
* **is\_aigc**: Indicate AI-generated content
* **auto\_add\_music**: Automatically add music
* **post\_mode**: Enum: DIRECT\_POST (post directly) | MEDIA\_UPLOAD (upload for user to finish in TikTok)
#### Pinterest Settings
* **board\_id**: Specify which board to pin to
* **link**: Attach a URL to the pin
* **title**: Pin title
* **description**: Pin description
* **note**: Additional pin notes
* **published**: Publish immediately or save as draft
#### Threads Settings
* **title**: Custom title for the post
* **link\_attachment**: Attach a URL to the post
#### Telegram Settings
* **title**: Custom title for the message
* **disable\_notification**: Send silently
* **protect\_content**: Protect from forwarding
#### Bluesky Settings
* **title**: Custom title for the post
* **website\_card**: Add a website card with URI, title, and description
### Media Guidelines
Different social platforms have different requirements for media. PostSyncer will automatically process your media to meet platform requirements, but keep these limitations in mind:
#### First comments
To schedule a first comment (hashtags, link, CTA) the same way the composer does, add a second content item with `is_first_comment: true`. On **Instagram, Facebook, LinkedIn, and YouTube**, that item is excluded from the main caption and posted as a comment after publish.
```json theme={null}
"content": [
{
"text": "Clean caption for the post",
"media": [1842]
},
{
"text": "Read more: https://example.com\n#hashtags",
"is_first_comment": true,
"first_comment_delay": 1
}
]
```
**Supported first-comment platforms only:** Instagram, Facebook, LinkedIn, and YouTube. Other platforms do **not** schedule a first comment from this flag.
**Multi-platform posts:** If the same post also targets accounts that do not support first comments (for example TikTok or Threads), the `is_first_comment` item is still part of `content`, but those platforms handle it differently — TikTok may fold the text into the caption; Threads / X may treat extra `content` items as a thread. Put anything that must appear on every platform (links, CTAs) in the main caption, or wait for per-platform content if you need different copy per network.
On **X/Twitter**, `content` items are published as a native thread. Do **not** set `is_first_comment` for Twitter posts — use a separate comment via [`POST /comments`](/api-reference/comments/create) after publish if you need a reply.
#### Video cover images
Set `content[0].cover_image` when creating or updating a video post. Only the first content thread's cover is applied when publishing.
#### Platform requirements for cover images
| Platform | Cover method | API field |
| ---------------------------- | ----------------------------------- | -------------------------- |
| **TikTok** | Frame selection from video **only** | `video_cover_timestamp_ms` |
| **YouTube** | Custom thumbnail upload **only** | `thumbnail` |
| **Instagram** (Reels) | Custom thumbnail upload **only** | `thumbnail` |
| **Facebook** (Reels / video) | Custom thumbnail upload **only** | `thumbnail` |
Do **not** send `thumbnail` for TikTok - use `video_cover_timestamp_ms` to pick a frame from the video. Do **not** send `video_cover_timestamp_ms` for YouTube, Instagram, or Facebook - upload a cover image and pass its library `id` (or a public image URL) in `thumbnail`.
**TikTok example** - frame at 2.5 seconds:
```json theme={null}
"content": [{
"text": "New TikTok!",
"media": [1842],
"cover_image": {"video_cover_timestamp_ms": 2500}
}]
```
**Instagram / Facebook / YouTube example** - custom cover image:
```json theme={null}
"content": [{
"text": "New reel!",
"media": [1842],
"cover_image": {"thumbnail": 1843}
}]
```
Typical workflow for custom thumbnails: upload the video → upload the cover image → create the post with both ids in `content[0]`.
| Platform | Max Images | Max Videos | Required Media | Max File Size |
| --------- | ---------- | ---------- | -------------- | ------------------------- |
| Twitter/X | 4 | 1 | No | 5MB (img), 512MB (video) |
| Facebook | 10 | 1 | No | 10MB (img), 4GB (video) |
| Instagram | 10 | 1 | Yes | 8MB (img), 100MB (video) |
| TikTok | 35 | 1 | Yes | 20MB (img), 4GB (video) |
| YouTube | 0 | 1 | Yes | N/A, 10GB (video) |
| Pinterest | 5 | 1 | Yes | 20MB (img), 200MB (video) |
| Threads | 10 | 10 | No | 8MB (img), 1GB (video) |
| Telegram | 10 | 10 | No | 5MB (img), 20MB (video) |
| LinkedIn | 9 | 1 | No | 5MB (img), 512MB (video) |
| Bluesky | 4 | 1 | No | 1MB (img), 100MB (video) |
### Error Codes
Validation errors, invalid parameters, or invalid media URLs
Missing or invalid API token
Token does not have 'posts' permission
Workspace, label, or account not found
Validation errors in request data
# Delete Post
Source: https://docs.postsyncer.com/api-reference/posts/delete
DELETE posts/{id}
Delete a post and cancel any scheduled publishing
## Delete Post
Deletes a post and cancels any scheduled publishing. This action cannot be undone.
### Request
The ID of the post to delete
```bash theme={null}
curl -X DELETE "https://postsyncer.com/api/v1/posts/123" \
-H "Authorization: Bearer YOUR_API_TOKEN"
```
### Response
Success message confirming the post was deleted
```json theme={null}
{
"message": "Post deleted successfully"
}
```
### Code Examples
```bash cURL theme={null}
curl -X DELETE "https://postsyncer.com/api/v1/posts/123" \
-H "Authorization: Bearer YOUR_API_TOKEN"
```
```javascript Node.js theme={null}
const axios = require('axios');
const deletePost = async (postId) => {
try {
const response = await axios.delete(`https://postsyncer.com/api/v1/posts/${postId}`, {
headers: {
'Authorization': 'Bearer YOUR_API_TOKEN'
}
});
console.log('Post deleted:', response.data.message);
} catch (error) {
console.error('Error deleting post:', error.response?.data || error.message);
}
};
deletePost(123);
```
```php PHP theme={null}
```
```python Python theme={null}
import requests
post_id = 123
headers = {
'Authorization': 'Bearer YOUR_API_TOKEN'
}
response = requests.delete(
f'https://postsyncer.com/api/v1/posts/{post_id}',
headers=headers
)
if response.status_code == 200:
result = response.json()
print(result['message'])
else:
print(f"Error: {response.status_code}")
print(response.text)
```
### Error Codes
Missing or invalid API token
Token does not have 'posts' permission
Post not found or user does not have access to the post's workspace
Invalid post ID provided
# Get Post
Source: https://docs.postsyncer.com/api-reference/posts/get
GET posts/{id}
Retrieve a specific post by ID
## Get Post
Retrieves detailed information about a specific post, including its content, status, scheduling information, and platform-specific details. Use `include_comments=true` to attach all comments for the post, including their associated contact info.
You can also load the same resource [by public permalink](/api-reference/posts/get-by-url) or [by platform-native post id](/api-reference/posts/get-by-platform-post-id) when you do not know the internal PostSyncer id.
For **aggregated analytics** without parsing `platforms` here, use the [Analytics API](/api-reference/analytics/overview) - for example [`GET /analytics/posts/{post}`](/api-reference/analytics/post) for one post (totals plus per-account rows).
### Request
The ID of the post to retrieve
When true, includes all comments associated with the post, including their contact info (CRM contact)
```bash theme={null}
curl -X GET "https://postsyncer.com/api/v1/posts/123?include_comments=true" \
-H "Authorization: Bearer YOUR_API_TOKEN"
```
### Response
Unique identifier for the post
Array of content objects with text, media, optional `cover_image`, and optional first-comment fields (`is_first_comment`, `first_comment_delay`). Cover requirements by platform: TikTok uses `video_cover_timestamp_ms` only; YouTube, Instagram, and Facebook use `thumbnail` only - see [Create post](/api-reference/posts/create#platform-requirements-for-cover-images). First comments: see [Create post - first comments](/api-reference/posts/create#first-comments).
Status of the post (draft, scheduled, published, failed)
Array of posted timestamps in workspace timezone
Scheduled date and time in workspace timezone
Whether the post is set to repeat
Number of times the post will repeat
Gap between repeats
Unit for the repeat gap (`minutes`, `hours`, `days`, `weeks`, `months`)
Account IDs limiting repeat scheduling when set; configure with `repeatable` / `repeatable_accounts` on [create](/api-reference/posts/create) or [update](/api-reference/posts/update).
Number of remaining posts in the repeat sequence
Information about the post's workspace
Array of labels attached to the post
Array of platform-specific post information. For published posts, each platform includes an `analytics` object with performance metrics (likes, comments, impressions, engagement\_rate, etc.)
Auto plug when enabled, or `null`. Update via [Update post auto plug](/api-reference/posts/auto-plug).
Per-post comment moderation. Update via [Update post comment moderation](/api-reference/posts/comment-moderation).
CRM contact collection rules. Update via [Update post contact collection](/api-reference/posts/contact-collection).
When `include_comments=true`, includes all comments for the post. Each comment has `id`, `content`, `author_name`, `platform`, `created_at`, and a `contact` object with CRM contact info (name, username, profile\_url, etc.) when available.
Creation timestamp in workspace timezone
Last update timestamp in workspace timezone
```json theme={null}
{
"id": 743,
"content": [
{
"text": "",
"media": [
{
"id": 1594,
"name": "file_example_MP4_1920_18MG.mp4",
"url": "https://postsyncer-local.c6c56f7fb91dd557ca29aa14c4e7d980.r2.cloudflarestorage.com/media/12/5ee789eb-c88c-4af2-8449-54c6c0561f31.mp4?X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=a4bcd089276d5d5b77253aa9b13fe59c%2F20250705%2Fauto%2Fs3%2Faws4_request&X-Amz-Date=20250705T131116Z&X-Amz-SignedHeaders=host&X-Amz-Expires=1800&X-Amz-Signature=0ef23b86009e4cac6e160863a4b91f15b78270e5524f328fb764a9f22bdaffe3",
"type": "video/mp4",
"size": 17839845
}
]
}
],
"status": "PUBLISHED",
"posted_on": [
"2025-07-05 12:36"
],
"scheduled_at": "2025-07-05 16:00",
"repeatable": false,
"repeatable_times": null,
"repeatable_gap": null,
"repeatable_gap_unit": null,
"remaining_posts": null,
"created_at": "2025-07-05 12:34",
"updated_at": "2025-07-05 12:36",
"workspace": {
"id": 12,
"name": "abdulmejidshemsuawel",
"slug": "abdulmejidshemsuawel",
"type": "PERSONAL",
"logo": null,
"timezone": "Africa/Addis_Ababa",
"language": "en"
},
"labels": [],
"platforms": [
{
"platform": "twitter",
"posted_on": [
"2025-07-05 12:36"
],
"status": "PUBLISHED",
"settings": {
"for_super_followers_only": false,
"reply_settings": "everyone",
"quote_tweet_id": null,
"reply": {
"in_reply_to_tweet_id": null
},
"community_id": null,
"share_with_followers": true,
"text": null,
"poll": {
"enabled": false
}
},
"analytics": {
"comments": 8,
"likes": 42,
"shares": 8,
"impressions": 1250,
"quotes": 2,
"saves": 12,
"engagement_rate": 4.4
}
}
],
"auto_plug": null,
"comments": []
}
```
### Code Examples
```bash cURL theme={null}
curl -X GET "https://postsyncer.com/api/v1/posts/123?include_comments=true" \
-H "Authorization: Bearer YOUR_API_TOKEN"
```
```javascript Node.js theme={null}
const axios = require('axios');
const getPost = async (postId) => {
try {
const response = await axios.get(`https://postsyncer.com/api/v1/posts/${postId}`, {
headers: {
'Authorization': 'Bearer YOUR_API_TOKEN'
}
});
console.log('Post:', response.data);
return response.data;
} catch (error) {
console.error('Error fetching post:', error.response?.data || error.message);
}
};
getPost(123);
```
```php PHP theme={null}
```
```python Python theme={null}
import requests
post_id = 123
headers = {
'Authorization': 'Bearer YOUR_API_TOKEN'
}
response = requests.get(
f'https://postsyncer.com/api/v1/posts/{post_id}',
headers=headers
)
if response.status_code == 200:
post = response.json()
print(f"Post {post['id']}: {post['content'][0]['text']}")
print(f"Status: {post['status']}")
print(f"Platforms: {len(post['platforms'])}")
else:
print(f"Error: {response.status_code}")
print(response.text)
```
### Error Codes
Missing or invalid API token
Token does not have 'posts' permission
Post not found or user does not have access to the post's workspace
# Get Post by platform post id
Source: https://docs.postsyncer.com/api-reference/posts/get-by-platform-post-id
GET posts/by-platform-post-id/{platform_post_id}
Retrieve a post by the native id assigned by the social platform
## Get Post by platform post id
Returns the same payload as [Get Post](/api-reference/posts/get), but resolves the post via `platform_posts.platform_post_id` (for example a tweet/status id), scoped to workspaces your token can access.
If the raw id contains characters that are special in URLs, percent-encode that single path segment.
### Request
Platform-native post identifier saved after publishing
Same as [Get Post](/api-reference/posts/get)
```bash theme={null}
curl -X GET "https://postsyncer.com/api/v1/posts/by-platform-post-id/1234567890?include_comments=false" \
-H "Authorization: Bearer YOUR_API_TOKEN"
```
### Response
Same fields as [Get Post](/api-reference/posts/get).
### Error Codes
Missing or invalid API token
Token does not have the `posts` ability
No post with this platform post id in any accessible workspace
# Get Post by URL
Source: https://docs.postsyncer.com/api-reference/posts/get-by-url
GET posts/by-url/{post_url}
Retrieve a post by its public platform permalink (stored as post_url)
## Get Post by URL
Returns the same payload as [Get Post](/api-reference/posts/get), but looks up the record by the **exact** `post_url` saved on a published platform row (`platform_posts.post_url`), scoped to workspaces your token can access.
Use this when you have a share link (for example `https://x.com/user/status/123`) instead of the internal PostSyncer post id.
### Path encoding
The URL must be passed as **one path segment**. Percent-encode it (RFC 3986), so slashes and colons become `%2F`, `%3A`, etc.
### Request
Percent-encoded public permalink matching the stored platform post URL
Same as [Get Post](/api-reference/posts/get): include comments and CRM contact info when true
```bash theme={null}
curl -X GET "https://postsyncer.com/api/v1/posts/by-url/https%3A%2F%2Fx.com%2Fuser%2Fstatus%2F1234567890?include_comments=false" \
-H "Authorization: Bearer YOUR_API_TOKEN"
```
### Response
Same fields as [Get Post](/api-reference/posts/get).
### Error Codes
Missing or invalid API token
Token does not have the `posts` ability
No post with this URL in any accessible workspace
# List Posts
Source: https://docs.postsyncer.com/api-reference/posts/list
GET posts
Retrieve a paginated list of posts from your workspaces
## List Posts
Retrieves a paginated list of posts from all workspaces that the authenticated user has access to. Posts are returned in reverse chronological order (newest first). Use `include_comments=true` to attach all comments for each post, including their associated contact info.
### Request
Page number for pagination
Number of posts per page (maximum 100)
When true, includes all comments associated with each post, including their contact info (CRM contact)
```bash theme={null}
curl -X GET "https://postsyncer.com/api/v1/posts?page=1&per_page=20&include_comments=true" \
-H "Authorization: Bearer YOUR_API_TOKEN"
```
### Response
Array of post objects
Current page number
Last page number
Number of items per page
Total number of posts
Starting post number for current page
Ending post number for current page
When `include_comments=true`, each post includes a `comments` array. Each comment has `id`, `content`, `author_name`, `platform`, `created_at`, and a `contact` object with CRM contact info (name, username, profile\_url, etc.) when available.
```json theme={null}
{
"data": [
{
"id": 123,
"content": [
{
"text": "Exciting news! We've just launched our winter collection ❄️",
"media": [
{
"id": 1594,
"name": "file_example_MP4_1920_18MG.mp4",
"url": "https://postsyncer-local.c6c56f7fb91dd557ca29aa14c4e7d980.r2.cloudflarestorage.com/media/12/5ee789eb-c88c-4af2-8449-54c6c0561f31.mp4?X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=a4bcd089276d5d5b77253aa9b13fe59c%2F20250705%2Fauto%2Fs3%2Faws4_request&X-Amz-Date=20250705T131116Z&X-Amz-SignedHeaders=host&X-Amz-Expires=1800&X-Amz-Signature=0ef23b86009e4cac6e160863a4b91f15b78270e5524f328fb764a9f22bdaffe3",
"type": "video/mp4",
"size": 17839845
}
]
}
],
"status": "PUBLISHED",
"posted_on": ["2023-12-15 02:00 PM"],
"scheduled_at": null,
"repeatable": false,
"repeatable_times": null,
"repeatable_gap": null,
"repeatable_gap_unit": null,
"remaining_posts": null,
"workspace": {
"id": 12,
"name": "abdulmejidshemsuawel",
"slug": "abdulmejidshemsuawel",
"type": "PERSONAL",
"logo": null,
"timezone": "Africa/Addis_Ababa",
"language": "en"
},
"labels": [
{
"id": 1,
"name": "Product Launch",
"color": "#ff0000"
}
],
"platforms": [
{
"platform": "twitter",
"posted_on": ["2025-07-05 12:36"],
"status": "PUBLISHED",
"settings": {
"for_super_followers_only": false,
"reply_settings": "everyone",
"quote_tweet_id": null,
"reply": {
"in_reply_to_tweet_id": null
},
"community_id": null,
"share_with_followers": true,
"text": null,
"poll": {
"enabled": false
}
},
"analytics": {
"comments": 8,
"likes": 42,
"shares": 8,
"impressions": 1250,
"quotes": 2,
"saves": 12,
"engagement_rate": 4.4
}
}
],
"auto_plug": null,
"created_at": "2023-12-01 09:30 AM",
"updated_at": "2023-12-15 02:00 PM",
"comments": []
}
],
"current_page": 1,
"last_page": 5,
"per_page": 20,
"total": 95,
"from": 1,
"to": 20,
"links": [
{
"url": "https://postsyncer.com/api/v1/posts?page=1",
"label": "« Previous",
"active": false
},
{
"url": "https://postsyncer.com/api/v1/posts?page=1",
"label": "1",
"active": false
},
{
"url": "https://postsyncer.com.test/api/v1/posts?page=2",
"label": "2",
"active": true
}
...
]
}
```
### Code Examples
```bash cURL theme={null}
curl -X GET "https://postsyncer.com/api/v1/posts?page=1&per_page=20&include_comments=true" \
-H "Authorization: Bearer YOUR_API_TOKEN"
```
```javascript Node.js theme={null}
const axios = require('axios');
const getPosts = async (page = 1, perPage = 20) => {
try {
const response = await axios.get('https://postsyncer.com/api/v1/posts', {
headers: {
'Authorization': 'Bearer YOUR_API_TOKEN'
},
params: {
page: page,
per_page: perPage
}
});
console.log('Posts:', response.data);
return response.data;
} catch (error) {
console.error('Error fetching posts:', error.response?.data || error.message);
}
};
getPosts();
```
```php PHP theme={null}
```
```python Python theme={null}
import requests
headers = {
'Authorization': 'Bearer YOUR_API_TOKEN'
}
params = {
'page': 1,
'per_page': 20
}
response = requests.get(
'https://postsyncer.com/api/v1/posts',
headers=headers,
params=params
)
if response.status_code == 200:
posts = response.json()
print(f"Found {posts['total']} posts")
for post in posts['data']:
print(f"Post {post['id']}: {post['content'][0]['text'][:50]}...")
else:
print(f"Error: {response.status_code}")
print(response.text)
```
### Error Codes
Missing or invalid API token
Token does not have 'posts' permission
# Update Post
Source: https://docs.postsyncer.com/api-reference/posts/update
PUT posts/{id}
Update an existing post with new content, scheduling, or settings. Only posts that have not been published yet can be updated.
## Update Post
Updates an existing post with new content, scheduling information, or other settings. Only posts that haven't been published yet can be updated. The post can include text content, media attachments, and platform-specific settings.
### Request
The ID of the workspace where the post will be created
Optional array of label IDs to apply to this post
Array of content objects (threads). Each object can contain text and media. At least one content item with text or media is required.
The text content for this post
Each item is a **string** (image/video URL) or (library id from [Media upload file](/api-reference/media/upload-file) / [Import URL](/api-reference/media/upload-url)). Mix allowed. See [Media overview](/api-reference/media/overview).
Optional cover/thumbnail for video posts. Only the **first** content item's `cover_image` is used when publishing. **Which field you use depends on the target platform** - see [Platform requirements](/api-reference/posts/create#platform-requirements-for-cover-images).
**Custom cover image upload** - workspace media library **id** (image only) or public **HTTPS URL**. **YouTube, Instagram, and Facebook only.** Not used on TikTok.
**Frame selection from the attached video** - timestamp in **milliseconds**. **TikTok only.**
When `true`, this content item is posted as the first comment after the main post publishes (Instagram, Facebook, LinkedIn, YouTube). Only one content item may set this. On other platforms in the same post (e.g. TikTok, Threads), the flag does not schedule a first comment — see [First comments](/api-reference/posts/create#first-comments).
Minutes to wait after publish before posting the first comment. Defaults to `1` when `is_first_comment` is `true`. Max `10080` (7 days).
Type of scheduling. Must be one of: publish\_now, schedule, draft
Optional scheduling object used when `schedule_type` is `schedule`. Provide `{"date": "YYYY-MM-DD", "time": "HH:MM", "timezone": "..."}` to schedule for a specific date/time, or omit/leave empty to auto-schedule to the next available time slot
The date to schedule the post (YYYY-MM-DD format)
The time to schedule the post (HH:MM format in 24-hour)
Timezone for the scheduled time (defaults to workspace timezone)
Whether the post should be repeated
Number of times to repeat the post. Required if repeatable is true.
Time gap between repeats. Required if repeatable is true.
The unit for the repeatable gap. Must be one of: `minutes`, `hours`, `days`, `weeks`, `months`. Required if repeatable is true. Set `repeatable` to `false` to clear repeat scheduling and `repeatable_accounts`.
Optional. Only when `repeatable` is true: account IDs in this workspace. To clear repeat scheduling, send `repeatable: false` (see [Update post](/api-reference/posts/update)).
**[Auto plug](/api-reference/posts/auto-plug)**, **[comment moderation](/api-reference/posts/comment-moderation)**, and **[contact collection](/api-reference/posts/contact-collection)** use dedicated `PATCH` endpoints.
Array of account objects to publish to
The ID of the social media account
Platform-specific settings for this post. Optional except for Pinterest which requires a board\_id. The available settings depend on the platform:
Whether to restrict the post to super followers only (default: false)
Who can reply to the post. Options: 'everyone', 'following', 'mentioned\_users' (default: 'everyone')
ID of the tweet to quote (optional)
Reply settings object with 'in\_reply\_to\_tweet\_id' (optional)
ID of the community to post to (optional)
Share community post with followers too.
Custom text content for Twitter (optional)
Poll settings with 'enabled', 'duration\_minutes', 'reply\_settings', and 'options' (optional)
everyone, following, mentionedUsers
Poll options, array of text
Type of post. Options: 'REELS', 'STORIES', 'POST' (default: 'REELS') (optional)
Custom caption for Instagram (optional)
Type of post. Options: 'REELS', 'STORIES', 'POST' (default: 'REELS') (optional)
Custom title for the Facebook post (optional)
URL to attach to the post (optional)
Post visibility. Options: 'PUBLIC', 'CONNECTIONS', 'LOGGED\_IN' (default: 'PUBLIC')
URL to attach to the post (optional)
Custom title for the LinkedIn post (optional)
Type of video. Options: 'video', 'short' (default: 'video')
Video title (optional)
Video description (optional)
Privacy status. Options: 'public', 'private', 'unlisted' (default: 'public')
Array of tags for the video (optional)
Whether the video can be embedded (default: true)
Whether the video is made for kids (default: false)
Whether to notify subscribers (default: true)
YouTube category ID
Privacy level for the video (optional) PUBLIC\_TO\_EVERYONE, MUTUAL\_FOLLOW\_FRIENDS, FOLLOWER\_OF\_CREATOR, SELF\_ONLY
Video title (optional)
Video description (optional)
Whether to disable comments (optional)
Whether to disable duets (optional)
Whether to disable stitches (optional)
Whether to mark as brand content (optional)
Whether to mark as organic brand content (optional)
Whether the content is AI-generated (optional)
Whether to auto-add music (optional)
Enum of (optional):
DIRECT\_POST: Directly post the content to TikTok account.
MEDIA\_UPLOAD: Upload content to TikTok to complete the post using TikTok's editing flow. Users will receive an inbox notification.
ID of the board to pin to
URL to attach to the pin (optional)
Pin title (optional)
Pin description (optional)
Pin note/description (optional)
Whether to publish the pin immediately (default: true)
Custom title for the Threads post (optional)
URL to attach to the post (optional)
Custom title for the Telegram message (optional)
Whether to disable notifications (default: false)
Whether to protect the content (default: false)
Custom title for the Bluesky post (optional)
Website card settings with 'uri', 'title', and 'description' (optional)
```json theme={null}
{
"workspace_id": 12,
"labels": [
5
],
"content": [
{
"text": "Updated: Post Once. Publish everywhere",
"media": [
"https://postsyncer.com/images/og/banner.png?v2"
]
}
],
"schedule_type": "schedule",
"schedule_for": {
"date": "2025-07-05",
"time": "23:00",
"timezone": "Africa/Addis_Ababa"
},
"accounts": [
{
"id": 136,
"settings": {
"for_super_followers_only": false,
"reply_settings": "everyone",
"quote_tweet_id": null,
"reply": {
"in_reply_to_tweet_id": null
},
"community_id": null,
"share_with_followers": true,
"text": null
}
}
]
}
```
### Response
Unique identifier for the updated post
Array of content objects with text, media, optional `cover_image`, and optional first-comment fields (`is_first_comment`, `first_comment_delay`). Platform cover requirements: TikTok - `video_cover_timestamp_ms` only; YouTube, Instagram, Facebook - `thumbnail` only.
Status of the post (draft, scheduled, published, failed)
Array of posted timestamps in workspace timezone
Scheduled date and time in workspace timezone
Whether the post is set to repeat
Number of times the post will repeat
Gap between repeats
Unit for the repeat gap (hours, days, weeks, months)
Number of remaining posts in the repeat sequence
Information about the post's workspace
Array of labels attached to the post
Array of platform-specific post information
Auto plug configuration when enabled, or `null` when off. Update via [Update post auto plug](/api-reference/posts/auto-plug).
Creation timestamp in workspace timezone
Last update timestamp in workspace timezone
```json theme={null}
{
"id": 749,
"content": [
{
"text": "Updated: Post Once. Publish everywhere",
"media": [
{
"id": 1672,
"name": "banner.png",
"url": "https://postsyncer.com/images/og/banner.png?v2",
"type": "image/png",
"size": 210537
}
]
}
],
"status": "SCHEDULED",
"posted_on": [],
"scheduled_at": "2025-07-05 23:00 PM",
"repeatable": null,
"repeatable_times": null,
"repeatable_gap": null,
"repeatable_gap_unit": null,
"remaining_posts": null,
"created_at": "2025-07-05 17:04 PM",
"updated_at": "2025-07-05 17:05 PM",
"workspace": {
"id": 12,
"name": "abdulmejidshemsuawel",
"slug": "abdulmejidshemsuawel",
"type": "PERSONAL",
"logo": null,
"timezone": "Africa/Addis_Ababa",
"language": "en"
},
"labels": [
{
"id": 5,
"name": "ol pasdm",
"color": "#bf3434"
}
],
"platforms": [
{
"platform": "twitter",
"posted_on": [],
"status": "PENDING",
"settings": {
"for_super_followers_only": false,
"reply_settings": "everyone",
"quote_tweet_id": null,
"reply": {
"in_reply_to_tweet_id": null
},
"community_id": null,
"share_with_followers": true,
"text": "",
"poll": {
"enabled": false,
"duration_minutes": null,
"reply_settings": "everyone",
"options": null
}
}
}
],
"auto_plug": null
}
```
### Code Examples
```bash cURL theme={null}
curl -X PUT "https://postsyncer.com/api/v1/posts/749" \
-H "Authorization: Bearer YOUR_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"workspace_id": 12,
"labels": [
5
],
"content": [
{
"text": "Updated: Post Once. Publish everywhere",
"media": [
"https://postsyncer.com/images/og/banner.png?v2"
]
}
],
"schedule_type": "schedule",
"schedule_for": {
"date": "2025-07-05",
"time": "23:00",
"timezone": "Africa/Addis_Ababa"
},
"accounts": [
{
"id": 136,
"settings": {
"for_super_followers_only": false,
"reply_settings": "everyone",
"quote_tweet_id": null,
"reply": {
"in_reply_to_tweet_id": null
},
"community_id": null,
"share_with_followers": true,
"text": null
}
}
]
}'
```
```javascript Node.js theme={null}
const axios = require('axios');
const updatePost = async (postId) => {
try {
const response = await axios.put(`https://postsyncer.com/api/v1/posts/${postId}`, {
workspace_id: 12,
labels: [
5
],
content: [
{
text: 'Updated: Post Once. Publish everywhere',
media: [
'https://postsyncer.com/images/og/banner.png?v2'
]
}
],
schedule_type: 'schedule',
schedule_for: {
date: '2025-07-05',
time: '23:00',
timezone: 'Africa/Addis_Ababa'
},
accounts: [
{
id: 136,
settings: {
for_super_followers_only: false,
reply_settings: 'everyone',
quote_tweet_id: null,
reply: {
in_reply_to_tweet_id: null
},
community_id: null,
share_with_followers: true,
text: null
}
}
]
}, {
headers: {
'Authorization': 'Bearer YOUR_API_TOKEN',
'Content-Type': 'application/json'
}
});
console.log('Post updated:', response.data);
} catch (error) {
console.error('Error updating post:', error.response?.data || error.message);
}
};
updatePost(749);
```
```php PHP theme={null}
12,
'labels' => [
5
],
'content' => [
[
'text' => 'Updated: Post Once. Publish everywhere',
'media' => [
'https://postsyncer.com/images/og/banner.png?v2'
]
]
],
'schedule_type' => 'schedule',
'schedule_for' => [
'date' => '2025-07-05',
'time' => '23:00',
'timezone' => 'Africa/Addis_Ababa'
],
'accounts' => [
[
'id' => 136,
'settings' => [
'for_super_followers_only' => false,
'reply_settings' => 'everyone',
'quote_tweet_id' => null,
'reply' => [
'in_reply_to_tweet_id' => null
],
'community_id' => null,
'share_with_followers': true,
'text' => null
]
]
]
];
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, "https://postsyncer.com/api/v1/posts/{$postId}");
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'PUT');
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data));
curl_setopt($ch, CURLOPT_HTTPHEADER, [
'Authorization: Bearer YOUR_API_TOKEN',
'Content-Type: application/json'
]);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($httpCode === 200) {
$post = json_decode($response, true);
echo "Post updated: " . $post['data']['id'];
} else {
echo "Error: " . $response;
}
?>
```
```python Python theme={null}
import requests
import json
post_id = 749
data = {
'workspace_id': 12,
'labels': [
5
],
'content': [
{
'text': 'Updated: Post Once. Publish everywhere',
'media': [
'https://postsyncer.com/images/og/banner.png?v2'
]
}
],
'schedule_type': 'schedule',
'schedule_for': {
'date': '2025-07-05',
'time': '23:00',
'timezone': 'Africa/Addis_Ababa'
},
'accounts': [
{
'id': 136,
'settings': {
'for_super_followers_only': False,
'reply_settings': 'everyone',
'quote_tweet_id': None,
'reply': {
'in_reply_to_tweet_id': None
},
'community_id': None,
'share_with_followers': true,
'text': None
}
}
]
}
headers = {
'Authorization': 'Bearer YOUR_API_TOKEN',
'Content-Type': 'application/json'
}
response = requests.put(
f'https://postsyncer.com/api/v1/posts/{post_id}',
json=data,
headers=headers
)
if response.status_code == 200:
post = response.json()
print(f"Post updated: {post['data']['id']}")
else:
print(f"Error: {response.status_code}")
print(response.text)
```
### Platform-Specific Settings
Each social media platform supports different settings that allow you to customize how your post appears and behaves. These settings are specified in the `settings` object for each account in the `accounts` array.
#### Twitter/X Settings
* **for\_super\_followers\_only**: Restrict post visibility to super followers
* **reply\_settings**: Control who can reply ('everyone', 'following', 'mentioned\_users')
* **quote\_tweet\_id**: Quote an existing tweet
* **reply**: Reply to a specific tweet
* **community\_id**: Post to a specific community
* **share\_with\_followers**: Share community post with followers too
* **text**: Override the main text content for Twitter
* **poll**: Create a poll with options and duration
#### Instagram Settings
* **post\_type**: Choose between 'REELS', 'STORIES', 'POST' (optional)
* **caption**: Custom caption with hashtags and mentions
#### Facebook Settings
* **title**: Custom title for the post
* **link**: Attach a URL to the post
#### LinkedIn Settings
* **visibility**: Control post visibility ('PUBLIC', 'CONNECTIONS', 'LOGGED\_IN')
* **link**: Attach a URL to the post
* **title**: Custom title for the post
#### YouTube Settings
* **video\_type**: Choose between 'video' or 'short'
* **title**: Video title
* **description**: Video description
* **privacyStatus**: Set privacy ('public', 'private', 'unlisted')
* **tags**: Array of tags for better discoverability
* **embeddable**: Allow video embedding
* **selfDeclaredMadeForKids**: Mark as content for children
* **notifySubscribers**: Send notifications to subscribers
* **category\_id**: YouTube category
#### TikTok Settings
* **privacy\_level**: Set video privacy (PUBLIC\_TO\_EVERYONE, MUTUAL\_FOLLOW\_FRIENDS, FOLLOWER\_OF\_CREATOR, SELF\_ONLY)
* **title**: Video title
* **description**: Video description
* **disable\_comment**: Turn off comments
* **disable\_duet**: Disable duet feature
* **disable\_stitch**: Disable stitch feature
* **brand\_content\_toggle**: Mark as brand content
* **brand\_organic\_toggle**: Mark as organic brand content
* **is\_aigc**: Indicate AI-generated content
* **auto\_add\_music**: Automatically add music
* **post\_mode**: Enum: DIRECT\_POST (post directly) | MEDIA\_UPLOAD (upload for user to finish in TikTok)
#### Pinterest Settings
* **board\_id**: Specify which board to pin to
* **link**: Attach a URL to the pin
* **title**: Pin title
* **description**: Pin description
* **note**: Additional pin notes
* **published**: Publish immediately or save as draft
#### Threads Settings
* **title**: Custom title for the post
* **link\_attachment**: Attach a URL to the post
#### Telegram Settings
* **title**: Custom title for the message
* **disable\_notification**: Send silently
* **protect\_content**: Protect from forwarding
#### Bluesky Settings
* **title**: Custom title for the post
* **website\_card**: Add a website card with URI, title, and description
### Media Guidelines
Different social platforms have different requirements for media. PostSyncer will automatically process your media to meet platform requirements, but keep these limitations in mind:
#### Video cover images
Set `content[0].cover_image` when updating a video post. Only the first content thread's cover is applied when publishing.
#### Platform requirements for cover images
| Platform | Cover method | API field |
| ---------------------------- | ----------------------------------- | -------------------------- |
| **TikTok** | Frame selection from video **only** | `video_cover_timestamp_ms` |
| **YouTube** | Custom thumbnail upload **only** | `thumbnail` |
| **Instagram** (Reels) | Custom thumbnail upload **only** | `thumbnail` |
| **Facebook** (Reels / video) | Custom thumbnail upload **only** | `thumbnail` |
**TikTok:** use `video_cover_timestamp_ms` only - no custom thumbnail upload. **YouTube, Instagram, Facebook:** use `thumbnail` only - upload a cover image to the media library (or pass a public image URL).
| Platform | Max Images | Max Videos | Required Media | Max File Size |
| --------- | ---------- | ---------- | -------------- | ------------------------- |
| Twitter/X | 4 | 1 | No | 5MB (img), 512MB (video) |
| Facebook | 10 | 1 | No | 10MB (img), 4GB (video) |
| Instagram | 10 | 1 | Yes | 8MB (img), 100MB (video) |
| TikTok | 35 | 1 | Yes | 20MB (img), 4GB (video) |
| YouTube | 0 | 1 | Yes | N/A, 10GB (video) |
| Pinterest | 5 | 1 | Yes | 20MB (img), 200MB (video) |
| Threads | 10 | 10 | No | 8MB (img), 1GB (video) |
| Telegram | 10 | 10 | No | 5MB (img), 20MB (video) |
| LinkedIn | 9 | 1 | No | 5MB (img), 512MB (video) |
| Bluesky | 4 | 1 | No | 1MB (img), 100MB (video) |
### Error Codes
Validation errors, invalid parameters, or invalid media URLs
Missing or invalid API token
Token does not have 'posts' permission
Post not found or user does not have access to the post's workspace
Validation errors in request data
# List Workspaces
Source: https://docs.postsyncer.com/api-reference/workspaces/list
GET workspaces
Retrieve all workspaces that the authenticated user has access to
## List Workspaces
Retrieves all workspaces that the authenticated user has access to, including their associated accounts.
### Request
```bash theme={null}
curl -X GET "https://postsyncer.com/api/v1/workspaces" \
-H "Authorization: Bearer YOUR_API_TOKEN"
```
### Response
Array of workspace objects
```json theme={null}
{
"data": [
{
"id": 12,
"name": "abdulmejidshemsuawel",
"slug": "abdulmejidshemsuawel",
"type": "PERSONAL",
"logo": null,
"timezone": "Africa/Addis_Ababa",
"language": "en",
"accounts": [
{
"id": 136,
"platform": "twitter",
"username": "heyabdulmejid",
"name": "Abdul",
"avatar": "https://pbs.twimg.com/profile_images/1878383928995684352/3Xw5HTsk_400x400.jpg"
}
]
},
{
"id": 14,
"name": "postsyncer",
"slug": "postsyncer",
"type": "ORGANIZATION",
"logo": null,
"timezone": "Africa/Addis_Ababa",
"language": "en",
"accounts": [
{
"id": 140,
"platform": "twitter",
"username": "postsyncer",
"name": "PostSyncer",
"avatar": "https://pbs.twimg.com/profile_images/1878383928995684352/3Xw5HTsk_400x400.jpg"
}
]
}
]
}
```
### Code Examples
```bash cURL theme={null}
curl -X GET "https://postsyncer.com/api/v1/workspaces" \
-H "Authorization: Bearer YOUR_API_TOKEN"
```
```javascript Node.js theme={null}
const axios = require('axios');
const getWorkspaces = async () => {
try {
const response = await axios.get('https://postsyncer.com/api/v1/workspaces', {
headers: {
'Authorization': 'Bearer YOUR_API_TOKEN'
}
});
console.log('Workspaces:', response.data);
return response.data;
} catch (error) {
console.error('Error fetching workspaces:', error.response?.data || error.message);
}
};
getWorkspaces();
```
```php PHP theme={null}
```
```python Python theme={null}
import requests
headers = {
'Authorization': 'Bearer YOUR_API_TOKEN'
}
response = requests.get(
'https://postsyncer.com/api/v1/workspaces',
headers=headers
)
if response.status_code == 200:
workspaces = response.json()
print(f"Found {len(workspaces['data'])} workspaces")
for workspace in workspaces['data']:
print(f"Workspace {workspace['id']}: {workspace['name']}")
print(f" Accounts: {len(workspace['accounts'])}")
else:
print(f"Error: {response.status_code}")
print(response.text)
```
### Error Codes
Missing or invalid API token
Token does not have 'workspaces' permission
# Authentication
Source: https://docs.postsyncer.com/essentials/authentication
Learn how to authenticate your API requests with PostSyncer
# Authentication
All PostSyncer API requests require authentication using an API key. This guide explains how to obtain and use your API key.
Don't have an API key yet? [Get one here](https://app.postsyncer.com/dashboard?action=settings\§ion=api-integrations).
## Getting Your API Key
1. **Sign in** to your PostSyncer account at [app.postsyncer.com](https://app.postsyncer.com)
2. **Navigate** to [Settings → API Integration](https://app.postsyncer.com/dashboard?action=settings\§ion=api-integrations)
3. **Click** "Create"
4. **Copy** the key immediately - it won't be shown again
Keep your API key secure and never share it publicly. If your key is compromised, regenerate it immediately.
## Using Your API Key
Include your API key in the `Authorization` header of all requests:
```bash theme={null}
Authorization: Bearer YOUR_API_KEY
```
### Example Request
```bash theme={null}
curl -X GET "https://postsyncer.com/api/v1/workspaces" \
-H "Authorization: Bearer 11|HzNfjB..." \
-H "Content-Type: application/json"
```
### JavaScript Example
```javascript theme={null}
const response = await fetch('https://postsyncer.com/api/v1/workspaces', {
headers: {
'Authorization': 'Bearer 11|HzNfjB...',
'Content-Type': 'application/json'
}
});
```
### Python Example
```python theme={null}
import requests
headers = {
'Authorization': 'Bearer 11|HzNfjB...',
'Content-Type': 'application/json'
}
response = requests.get('https://postsyncer.com/api/v1/workspaces', headers=headers)
```
### Python Example
```python theme={null}
import requests
headers = {
'Authorization': 'Bearer 11|HzNfjB...',
'Content-Type': 'application/json'
}
response = requests.get('https://postsyncer.com/api/v1/workspaces', headers=headers)
```
### PHP Example
```php theme={null}
$headers = [
'Authorization: Bearer 11|HzNfjB...',
'Content-Type: application/json',
];
$ch = curl_init('https://postsyncer.com/api/v1/workspaces');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
$response = curl_exec($ch);
if (curl_errno($ch)) {
echo 'Error: ' . curl_error($ch);
} else {
$data = json_decode($response, true);
print_r($data);
}
curl_close($ch);
```
## Error Responses
If authentication fails, you'll receive a `401 Unauthorized` response:
```json theme={null}
{
"message": "Unauthenticated."
}
```
Common authentication errors:
| Error Code | Description |
| -------------------------------------- | ----------------------------------------- |
| `Unauthenticated.` | Invalid or missing API key |
| `Expired` | API key has expired |
| `Forbidden - Insufficient permissions` | API key doesn't have required permissions |
# Error Handling
Source: https://docs.postsyncer.com/essentials/error-handling
Learn how to handle API errors and responses properly
# Error Handling
The PostSyncer API uses standard HTTP status codes and returns detailed error messages to help you debug issues quickly.
## HTTP Status Codes
| Status Code | Description |
| ----------- | ----------------------------------------- |
| `200` | Success - Request completed successfully |
| `201` | Created - Resource created successfully |
| `400` | Bad Request - Invalid request parameters |
| `401` | Unauthorized - Invalid or missing API key |
| `403` | Forbidden - Insufficient permissions |
| `404` | Not Found - Resource doesn't exist |
| `422` | Unprocessable Entity - Validation errors |
| `429` | Too Many Requests - Rate limit exceeded |
| `500` | Internal Server Error - Server error |
## Error Response Format
All error responses follow this format:
```json theme={null}
{
"message": "Human-readable error message",
"errors": {}
}
```
## Common Error Codes
### Authentication Errors
```json theme={null}
{
"message": "Unauthenticated"
}
```
### Validation Errors
```json theme={null}
{
"message": "The workspace id field is required.",
"errors": {
"workspace_id": [
"The workspace id field is required."
]
}
}
```
### Rate Limit Errors
```json theme={null}
{
"message": "Too Many Attempts."
}
```
## Best Practices
Always check HTTP status codes before processing responses
Implement retry logic for 429 responses with exponential backoff
Log detailed error information for debugging
Validate data before sending to avoid 422 errors
# PostSyncer API Documentation
Source: https://docs.postsyncer.com/introduction
Complete API reference for PostSyncer - automate social media posting across many networks (X, Mastodon, LinkedIn, and more) with our REST API
# PostSyncer API
PostSyncer’s HTTP API lets you create and schedule posts, **manage comments** (list, create, update, delete, hide on platform), manage accounts and labels, and read analytics across your workspaces-using the same product you use in the dashboard.
**New here?** Follow [Quick start](/quickstart). **Connecting a client?** Read [Authentication](/essentials/authentication). **Looking for a method?** Open [API reference](/api-reference/introduction) in the sidebar or use **search** (⌘K / Ctrl+K).
## Where to go next
First request, base URL, and a minimal example in a few minutes.
API keys and Bearer tokens.
What the API covers, then endpoints under **Posts**, **Comments**, **Analytics**, and more.
## MCP (Claude, Cursor, …)
Connect assistants via the **Model Context Protocol**-either add it as a **connector** and authorize in PostSyncer (no token, e.g. Claude custom connectors) or use a **Bearer token** from [API Integrations](https://app.postsyncer.com/dashboard?action=settings\§ion=api-integrations) (scopes match REST). Tools include `list-workspaces`, `create-post`, `list-comments`, analytics, and more.
Endpoint, auth (connector or token), and how MCP maps to API v1.
Connector (OAuth) and Bearer-token setup for Claude, Cursor, and other MCP clients.
## Rate limits
API traffic is limited to **60 requests per minute** per user. Details: [Fair usage & limits](/rate-limits).
## Base URL
```
https://postsyncer.com/api/v1
```
## Supported platforms
# Integrating MCP clients
Source: https://docs.postsyncer.com/mcp/integrating-clients
Connect Claude (connector/OAuth), Claude Desktop, Claude Code, Cursor, and other MCP clients to PostSyncer over Streamable HTTP.
# Client setup
Configure your AI client to connect to the PostSyncer MCP server. There are two ways to authenticate:
* **Connector (OAuth)** — for clients that support it (e.g. Claude custom connectors). No token to create; you authorize in PostSyncer. See [Connect as a connector (OAuth)](#connect-as-a-connector-oauth) below.
* **Bearer token** — for config-file clients (Claude Desktop, Claude Code, Cursor) and scripts. See [Connect with a Bearer token](#connect-with-a-bearer-token) below.
## Connect as a connector (OAuth)
If your client supports MCP connectors (Claude on the web and desktop do), this is the simplest path — there is **no token to create or paste**.
1. In Claude, open **Settings → Connectors → Add custom connector** (the option may be **Add custom connector** / **Connect apps**; it requires custom connectors to be enabled for your plan).
2. Set the **URL** to `https://postsyncer.com/mcp` and add it.
3. Claude sends you to PostSyncer to **authorize**. Sign in if needed, then click **Allow** on the consent screen.
4. Claude is connected. The PostSyncer tools (e.g. `list-workspaces`, `create-post`) are now available, scoped to the workspaces you belong to.
To disconnect later, remove the connector in Claude and/or revoke access from your PostSyncer account.
### One-click connect links
Some clients accept a deep link that pre-fills the PostSyncer server, so you only confirm and authorize. The same buttons are available in-app under **Settings → API Keys → Connect to AI assistants**.
| Client | One-click link |
| ---------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Claude** | [Add PostSyncer to Claude](https://claude.ai/customize/connectors?modal=add-custom-connector\&connectorName=PostSyncer\&connectorUrl=https%3A%2F%2Fpostsyncer.com%2Fmcp) — opens the *Add custom connector* dialog, prefilled |
**ChatGPT** and other clients have no prefill link — add `https://postsyncer.com/mcp` manually in their connector settings.
Behind the scenes this uses standard OAuth 2.0 (discovery + Dynamic Client Registration + PKCE). Any MCP client that implements the connector/OAuth flow can use it against `https://postsyncer.com/mcp` — it will discover the endpoints automatically.
## Connect with a Bearer token
**Create a token first:** open [app.postsyncer.com → Settings → API Integrations](https://app.postsyncer.com/dashboard?action=settings\§ion=api-integrations), click **Create**, and copy the key, see [Authentication](/essentials/authentication).
### Connection details
| Setting | Value |
| ----------------- | ------------------------------------------------------------- |
| **URL** | `https://postsyncer.com/mcp` |
| **Transport** | Streamable HTTP |
| **Authorization** | `Bearer YOUR_TOKEN` (send as the `Authorization` HTTP header) |
Replace `YOUR_TOKEN` with the token from API Integrations (include the `Bearer ` prefix in the header value).
## Claude Desktop
Claude Desktop only loads MCP servers as **local processes** (`command` + `args`). It does **not** use the `url` / `headers` shape from HTTP-native clients. Use the [`mcp-remote`](https://www.npmjs.com/package/mcp-remote) proxy so stdio speaks to PostSyncer’s HTTPS endpoint.
Add PostSyncer under `mcpServers` in your Claude Desktop config:
Edit `~/Library/Application Support/Claude/claude_desktop_config.json`:
```json theme={null}
{
"mcpServers": {
"postsyncer": {
"command": "npx",
"args": [
"mcp-remote",
"https://postsyncer.com/mcp",
"--header",
"Authorization: Bearer YOUR_TOKEN_HERE"
]
}
}
}
```
Edit `%APPDATA%\Claude\claude_desktop_config.json`:
```json theme={null}
{
"mcpServers": {
"postsyncer": {
"command": "npx",
"args": [
"mcp-remote",
"https://postsyncer.com/mcp",
"--header",
"Authorization: Bearer YOUR_TOKEN_HERE"
]
}
}
}
```
On Linux, the file is usually `~/.config/Claude/claude_desktop_config.json`.
**Tips:** If `npx` prompts to install packages, add `-y` as the first entry in `args` (for example `["-y", "mcp-remote", ...]`). Node 18+ must be on your PATH-Claude Desktop uses your system Node. Restart Claude Desktop after saving.
On **Windows** (and some Cursor builds), spaces inside a single `args` string can be mangled; if the header fails, use the [env workaround](https://www.npmjs.com/package/mcp-remote#custom-headers): put `Authorization:Bearer ${POSTSYNCER_TOKEN}` in `args` (no space after the colon) and set `POSTSYNCER_TOKEN` to `Bearer YOUR_TOKEN_HERE` in `env`.
## Claude Code
Claude Code supports **HTTP** MCP servers natively (recommended for PostSyncer). Use the CLI or a `.mcp.json` / `~/.claude.json` entry as in the [Claude Code MCP docs](https://code.claude.com/docs/en/mcp).
**CLI (Bearer token):**
```bash theme={null}
claude mcp add --transport http postsyncer https://postsyncer.com/mcp \
--header "Authorization: Bearer YOUR_TOKEN_HERE"
```
**JSON** (for example project `.mcp.json` or user config)-HTTP servers use `type`, `url`, and `headers`:
```json theme={null}
{
"mcpServers": {
"postsyncer": {
"type": "http",
"url": "https://postsyncer.com/mcp",
"headers": {
"Authorization": "Bearer YOUR_TOKEN_HERE",
"Accept": "application/json"
}
}
}
}
```
Alternatively, you can use the same **`mcp-remote` + `npx`** block as [Claude Desktop](#claude-desktop) if you prefer one config shape everywhere.
## Cursor
1. Open **Cursor Settings → MCP** and add a new server, **or** edit **`~/.cursor/mcp.json`** (global) or **`.cursor/mcp.json`** (project)-see [Cursor’s MCP docs](https://cursor.com/docs/context/mcp).
2. For a remote server, use **`url`** and **`headers`** (Cursor’s supported remote shape). Prefer an env var for the token via [config interpolation](https://cursor.com/docs/context/mcp):
```json theme={null}
{
"mcpServers": {
"postsyncer": {
"url": "https://postsyncer.com/mcp",
"headers": {
"Authorization": "Bearer ${env:POSTSYNCER_TOKEN}",
"Accept": "application/json"
}
}
}
}
```
Set `POSTSYNCER_TOKEN` to your personal access token only (the same secret string as in API Integrations-not the word `Bearer`; the JSON adds `Bearer ` for you). For a one-off test you can use `"Authorization": "Bearer YOUR_TOKEN_HERE"` instead of `${env:...}`.
If your Cursor version does not connect with `url` + `headers`, use the same **`mcp-remote` + `npx`** block as [Claude Desktop](#claude-desktop) ([`mcp-remote` notes](https://www.npmjs.com/package/mcp-remote) that some clients still need this for OAuth or compatibility).
Restart Cursor so the tool list refreshes.
## Other MCP clients
Any MCP-compatible client that supports **Streamable HTTP** (or remote HTTP) and **custom headers** can use PostSyncer:
* **URL:** `https://postsyncer.com/mcp`
* **Transport:** Streamable HTTP
* **Header:** `Authorization: Bearer YOUR_TOKEN_HERE`
* **Recommended:** `Accept: application/json`
Follow that product’s own docs for where to enter the URL and headers.
## Verify the connection
After connecting, ask your assistant to run a read-only tool, for example:
> List my workspaces
If setup is correct, it should call `list-workspaces` and return data. Then try **list my connected accounts** to exercise `list-accounts`.
## Troubleshooting
| Symptom | What to check |
| -------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| 401 / Unauthorized (token) | Token missing, revoked, or `Authorization` not formatted as `Bearer ` plus your token (no typos or extra spaces). Create a new token from [API Integrations](https://app.postsyncer.com/dashboard?action=settings\§ion=api-integrations) if unsure. |
| Connector won't authorize | Make sure you're signed in to PostSyncer in the same browser, then click **Allow** on the consent screen. If the connector was removed, re-add `https://postsyncer.com/mcp` and authorize again. |
| Missing tools (token) | Token abilities: you need `posts` for post, comment, and analytics tools; `workspaces` and `accounts` to discover IDs. Connector authorizations get full access, scoped to your workspaces. |
| Empty or partial tools | Update the client; some older builds mishandle `tools/list` pagination. |
## See also
* [MCP overview](/mcp/overview)
* [Tools reference](/mcp/tools-reference)
* [Authentication](/essentials/authentication)
# MCP overview
Source: https://docs.postsyncer.com/mcp/overview
PostSyncer Model Context Protocol server - same powers as API v1 for AI assistants and autonomous agents.
# PostSyncer MCP
PostSyncer exposes a **Model Context Protocol (MCP)** server so AI assistants and coding agents can manage social content with **tools** (structured actions) instead of ad-hoc HTTP calls.
Two ways to connect: **(1) a connector** — clients that support OAuth (e.g. Claude with custom connectors) add the URL and authorize in PostSyncer, with no token to paste; **(2) a Bearer token** — the same personal access tokens as REST, created in [app.postsyncer.com → Settings → API Integrations](https://app.postsyncer.com/dashboard?action=settings\§ion=api-integrations).
## Endpoint
| Item | Value |
| -------------------- | -------------------------------------------------------- |
| **MCP URL** | `https://postsyncer.com/mcp` |
| **Auth (connector)** | OAuth — authorize in PostSyncer when prompted (no token) |
| **Auth (token)** | `Authorization: Bearer YOUR_TOKEN` |
| **Transport** | Streamable HTTP |
### Choosing an auth method
* **Connector (OAuth)** — easiest for assistants that support it (Claude: **Settings → Connectors → Add custom connector**). You add `https://postsyncer.com/mcp`, get sent to PostSyncer to click **Allow**, and the client is connected — nothing to copy or rotate. Access is scoped to the workspaces you belong to.
* **Bearer token** — best for config-file clients (Claude Desktop, Claude Code, Cursor) and scripts. Create a personal access token and send it in the `Authorization` header.
See [Client setup](/mcp/integrating-clients) for both.
Treat tokens like production passwords. They can publish posts, delete content, and disconnect accounts when matching tools are invoked. Connector access can be revoked anytime from your PostSyncer account.
## What the server does
The **PostSyncer** MCP server (name `PostSyncer`, version `1.0.0`) implements tools that mirror API v1:
* **Workspaces** - discover context and connected accounts.
* **Accounts** - list connected social profiles; disconnect when needed.
* **Labels & campaigns** - organize content.
* **Posts** - create, list, read (by id, public URL, or platform post id), analyze public X/Twitter threads with AI, update, delete, schedule.
* **Comments** - list, sync from platforms, create replies, update, delete, hide.
* **Analytics** - summary, workspace, post, and account metrics; sync post analytics.
Server instructions remind models to start with `list-workspaces` and `list-accounts`, respect workspace boundaries, and only run `delete-*` tools when the user clearly asked.
## Relationship to the REST API
* **Parity**: MCP tools call the same backend rules as `https://postsyncer.com/api/v1`.
* **Choose MCP** when your client supports Streamable HTTP and Bearer headers (see [Client setup](/mcp/integrating-clients)).
* **Choose REST** for servers, webhooks, mobile apps, or languages without MCP.
## More on PostSyncer.com
* [AI agents & MCP](https://postsyncer.com/agents)
* [OpenClaw + PostSyncer](https://postsyncer.com/openclaw)
* [MCP for assistants (Claude, Cursor, …)](https://postsyncer.com/ai-mcp)
## Next steps
Claude Desktop, Claude Code, Cursor, and other Streamable HTTP clients.
Full list of MCP tool names grouped by domain.
# MCP tools reference
Source: https://docs.postsyncer.com/mcp/tools-reference
All PostSyncer MCP tool names: workspaces, accounts, labels, campaigns, posts, media, folders, comments, analytics.
# MCP tools reference
These are the tool names your MCP client shows after it connects to PostSyncer. Each name matches the server implementation; behavior and request bodies align with [API v1](/api-reference/introduction) where the same action exists over REST.
Tools such as delete-post, delete-account, and hide-comment change or remove live data. Only use them when the end user clearly asked for that outcome.
## Workspaces
| Tool | Purpose |
| ----------------- | --------------------------------------------------------- |
| `list-workspaces` | List workspaces (and related context your token can see). |
## Accounts
| Tool | Purpose |
| ---------------- | ---------------------------------------------------------- |
| `list-accounts` | List connected social accounts for selection when posting. |
| `delete-account` | Disconnect/remove a connected account (destructive). |
## Labels
| Tool | Purpose |
| -------------- | --------------- |
| `list-labels` | List labels. |
| `create-label` | Create a label. |
| `get-label` | Get one label. |
| `update-label` | Update a label. |
| `delete-label` | Delete a label. |
## Campaigns
| Tool | Purpose |
| ----------------- | ------------------ |
| `list-campaigns` | List campaigns. |
| `create-campaign` | Create a campaign. |
| `get-campaign` | Get one campaign. |
| `update-campaign` | Update a campaign. |
| `delete-campaign` | Delete a campaign. |
## Posts
| Tool | Purpose |
| -------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `list-posts` | List posts. |
| `create-post` | Create / schedule a post (supports `repeatable_accounts` with `repeatable`, `content[].cover_image` for video covers, and `content[].is_first_comment` / `first_comment_delay` for first comments). |
| `get-post` | Get one post. |
| `get-post-by-url` | Get one post by full public permalink (`post_url`). |
| `get-post-by-platform-post-id` | Get one post by native platform id (`platform_post_id`). |
| `analyze-twitter-post` | Fetch any public X/Twitter URL, load replies, and answer a question with AI (same as `POST /posts/analyze-twitter`). |
| `update-post` | Update a post (supports `repeatable_accounts` with `repeatable`, and the same first-comment fields as create-post). |
| `update-post-auto-plug` | Enable/disable auto plug (engagement thresholds). |
| `update-post-comment-moderation` | Per-post comment moderation settings. |
| `update-post-contact-collection` | CRM contact collection (keyword rules or add\_all). |
| `delete-post` | Delete a post (destructive). |
**Video cover images** (`content[0].cover_image`): **TikTok** - `video_cover_timestamp_ms` only (frame from video). **YouTube, Instagram, Facebook** - `thumbnail` only (custom image upload). See [Create post - platform requirements](/api-reference/posts/create#platform-requirements-for-cover-images).
**First comments**: add a second `content` item with `is_first_comment: true` (and optional `first_comment_delay` in minutes). Schedules a first comment on Instagram, Facebook, LinkedIn, and YouTube only. If the same post also targets TikTok, Threads, or other networks, put links/CTAs you need everywhere in the main caption (or use per-platform content when available). See [Create post - first comments](/api-reference/posts/create#first-comments).
## Media
| Tool | Purpose |
| ----------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `list-media` | Paginated library items; optional `workspace_id`, `folder_id`, `root_only`. |
| `get-media` | Get one item by `media_id`. |
| `upload-media-from-url` | Import URLs into the library (`workspace_id`, `urls`, optional `folder_id`). |
| `upload-media-file` | Upload binary as base64 (`workspace_id`, `file_base64`, `filename`, optional `mime_type`, `folder_id`); same validation as `POST /media/upload/file`. Prefer REST multipart for very large files. |
| `delete-media` | Delete one item by `media_id` (destructive). |
## Folders
| Tool | Purpose |
| --------------- | ------------------------------------------------------------------------ |
| `list-folders` | List media folders; optional `workspace_id`, `parent_id`, `root`. |
| `create-folder` | Create a folder (`workspace_id`, `name`, optional `color`, `parent_id`). |
| `get-folder` | Get one folder by `folder_id`. |
| `update-folder` | Update `folder_id` plus at least one of `name`, `color`, `parent_id`. |
| `delete-folder` | Delete a folder (destructive). |
## Comments
| Tool | Purpose |
| ------------------------------ | --------------------------------------------- |
| `list-comments` | List comments. |
| `sync-comments-from-platforms` | Pull comments from platforms into PostSyncer. |
| `create-comment` | Create a reply / comment. |
| `get-comment` | Get one comment. |
| `update-comment` | Update a comment. |
| `delete-comment` | Delete a comment (destructive). |
| `hide-comment` | Hide a comment on the platform. |
## Analytics
| Tool | Purpose |
| ------------------------- | ----------------------------- |
| `get-analytics-summary` | Summary analytics. |
| `get-analytics-workspace` | Workspace-level analytics. |
| `get-analytics-post` | Post-level analytics. |
| `get-analytics-account` | Account-level analytics. |
| `sync-post-analytics` | Refresh analytics for a post. |
## Typical workflow
A sensible order for agents:
1. `list-workspaces` → choose `workspace_id`.
2. `list-accounts` → choose account IDs for publishing.
3. Optionally `list-labels` / `list-campaigns`.
4. Optionally `list-folders` / `list-media` or `upload-media-from-url` / `upload-media-file` for assets.
5. `create-post`, or comment/analytics tools as needed.
## Related REST docs
Each tool maps to REST routes under [API reference](/api-reference/introduction). Parameters and validation follow the same rules.
# OAuth Delegation
Source: https://docs.postsyncer.com/oauth-delegation
Let your app connect a user’s social account through PostSyncer and receive verified ownership proof
# OAuth Delegation
OAuth delegation lets a third-party app connect a user's social media account
**through PostSyncer** and receive back a **signed proof of ownership** for that
account. It works for any platform PostSyncer supports that uses OAuth
(TikTok, Instagram, X/Twitter, LinkedIn, YouTube, Facebook, Pinterest, Threads).
Instead of integrating each platform's OAuth yourself, you redirect the user to
PostSyncer, PostSyncer runs the platform OAuth, and then redirects the user back
to you with the platform's permanent user id, the handle, and an
**HMAC-SHA256 signature** you can verify with your signing secret.
Delegation does not store a social account on your PostSyncer workspace. The
`platform_id` and `handle` are read directly from the platform's OAuth response
and passed through to you - nothing else is persisted.
Your **API key is a full-access secret** and must never appear in the browser.
You start delegation with a **server-to-server** call from your backend (the API
key goes in the `Authorization` header), and PostSyncer returns an opaque,
single-use `authorize_url`. Only that opaque URL is ever sent to the user's
browser.
## Prerequisites
1. A PostSyncer **API key** ([create one](https://app.postsyncer.com/dashboard?action=settings\§ion=api-integrations)).
2. A **signing secret** for that API key. In **Settings → API Keys**, click the
shield icon on the API key row and choose **Generate signing secret**. Copy
it - it's shown only once. Regenerating invalidates the previous secret.
Keep the signing secret private. It is the shared secret used to sign and verify
ownership proofs. Anyone with it can forge proofs.
## Flow overview
```text theme={null}
Your server ─(1) POST /oauth/delegate/sessions (api_key in header)─▶ PostSyncer
Your server ◀──────────── { authorize_url } (opaque, single-use) ───
│
(2) redirect the user's browser to authorize_url
│
PostSyncer runs platform OAuth (TikTok, X, …)
│
Your app ◀─(3) redirect────── PostSyncer (callback_url + signed params)
│
(4) verify state, expires, sig ──▶ trust platform_id as ownership proof
```
## Step 1 - Create a delegation session (server-to-server)
From **your backend**, call:
```text theme={null}
POST https://postsyncer.com/api/oauth/delegate/sessions
```
with your API key in the `Authorization` header:
`Bearer `. Sent server-to-server only - never exposed
to the browser.
The platform to connect, e.g. `tiktok`, `instagram`, `twitter`, `linkedin`,
`youtube`, `facebook`, `pinterest`, `threads`.
Absolute URL PostSyncer redirects the user back to after authentication.
A random, single-use string you generate. Used for CSRF protection; PostSyncer
echoes it back unchanged.
The response contains an **opaque, single-use** authorize URL (valid for 15 minutes):
```json theme={null}
{
"authorize_url": "https://app.postsyncer.com/oauth/delegate?request=psd_xxx",
"expires_in": 900
}
```
```javascript Node.js theme={null}
import crypto from 'crypto';
const state = crypto.randomBytes(16).toString('hex');
// persist `state` server-side (single use) so you can verify it on return
const res = await fetch('https://postsyncer.com/api/oauth/delegate/sessions', {
method: 'POST',
headers: {
'Authorization': `Bearer ${process.env.POSTSYNCER_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
platform: 'tiktok',
callback_url: 'https://yourapp.com/postsyncer/callback',
state,
}),
});
const { authorize_url } = await res.json();
```
## Step 2 - Redirect the user to the authorize URL
Send the user's browser to the `authorize_url` you received. It carries only an
opaque request token - no API key, platform or callback URL.
```javascript Node.js theme={null}
res.redirect(authorize_url);
```
PostSyncer then runs the platform's normal OAuth consent screen.
## Step 3 - PostSyncer redirects back to your `callback_url`
On success, PostSyncer redirects the user to your `callback_url` with:
The platform that was connected.
The platform's permanent unique identifier for the user (e.g. TikTok `open_id`,
X `user_id`, Instagram `user_id`). This is the value you trust as ownership proof.
The username on that platform. For display only - handles can change.
The same value you sent in step 1.
Unix timestamp, 5 minutes from when the proof was issued.
`HMAC-SHA256` signature of the string
`platform={platform}&platform_id={platform_id}&handle={handle}&state={state}&expires={expires}`
using your signing secret.
```text Example callback theme={null}
https://yourapp.com/postsyncer/callback
?platform=tiktok
&platform_id=_000abc123
&handle=janedoe
&state=9f2b...c4
&expires=1717000000
&sig=4d6c...e1
```
If something fails (or the user cancels), PostSyncer redirects to your
`callback_url` with `error`, `error_description`, and `state` instead.
## Step 4 - Verify the proof
On your server, before trusting `platform_id`:
1. **Verify `state`** matches the value you generated and mark it as used (single use).
2. **Verify `sig`** by recomputing the HMAC over the exact base string.
3. **Verify `expires`** is in the future.
Only if all three pass should you treat `platform_id` as verified ownership.
```javascript Node.js theme={null}
import crypto from 'crypto';
function verifyDelegation(query, signingSecret, expectedState) {
const { platform, platform_id, handle, state, expires, sig } = query;
// 1. CSRF - must match what you issued, and be single-use
if (!state || state !== expectedState) return false;
// 3. Freshness
if (!expires || Number(expires) < Math.floor(Date.now() / 1000)) return false;
// 2. Signature
const base =
`platform=${platform}&platform_id=${platform_id}` +
`&handle=${handle}&state=${state}&expires=${expires}`;
const expected = crypto
.createHmac('sha256', signingSecret)
.update(base)
.digest('hex');
return crypto.timingSafeEqual(Buffer.from(sig), Buffer.from(expected));
}
```
```php PHP theme={null}
## Errors
**Step 1 (session creation)** returns a JSON error to your server with an HTTP
4xx status and a `code`:
No API key was provided in the `Authorization` header. (401)
The API key is invalid or expired. (401)
The API key has no signing secret. Generate one under **Settings → API Keys**. (422)
The `platform` value is not a supported OAuth platform. (422)
**Step 3 (after the user authorizes)** errors are redirected back to your
`callback_url` with an `error`, `error_description` and `state` instead of a
signed proof:
The user cancelled or declined the platform consent screen.
The platform OAuth did not complete.
The authorize URL was already used or expired before the user opened it.
The signature only covers successful proofs. Always treat any response that
carries an `error` parameter as a failed connection.
# Quick Start with PostSyncer API
Source: https://docs.postsyncer.com/quickstart
Get up and running with the PostSyncer API in minutes
## Get Your API Key
Before you can use the PostSyncer API, you need to generate an API key.
Visit [app.postsyncer.com](https://app.postsyncer.com) and sign in to your account.
Go to Settings → API Integrations and click "Create". Copy the key immediately, as it won't be shown again.
## Your First API Request
Let's start by listing your workspaces to verify your API key is working.
```bash theme={null}
curl -X GET "https://postsyncer.com/api/v1/workspaces" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json"
```
```json theme={null}
{
"id": 12,
"name": "abdulmejidshemsuawel",
"slug": "abdulmejidshemsuawel",
"type": "PERSONAL",
"logo": null,
"timezone": "Africa/Addis_Ababa",
"language": "en",
"accounts": [
{
"id": 136,
"platform": "twitter",
"username": "heyabdulmejid",
"name": "Abdul",
"avatar": "https://pbs.twimg.com/profile_images/1878383928995684352/3Xw5HTsk_400x400.jpg"
}
]
}
```
## Create Your First Post
Now let's create a simple text post across your connected accounts.
```bash theme={null}
curl -X POST "https://postsyncer.com/api/v1/posts" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"workspace_id": 12,
"labels": [5],
"content": [
{
"text": "Post Once. Publish everywhere",
"media": [
"https://postsyncer.com/images/og/banner.png?v2"
]
}
],
"schedule_type": "schedule",
"schedule_for": {
"date": "2025-07-04",
"time": "23:00",
"timezone": "Africa/Addis_Ababa"
},
"accounts": [
{
"id": 136,
"settings": {
"for_super_followers_only": false,
"reply_settings": "everyone",
"quote_tweet_id": 1940147562003963969,
"reply": {
"in_reply_to_tweet_id": null
},
"community_id": null,
"share_with_followers": true,
"text": null
}
},
{
"id": 95,
"settings": {
"board_id": 871517034080685367
}
}
]
}'
```
```json theme={null}
{
"id": 123,
"workspace_id": 12,
"labels": [5],
"content": [
{
"text": "Pinterest publish",
"media": [
"https://cdn.pixabay.com/photo/2015/09/16/08/55/online-942406_1280.jpg"
]
},
{
"text": "ACVA",
"media": []
}
],
"schedule_type": "schedule",
"schedule_for": {
"date": "2025-07-04",
"time": "23:00",
"timezone": "Africa/Addis_Ababa"
},
"repeatable": true,
"repeatable_times": 2,
"repeatable_gap": 2,
"repeatable_gap_unit": "days",
"status": "scheduled",
"created_at": "2024-01-15T10:30:00Z",
"accounts": [
{
"id": 136,
"name": "Twitter Account",
"status": "scheduled",
"settings": {
"for_super_followers_only": false,
"reply_settings": "everyone",
"quote_tweet_id": 1940147562003963969,
"reply": {
"in_reply_to_tweet_id": null
},
"community_id": null,
"share_with_followers": true,
"text": null
}
},
{
"id": 95,
"name": "Pinterest Account",
"status": "scheduled",
"settings": {
"board_id": 871517034080685367
}
}
]
}
```
## List Your Posts
Retrieve all your posts to see what you've created.
```bash theme={null}
curl -X GET "https://postsyncer.com/api/v1/posts" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json"
```
## SDK Examples
### JavaScript/Node.js
```javascript theme={null}
const axios = require('axios');
const apiKey = 'YOUR_API_KEY';
const baseURL = 'https://postsyncer.com/api/v1';
const api = axios.create({
baseURL,
headers: {
'Authorization': `Bearer ${apiKey}`,
'Content-Type': 'application/json'
}
});
// Create a post
const createPost = async () => {
try {
const response = await api.post('/posts', {
workspace_id: 12,
labels: [5],
content: [
{
text: 'Hello from Node.js!',
media: []
}
],
schedule_type: 'schedule',
schedule_for: {
date: '2025-07-04',
time: '23:00',
timezone: 'Africa/Addis_Ababa'
},
repeatable: true,
repeatable_times: 2,
repeatable_gap: 2,
repeatable_gap_unit: 'days',
accounts: [
{
id: 136,
settings: {
for_super_followers_only: false,
reply_settings: 'everyone',
quote_tweet_id: null,
reply: {
in_reply_to_tweet_id: null
},
community_id: null,
share_with_followers: true,
text: null
}
}
]
});
console.log('Post created:', response.data);
} catch (error) {
console.error('Error:', error.response.data);
}
};
createPost();
```
### Python
```python theme={null}
import requests
import json
from datetime import datetime, timedelta
api_key = 'YOUR_API_KEY'
base_url = 'https://postsyncer.com/api/v1'
headers = {
'Authorization': f'Bearer {api_key}',
'Content-Type': 'application/json'
}
# Create a post
def create_post():
data = {
'workspace_id': 12,
'labels': [5],
'content': [
{
'text': 'Hello from Python!',
'media': []
}
],
'schedule_type': 'schedule',
'schedule_for': {
'date': '2025-07-04',
'time': '23:00',
'timezone': 'Africa/Addis_Ababa'
},
'accounts': [
{
'id': 136,
'settings': {
'for_super_followers_only': False,
'reply_settings': 'everyone',
'quote_tweet_id': None,
'reply': {
'in_reply_to_tweet_id': None
},
'community_id': None,
'share_with_followers': true,
'text': None
}
}
]
}
response = requests.post(f'{base_url}/posts', headers=headers, json=data)
print('Post created:', response.json())
create_post()
```
### PHP
```php theme={null}
12,
'labels' => [5],
'content' => [
[
'text' => 'Hello from PHP!',
'media' => []
]
],
'schedule_type' => 'schedule',
'schedule_for' => [
'date' => '2025-07-04',
'time' => '23:00',
'timezone' => 'Africa/Addis_Ababa'
],
'accounts' => [
[
'id' => 136,
'settings' => [
'for_super_followers_only' => false,
'reply_settings' => 'everyone',
'quote_tweet_id' => null,
'reply' => [
'in_reply_to_tweet_id' => null
],
'community_id' => null,
'share_with_followers': true,
'text' => null
]
]
]
];
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $base_url . '/posts');
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data));
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$response = curl_exec($ch);
curl_close($ch);
echo 'Post created: ' . $response;
}
createPost();
?>
```
## Next Steps
Now that you've made your first API calls, explore these resources:
Complete documentation for all endpoints and parameters
Learn how to handle API errors and rate limits
# Fair Usage & API Limits
Source: https://docs.postsyncer.com/rate-limits
Understand PostSyncer API rate limits
# Fair Usage & API Limits
PostSyncer does not limit post creation-posting is unlimited. We operate under a fair usage policy to ensure service quality for all users. The only limit we enforce is on API requests (60 per minute).
## API Rate Limit
To keep our service reliable, the PostSyncer API enforces a rate limit on requests. This is the **only** limit imposed by PostSyncer.
### API Limits
| Limit Window | Requests Allowed |
| ------------------ | ---------------- |
| **1-minute fixed** | 60 requests |
**Note:** If you need higher throughput for your integration, please contact our support team.
### Tracking Your Usage
When you approach the rate limit, API responses include these headers so you can monitor your usage:
| Header | Description |
| ----------------------- | ------------------------------------------------------------ |
| `X-RateLimit-Limit` | Total requests allowed in the current window |
| `X-RateLimit-Remaining` | Requests remaining in the current time window |
| `X-RateLimit-Reset` | UNIX timestamp when your window resets (next available slot) |
**Example Response Headers:**
```bash theme={null}
X-RateLimit-Limit: 60
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1714558492
```
### When You Exceed the API Limit
If you exceed 60 requests per minute, the API returns:
**HTTP Status:** `429 Too Many Requests`
**Response Body:**
```json theme={null}
{
"message": "Too Many Attempts."
}
```
Headers will show `X-RateLimit-Remaining: 0` and a future `X-RateLimit-Reset`.
## Summary
| What | Limit |
| ------------------------------ | ------------- |
| **API requests** | 60 per minute |
| **Post creation & publishing** | Unlimited |
## Requesting Higher API Limits
For integrations requiring more than 60 requests per minute, please contact us with:
* Your expected request volume (per minute/hour/day)
* Use case description and critical endpoints
* Contact info
Request higher API rate limits for your integration