Publishing
Give a profile recurring weekly posting slots, add posts to the next free slot with queuedFromProfile, and republish evergreen posts on a schedule.
A queue is a weekly timetable for a profile: Monday 09:00, Wednesday 09:00, Friday 17:30. Instead of picking a time for every post, you add posts to the queue and each one takes the next free slot. Fifty posts become fifty slots without a single scheduledFor.
Queues belong to profiles, not to accounts. A post added to a profile's queue publishes to whichever accounts you list in its platforms.
#Create a queue
POST /v1/queue/slots takes a profile, a name, a timezone and the weekly slots. dayOfWeek runs from 0 (Sunday) to 6 (Saturday) and time is HH:mm in 24-hour format, read in the queue's timezone.
curl -X POST https://api.pozy.ai/v1/queue/slots \
-H "Authorization: Bearer $POZY_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"profileId": "670f9b1d4c2e8a7f3b6d5e10",
"name": "Weekday mornings",
"timezone": "Europe/London",
"slots": [
{ "dayOfWeek": 1, "time": "08:45" },
{ "dayOfWeek": 3, "time": "08:45" },
{ "dayOfWeek": 5, "time": "12:30" }
]
}'const res = await fetch("https://api.pozy.ai/v1/queue/slots", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.POZY_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
profileId: "670f9b1d4c2e8a7f3b6d5e10",
name: "Weekday mornings",
timezone: "Europe/London",
slots: [
{ dayOfWeek: 1, time: "08:45" },
{ dayOfWeek: 3, time: "08:45" },
{ dayOfWeek: 5, time: "12:30" },
],
}),
});
const data = await res.json();
if (!res.ok) throw new Error(data.error);
console.log(data.schedule._id, data.nextSlots);resp = requests.post(
"https://api.pozy.ai/v1/queue/slots",
headers={"Authorization": f"Bearer {os.environ['POZY_API_KEY']}"},
json={
"profileId": "670f9b1d4c2e8a7f3b6d5e10",
"name": "Weekday mornings",
"timezone": "Europe/London",
"slots": [
{"dayOfWeek": 1, "time": "08:45"},
{"dayOfWeek": 3, "time": "08:45"},
{"dayOfWeek": 5, "time": "12:30"},
],
},
timeout=30,
)
queue = resp.json()
print(queue["schedule"]["_id"], queue["nextSlots"]){
"success": true,
"schedule": {
"_id": "6722f1a93b0c4d5e6f7a8b90",
"profileId": "670f9b1d4c2e8a7f3b6d5e10",
"name": "Weekday mornings",
"timezone": "Europe/London",
"slots": [
{ "dayOfWeek": 1, "time": "08:45" },
{ "dayOfWeek": 3, "time": "08:45" },
{ "dayOfWeek": 5, "time": "12:30" }
],
"active": true,
"isDefault": true
},
"nextSlots": ["2026-11-02T08:45:00+00:00", "2026-11-04T08:45:00+00:00"]
}The first queue a profile gets becomes its default. A profile can have several queues, for example one for product news and one for weekend content. Create another with the same profileId and a different name.
#Add a post to the queue
Create the post with queuedFromProfile set to the profile id and no scheduledFor. Pozy locks the next free slot for it, so the next post you add takes the slot after that.
curl -X POST https://api.pozy.ai/v1/posts \
-H "Authorization: Bearer $POZY_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"content": "Tip of the week: export any report as CSV from the share menu.",
"platforms": [
{ "platform": "linkedin", "accountId": "6710c3e2a9f14b0d8e5c7a21" },
{ "platform": "twitter", "accountId": "6710c3e2a9f14b0d8e5c7a23" }
],
"queuedFromProfile": "670f9b1d4c2e8a7f3b6d5e10"
}'const res = await fetch("https://api.pozy.ai/v1/posts", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.POZY_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
content: "Tip of the week: export any report as CSV from the share menu.",
platforms: [
{ platform: "linkedin", accountId: "6710c3e2a9f14b0d8e5c7a21" },
{ platform: "twitter", accountId: "6710c3e2a9f14b0d8e5c7a23" },
],
queuedFromProfile: "670f9b1d4c2e8a7f3b6d5e10",
}),
});
const data = await res.json();
if (!res.ok) throw new Error(data.error);
console.log(data.post.scheduledFor, data.post.queueId);resp = requests.post(
"https://api.pozy.ai/v1/posts",
headers={"Authorization": f"Bearer {os.environ['POZY_API_KEY']}"},
json={
"content": "Tip of the week: export any report as CSV from the share menu.",
"platforms": [
{"platform": "linkedin", "accountId": "6710c3e2a9f14b0d8e5c7a21"},
{"platform": "twitter", "accountId": "6710c3e2a9f14b0d8e5c7a23"},
],
"queuedFromProfile": "670f9b1d4c2e8a7f3b6d5e10",
},
timeout=30,
)
post = resp.json()["post"]
print(post["scheduledFor"], post["queueId"])The post comes back scheduled, with the slot's time in scheduledFor (UTC) and the queue in queueId. Add queueId next to queuedFromProfile to target a queue other than the default one.
GET /v1/queue/next-slot is for showing people when a post would go out. If you read it and then send that time as scheduledFor, two posts created at the same moment can land on the same slot. Send queuedFromProfile and let Pozy assign the slot.
#Preview upcoming slots
| Call | Returns |
|---|---|
GET /v1/queue/next-slot?profileId=... |
The next free slot, its timezone and the queue it belongs to. 404 when the profile has no queue or no free slot. |
GET /v1/queue/preview?profileId=...&count=10 |
The next count slot times (up to 100) as ISO date-times. |
GET /v1/queue/slots?profileId=... |
The default queue with its slots and next times. Add all=true for every queue of the profile, or queueId for one. |
#Change or remove a queue
PUT /v1/queue/slots replaces a queue's slots, name, timezone or active state. Without queueId it changes the default queue; with queueId it changes that one.
| Field | What it does |
|---|---|
slots, timezone |
Required. The full new timetable. |
name |
Rename the queue. |
active |
false pauses the queue. next-slot then returns 400 and new posts cannot join it. |
setAsDefault |
true makes this queue the profile's default. |
reshuffleExisting |
true moves posts already waiting in the queue onto the new timetable. The response reports reshuffledCount. |
DELETE /v1/queue/slots?profileId=...&queueId=... deletes one queue. Leave out queueId to delete every queue of the profile. Deleting the default queue promotes another one to default.
#Common errors
| Error | Cause | Fix |
|---|---|---|
400 on create or update |
A slot with time not in HH:mm, a dayOfWeek outside 0 to 6, or an unknown timezone. |
Fix the slot or use a canonical IANA name. |
404 on next-slot |
The profile has no queue, or every slot is taken. | Create a queue or add slots. |
409 queue_slot_conflict on PUT /v1/posts/{postId} |
You moved a queued post onto a time another post in the same queue already holds. | Pick another time, drop scheduledFor, or send queueId: null to schedule it outside the queue. |
#Recycle evergreen posts
Recycling republishes a post on a repeating schedule, which suits evergreen content such as guides, FAQs or a pinned offer. Set recycling when you create or update the post:
{
"content": "New here? Our 5-minute setup guide covers everything.",
"platforms": [{ "platform": "linkedin", "accountId": "6710c3e2a9f14b0d8e5c7a21" }],
"scheduledFor": "2026-11-02T09:00:00",
"timezone": "Europe/London",
"recycling": {
"enabled": true,
"gap": 2,
"gapFreq": "month",
"expireCount": 6,
"contentVariations": [
"New here? Our 5-minute setup guide covers everything.",
"Setting up a new workspace? Start with the 5-minute guide.",
"The fastest way to get started is still the 5-minute setup guide."
]
}
}| Field | What it does |
|---|---|
enabled |
Turns recycling on or off. Defaults to true when you send recycling. |
gap and gapFreq |
How long to wait between copies: gap weeks or months (gapFreq is week or month, default month). |
startDate |
When the first copy may go out. |
expireCount |
Stop after this many copies. null for no limit. |
expireDate |
Stop after this date. null for no limit. |
contentVariations |
Texts to rotate through, so followers do not see the same words every time. |
Each copy is a new post with its own id. It records the original in recycledFromPostId, fires post.recycled, then follows the normal lifecycle with its own post.scheduled and publish events. The original post shows progress in recycling.recycleCount, recycling.nextRecycleAt and recycling.lastRecycledAt.
Recycling is the one thing you can still change on a published post: send a new recycling object with PUT /v1/posts/{postId}, or "enabled": false to stop it.
#Endpoints
| Endpoint | What it does |
|---|---|
GET /v1/queue/slots |
List schedules |
POST /v1/queue/slots |
Create schedule |
PUT /v1/queue/slots |
Update schedule |
DELETE /v1/queue/slots |
Delete schedule |
GET /v1/queue/preview |
Preview upcoming slots |
GET /v1/queue/next-slot |
Get next available slot |
#Webhook events
| Event | When it fires |
|---|---|
post.scheduled |
Post scheduled. |
post.recycled |
Post recycled. |