Publishing
Save posts as drafts, hold them for review with requiresApproval, and approve or reject them with one call before anything goes live.
Not every post should publish the moment it is written. A draft is saved and goes nowhere until you schedule it. A post that requires approval has a time but waits in pending_approval until someone with the right access approves it. Both are ordinary posts, so they show up in GET /v1/posts and can be edited like any other.
#Save a draft
Send isDraft: true, or simply leave out every scheduling field. platforms is optional for drafts, so you can save the text and media before anyone has chosen the accounts.
curl -X POST https://api.pozy.ai/v1/posts \
-H "Authorization: Bearer $POZY_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"content": "Draft: holiday opening hours. Confirm with the store team.",
"isDraft": true,
"tags": ["holiday", "needs-review"]
}'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: "Draft: holiday opening hours. Confirm with the store team.",
isDraft: true,
tags: ["holiday", "needs-review"],
}),
});
const data = await res.json();
if (!res.ok) throw new Error(data.error);
console.log(data.post._id, data.post.status); // "draft"resp = requests.post(
"https://api.pozy.ai/v1/posts",
headers={"Authorization": f"Bearer {os.environ['POZY_API_KEY']}"},
json={
"content": "Draft: holiday opening hours. Confirm with the store team.",
"isDraft": True,
"tags": ["holiday", "needs-review"],
},
timeout=30,
)
print(resp.json()["post"]["status"]) # draftisDraft: true wins over publishNow, scheduledFor and queuedFromProfile, so a draft can never publish by accident. Drafts skip most platform checks, which is why an incomplete post saves without errors. The checks run when you schedule it.
List drafts with GET /v1/posts?status=draft, optionally filtered by profileId, tags or search.
#Turn a draft into a scheduled post
Update the post with isDraft: false and one of scheduledFor, publishNow or queuedFromProfile. Add platforms if the draft had none.
curl -X PUT https://api.pozy.ai/v1/posts/6721a8c04e9b2f1d7c3a5e88 \
-H "Authorization: Bearer $POZY_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"isDraft": false,
"content": "Holiday hours: open 10:00 to 16:00 on 24 and 31 December.",
"platforms": [
{ "platform": "facebook", "accountId": "6710c3e2a9f14b0d8e5c7a25" },
{ "platform": "googlebusiness", "accountId": "6710c3e2a9f14b0d8e5c7a26" }
],
"scheduledFor": "2026-12-20T09:00:00",
"timezone": "Europe/Madrid"
}'const res = await fetch("https://api.pozy.ai/v1/posts/6721a8c04e9b2f1d7c3a5e88", {
method: "PUT",
headers: {
Authorization: `Bearer ${process.env.POZY_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
isDraft: false,
content: "Holiday hours: open 10:00 to 16:00 on 24 and 31 December.",
platforms: [
{ platform: "facebook", accountId: "6710c3e2a9f14b0d8e5c7a25" },
{ platform: "googlebusiness", accountId: "6710c3e2a9f14b0d8e5c7a26" },
],
scheduledFor: "2026-12-20T09:00:00",
timezone: "Europe/Madrid",
}),
});
if (!res.ok) throw new Error((await res.json()).error);requests.put(
"https://api.pozy.ai/v1/posts/6721a8c04e9b2f1d7c3a5e88",
headers={"Authorization": f"Bearer {os.environ['POZY_API_KEY']}"},
json={
"isDraft": False,
"content": "Holiday hours: open 10:00 to 16:00 on 24 and 31 December.",
"platforms": [
{"platform": "facebook", "accountId": "6710c3e2a9f14b0d8e5c7a25"},
{"platform": "googlebusiness", "accountId": "6710c3e2a9f14b0d8e5c7a26"},
],
"scheduledFor": "2026-12-20T09:00:00",
"timezone": "Europe/Madrid",
},
timeout=30,
).raise_for_status()If you send a time without isDraft: false, the update succeeds but the post stays a draft. This is deliberate: an editor changing the suggested time of a draft should not publish it.
Scheduling runs the full platform validation (media rules, required fields, text limits). A post that fails it returns 400 and stays a draft. Run validation first if you want to show problems before the person presses Schedule.
#Hold a post for approval
Add requiresApproval: true and a time. The post is saved as pending_approval and will not publish, even when its time comes, until it is approved.
{
"content": "Flash sale: 30% off annual plans until Sunday.",
"platforms": [
{ "platform": "instagram", "accountId": "6710c3e2a9f14b0d8e5c7a22" },
{ "platform": "linkedin", "accountId": "6710c3e2a9f14b0d8e5c7a21" }
],
"mediaItems": [{ "type": "image", "url": "https://pozy.ai/media/temp/1730289600_z1x5_sale.jpg" }],
"scheduledFor": "2026-11-20T10:00:00",
"timezone": "Europe/Madrid",
"requiresApproval": true
}A common setup: writers and AI agents create posts with keys that always set requiresApproval, and a person reviews the list each morning with GET /v1/posts?status=pending_approval.
#Approve or reject
POST /v1/posts/{postId}/review takes one field, decision, which is approve or reject:
curl -X POST https://api.pozy.ai/v1/posts/6721a8c04e9b2f1d7c3a5e88/review \
-H "Authorization: Bearer $POZY_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "decision": "approve" }'const res = await fetch("https://api.pozy.ai/v1/posts/6721a8c04e9b2f1d7c3a5e88/review", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.POZY_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({ decision: "approve" }),
});
const data = await res.json();
if (!res.ok) throw new Error(data.error);
console.log(data.post.status); // "scheduled"resp = requests.post(
"https://api.pozy.ai/v1/posts/6721a8c04e9b2f1d7c3a5e88/review",
headers={"Authorization": f"Bearer {os.environ['POZY_API_KEY']}"},
json={"decision": "approve"},
timeout=30,
)
print(resp.json()["post"]["status"]) # scheduled| Decision | Result |
|---|---|
approve |
The post becomes scheduled for its scheduledFor and post.scheduled fires. If that time has already passed, it publishes within a couple of minutes. |
reject |
The post goes back to draft. Edit it and send it for approval again, or delete it. |
Reviewing a post that is not pending_approval returns 409. Approval does not fire post.scheduled at creation time, only when the post is approved.
#Who can create, approve and publish
| Caller | Create drafts and posts | Approve or reject | Notes |
|---|---|---|---|
| Workspace owner, admin or member | Yes | Yes | From the dashboard or the MCP server. |
| Workspace viewer | No | No | Read-only. |
| Read-write API key | Yes | Yes | Restrict it to some profiles with a profile-scoped key. |
| Read-only API key | No | No | Writes return 403 with code: "read_only_key". |
To keep approval with people only, give automated writers a key that is scoped to the right profiles and always send requiresApproval: true from your own code. Keys cannot be forced to send it, so treat it as a rule in your integration.
#Platform drafts are different
Two platforms have a draft of their own, separate from Pozy drafts:
- TikTok:
tiktokSettings.draft: trueuploads the video or photos to the creator's TikTok inbox, where they finish and post it in the app. - Facebook:
facebookSettings.draft: truecreates an unpublished Page post.
For Pozy these are published posts: the upload succeeded. See TikTok and Facebook.
#Endpoints
| Endpoint | What it does |
|---|---|
POST /v1/posts |
Create post |
PUT /v1/posts/{postId} |
Update post |
POST /v1/posts/{postId}/review |
Approve or reject a post |
GET /v1/posts |
List posts |
DELETE /v1/posts/{postId} |
Delete post |
#Webhook events
| Event | When it fires |
|---|---|
post.scheduled |
Post scheduled. |