A post set is one slot on your schedule: a date, a status, and one post per social network it targets. Scheduling the same content to Instagram and X is one post set with two posts, each carrying its own caption, media, and network-specific fields.
A post set moves through four statuses:
| Status | Meaning |
|---|---|
DRAFT | Parked, will not publish until scheduled |
SCHEDULED | Will publish at scheduledAt |
PUBLISHING | Publishing right now, read-only |
PUBLISHED | Done, read-only. Each post carries a result |
Creating or updating a set as DRAFT needs the post-sets:write scope. Anything that leaves a set SCHEDULED additionally needs post-sets:publish, see authentication.
List post sets
GET /v1/post-sets
| Parameter | Required | Meaning |
|---|---|---|
socialSetId | Yes | The social set to list |
page, size | No | See pagination |
filters[startDate] | No | ISO 8601, sets scheduled at or after this time |
filters[endDate] | No | ISO 8601, sets scheduled before this time |
filters[status][] | No | One or more of DRAFT, SCHEDULED, PUBLISHING, PUBLISHED |
filters[socialNetworks][] | No | Only sets containing posts for these networks, one or more of INSTAGRAM, FACEBOOK, TWITTER, LINKEDIN, GMB, PINTEREST, TIKTOK, YOUTUBE, THREADS, BLUESKY |
filters[approval][] | No | One or more of NONE, PENDING, EXTERNAL_PENDING, APPROVED, DENIED |
filters[labelsId][] | No | Only sets carrying these label ids |
filters[search] | No | Caption text search, at least 3 characters |
sort[scheduledAt], sort[createdAt] | No | ASC or DESC |
curl "https://app.pallyy.com/api/v1/post-sets?socialSetId=SET_ID&filters[status][]=SCHEDULED&sort[scheduledAt]=ASC" \
-H "Authorization: Bearer pallyy_your_api_key"
Get a post set
GET /v1/post-sets/{id}
Returns the full post set, or a 404 if it does not exist or is outside the key's access.
Create a post set
POST /v1/post-sets
Requires post-sets:write, plus post-sets:publish when status is SCHEDULED. Returns the created set with a 201.
| Field | Required | Meaning |
|---|---|---|
socialSetId | Yes | The social set to create in |
scheduledAt | Yes | ISO 8601 date-time the set publishes at |
status | Yes | DRAFT or SCHEDULED |
posts | Yes | 1 to 11 post objects, one per targeted network |
repeat | No | { "interval": "DAY" | "WEEK" | "MONTH" | "YEAR", "occurrences": 2-50 } |
Post sets created via the API never require approval, carry no labels, and are never locked.
Repeats
The repeat option turns one request into a repeating series. The first occurrence publishes at scheduledAt, and each following occurrence is spaced one interval apart (DAY, WEEK, MONTH, or YEAR), for a total of occurrences post sets, between 2 and 50. The series is materialized immediately: one post set per occurrence, all sharing the same seriesId, and every occurrence counts against your plan's monthly post limit.
Once a set belongs to a series, its repeat rule can no longer be changed via the API, matching the in-app editor: updating it returns a 400 with post-set:repeat_not_updatable. You can still edit each occurrence individually, since each one is a regular post set. Repeats are only available on plans that include them, otherwise the request fails with post-set:repeats_not_available.
Post objects
Every post shares these fields:
| Field | Required | Meaning |
|---|---|---|
accountType | Yes | The network to publish to, see the account type values on social sets |
caption | Yes | The caption text. Each network's own length limit applies |
media | Yes | Media items, may be empty where the network allows text-only posts |
Media items reference your media library by id, with optional per-item metadata:
{
"mediaLibraryId": "665f1c2ab7e2a4d1f0a1b2c5",
"altText": "A latte on a wooden table"
}
Besides altText, items accept title, description, link, tags, and products where the network supports them. The referenced media must belong to the same social set as the post set. To get media into the library via the API, see media uploads.
Network-specific fields
| Network | Fields |
|---|---|
FACEBOOK | type (required): TEXT, IMAGE, VIDEO, CAROUSEL, MULTI_IMAGE, REEL, or STORY. richCaption (required, see below). Optional location (see below), videoCoverImageSeconds |
INSTAGRAM | type (required): IMAGE, CAROUSEL, REEL, or STORY. collaborators (required, pass [] for none). Optional location (see below), firstComment, audio, audioName, videoCoverImage (seconds into the first video) |
LINKEDIN_PAGE, LINKEDIN_PERSONAL | richCaption (required, see below) |
GMB | ctaType (required): NONE, SIGN_UP, CALL, SHOP, LEARN_MORE, ORDER, or BOOK. Optional ctaUrl |
PINTEREST | title (required), website (required), optional board as { "id", "name" } |
YOUTUBE | title (required), privacy (required): PUBLIC, PRIVATE, or UNLISTED |
TWITTER, TIKTOK, THREADS, BLUESKY | Caption and media only |
Rich captions and mentions
richCaption on Facebook and LinkedIn is the caption split into 1 to 32 segments, a mix of plain strings and mention objects, so pages and profiles can be tagged inline. Without mentions, pass the caption as a single-item array: ["Your caption"].
[
"Grabbing a coffee with ",
{ "mention": { "id": "106039436102339", "index": "0", "value": "Pallyy", "denotationChar": "@" } },
" this morning."
]
Each mention object wraps its fields under a mention key:
| Field | Meaning |
|---|---|
id | The platform's id for the tagged page or profile |
index | Zero-based position of the mention among the caption's mentions, as a numeric string |
value | The display text, without the denotation character |
denotationChar | The character prefixing the mention, usually @ |
The location object
Facebook and Instagram posts can tag a place by passing location:
{
"id": "108424279189115",
"name": "New York, New York",
"location": {
"city": "New York",
"country": "United States",
"state": "NY",
"zip": "10001"
}
}
| Field | Required | Meaning |
|---|---|---|
id | Yes | The Facebook place id |
name | Yes | The place's display name |
location | Yes | The place's address, all fields optional: city, country, state, zip |
Place ids come from Facebook's places database, the same one Pallyy's composer searches when you tag a location.
Example
curl https://app.pallyy.com/api/v1/post-sets \
-X POST \
-H "Authorization: Bearer pallyy_your_api_key" \
-H "Content-Type: application/json" \
-d '{
"socialSetId": "665f1c2ab7e2a4d1f0a1b2c3",
"scheduledAt": "2026-09-01T09:30:00.000Z",
"status": "SCHEDULED",
"posts": [
{
"accountType": "INSTAGRAM",
"type": "IMAGE",
"caption": "Fresh from the roastery.",
"collaborators": [],
"media": [{ "mediaLibraryId": "665f1c2ab7e2a4d1f0a1b2c5", "altText": "A latte on a wooden table" }]
},
{
"accountType": "TWITTER",
"caption": "Fresh from the roastery.",
"media": [{ "mediaLibraryId": "665f1c2ab7e2a4d1f0a1b2c5" }]
}
]
}'
Validation and plan limits
Drafts are validated loosely, so you can park incomplete content. Scheduling validates every post against its network's rules, and failures return post-set:invalid_posts with one entry per problem, see errors.
Your plan is enforced on every write: the monthly post limit (post-set:monthly_posts_exceeded), carousels (post-set:carousels_not_available), videos (post-set:videos_not_available), and repeats (post-set:repeats_not_available) all return a 403 when unavailable.
Update a post set
PATCH /v1/post-sets/{id}
Requires post-sets:write, plus post-sets:publish when the set ends up SCHEDULED, whether because you set the status or because it already was. The update is partial: omitted fields keep their saved values.
| Field | Meaning |
|---|---|
scheduledAt | New publish time |
status | DRAFT or SCHEDULED |
repeat | Turn the set into a repeating series. Not allowed on a set that already belongs to one |
posts | Replaces the set's posts entirely, see below |
When posts is provided, it describes the complete new list:
- A post with an
idkeeps that existing post's identity. ItsaccountTypecannot change. - A post without an
idis created new. - Existing posts that are not referenced are removed.
- Each post's
mediaworks the same way:{ "id": "..." }keeps an existing media item of the set (it may move between posts),{ "mediaLibraryId": "..." }adds a new one from the library, and unreferenced media are removed. - Each post or media id may be referenced at most once, otherwise the request fails with
post-set:duplicate_references.
Sets that are PUBLISHING or PUBLISHED are read-only and return a 412 with post-set:read_only.
The post set object
| Field | Type | Meaning |
|---|---|---|
id | string | The post set id |
socialSetId | string | The social set it belongs to |
scheduledAt | string | ISO 8601 publish time |
status | string | DRAFT, SCHEDULED, PUBLISHING, or PUBLISHED |
posts | array | The per-network posts, including their media and network-specific fields |
seriesId | string | Present when the set belongs to a repeating series |
repeat | object | The repeat rule, when the set started a series |
labelsId | array | Label ids applied in Pallyy |
approval | string | The approval state within Pallyy's approval workflow |
isLocked | boolean | Whether the set is locked in Pallyy |
createdAt, updatedAt | string | ISO 8601 timestamps |
Once a set has published, each post carries a result:
{
"result": {
"status": "POSTED",
"providerId": "18123456789",
"permalink": "https://www.instagram.com/p/..."
}
}
Failed posts have "status": "FAILED" with an optional message and one of these reason codes:
| Reason | Meaning |
|---|---|
ACCOUNT_CONNECTION_EXPIRED | The account's connection expired and needs to be reauthorized in Pallyy |
ACCOUNT_RESTRICTED | The network has restricted the account |
ACCOUNT_RESTRICTED_SPAM | The network has restricted the account for spam |
ACCOUNT_SUSPENDED | The network has suspended the account |
ACTION_BLOCKED | The network blocked the publishing action |
CAPTION_TOO_LONG | The caption exceeds the network's length limit |
UNKNOWN_MEDIA_ERROR | The network rejected the post's media |
UNKNOWN | The publish failed for another reason, check message |
More in Endpoints
Questions about the API? Email us at hey@pallyy.com and we'll get back to you.