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.
Any post set can also be opened in Pallyy's calendar with a link carrying its social set in the set query parameter and its id in post, handy for linking from your own tools back to the app:
https://app.pallyy.com/dashboard/scheduling/calendar/month?set=<socialSetId>&post=<postSetId>
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 } |
approval | Yes | The approval state. See below |
Approvals
approval places the set in Pallyy's approval workflow, the same one your team uses in the app:
| Value | Meaning |
|---|---|
NONE | No approval needed |
PENDING | Waiting for a team member to approve it in Pallyy |
EXTERNAL_PENDING | Waiting for an external reviewer to approve it via a shared calendar |
APPROVED | Approved |
DENIED | Denied |
The approval state is independent of status: it tracks review, while status decides whether the set publishes. Any value other than NONE requires approvals on your plan; otherwise the request fails with a 403.
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, and tags (see below) 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.
Media tags
tags places account tags on an image, the way Instagram lets you tag people in a photo. Each tag carries the handle to tag and where on the image it sits:
{
"mediaLibraryId": "665f1c2ab7e2a4d1f0a1b2c5",
"tags": [
{ "x": 0.25, "y": 0.4, "text": "pallyy" }
]
}
| Field | Required | Meaning |
|---|---|---|
x | Yes | Horizontal position, from 0 (left edge) to 1 (right edge) |
y | Yes | Vertical position, from 0 (top edge) to 1 (bottom edge) |
text | Yes | The handle to tag, without the @ |
Positions are fractions of the image size, so the same tag lands on the same spot regardless of the image's dimensions.
Network-specific fields
| Network | Fields |
|---|---|
FACEBOOK | type (required): TEXT, IMAGE, VIDEO, CAROUSEL, MULTI_IMAGE, REEL, or STORY. richCaption (required, see below). Optional videoCoverImageSeconds |
INSTAGRAM | type (required): IMAGE, CAROUSEL, REEL, or STORY. collaborators (required, pass [] for none). Optional 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 |
richCaption on Facebook and LinkedIn is the caption as an array of text segments. Pass the caption as a single-item array: ["Your caption"].
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), repeats (post-set:repeats_not_available), and approvals 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 |
approval | New approval state, see approvals. Changing it requires approvals on your plan |
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 | NONE, PENDING, EXTERNAL_PENDING, APPROVED, or DENIED, see approvals |
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.