Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,14 @@ All notable changes to the Postiz CLI will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [Unreleased]

### Added
- `posts:settings` - Update a post's provider settings via `PUT /public/v1/posts/:id/settings` (merged — only the keys you pass change; unpublished DRAFT/QUEUE posts only).

### Changed
- `posts:list` responses now include each post's current `settings`.

## [1.0.0] - 2026-02-13

### Added
Expand Down
12 changes: 11 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -183,7 +183,7 @@ postiz posts:list --startDate "2024-01-01T00:00:00Z" --endDate "2024-12-31T23:59
postiz posts:list --customer "customer-id"
```

Defaults to last 30 days to next 30 days if dates not specified.
Defaults to last 30 days to next 30 days if dates not specified. Each returned post includes its current `settings` (returned as a JSON string — `JSON.parse` it). The intended workflow is `posts:list` (read current settings) → `posts:settings` (patch them).

**Delete post**
```bash
Expand All @@ -198,6 +198,14 @@ postiz posts:status <post-id> --status schedule

Move a scheduled post back to a draft, or promote a draft into the publishing queue. Switching to `draft` also terminates any workflow that's already running for the post, so it won't publish. Switching to `schedule` queues the post for publishing at its stored date.

**Update a post's provider-specific settings**
```bash
postiz posts:settings <post-id> --settings '{"content_posting_method":"DIRECT_POST"}'
postiz posts:settings <post-id> --settings '{"subreddit":[{"value":{"subreddit":"/r/selfhosted","title":"My title","type":"self","is_flair_required":true}}]}'
```

Patches a post's settings server-side. The backend **merges** the object — only the keys you pass change, everything else is preserved — so pass a partial object, not the full settings blob. Only **DRAFT/QUEUE** (unpublished) posts can be updated; published posts are rejected. Pass the **main post id**, not a comment id. Do **not** include `__type` — the backend adds it automatically from the integration.

---

### Analytics
Expand Down Expand Up @@ -560,6 +568,7 @@ The CLI interacts with these Postiz API endpoints:
| `/public/v1/posts` | POST | Create a post |
| `/public/v1/posts` | GET | List posts |
| `/public/v1/posts/:id` | DELETE | Delete a post |
| `/public/v1/posts/:id/settings` | PUT | Update a post's provider settings (merged; unpublished only) |
| `/public/v1/posts/:id/missing` | GET | Get missing content from provider |
| `/public/v1/posts/:id/release-id` | PUT | Update release ID for a post |
| `/public/v1/integrations` | GET | List integrations (optional `?group=` filter) |
Expand Down Expand Up @@ -679,6 +688,7 @@ postiz posts:list # List posts
postiz posts:delete <id> # Delete post
postiz posts:status <id> --status draft # Move to draft (stops workflow)
postiz posts:status <id> --status schedule # Queue draft for publishing
postiz posts:settings <id> --settings '{}' # Patch a post's settings (merged; DRAFT/QUEUE only)
postiz upload <file> # Upload media

# Analytics
Expand Down
10 changes: 10 additions & 0 deletions SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,8 @@ postiz posts:create --json post.json

```bash
# List posts (defaults to last 30 days to next 30 days)
# Each returned post includes its current `settings` (as a JSON string — JSON.parse it).
# Workflow: run posts:list to read a post's current settings, then posts:settings to patch them.
postiz posts:list

# List posts in date range
Expand All @@ -205,6 +207,12 @@ postiz posts:delete <post-id>
# Change post status (draft ↔ schedule)
postiz posts:status <post-id> --status draft # Move back to draft, terminates any running publish workflow
postiz posts:status <post-id> --status schedule # Promote a draft into the publishing queue (uses the post's stored date)

# Update a post's provider-specific settings (merged — only the keys you pass change)
# Only DRAFT/QUEUE (unpublished) posts can be updated. Pass the MAIN post id, not a comment id.
# Do NOT include __type — the backend adds it automatically from the integration.
postiz posts:settings <post-id> --settings '{"content_posting_method":"DIRECT_POST"}' # Switch a TikTok draft to direct publishing
postiz posts:settings <post-id> --settings '{"subreddit":[{"value":{"subreddit":"/r/selfhosted","title":"My title","type":"self","is_flair_required":true}}]}' # Set a Reddit post's subreddit
```

### Analytics
Expand Down Expand Up @@ -749,6 +757,7 @@ https://clawhub.ai/nevo-david/agent-media
9. **Required settings** - Some platforms require specific settings (Reddit needs title, YouTube needs title)
10. **Media MIME types** - CLI auto-detects from file extension, ensure correct extension
11. **Analytics returns `{"missing": true}`** - The post was published but the platform didn't return a post ID. Run `posts:missing <post-id>` to get available content, then `posts:connect <post-id> --release-id "<id>"` to link it. Analytics will work after connecting.
12. **`posts:settings` merges** - Only the keys you pass change; everything else on the post is preserved, so pass a partial object, not the full settings blob. Only **DRAFT/QUEUE** (unpublished) posts can be updated — published posts are rejected. Pass the **main post id**, not a comment id. Never include `__type` — the backend adds it automatically from the integration.

---

Expand Down Expand Up @@ -781,6 +790,7 @@ postiz posts:list # List posts
postiz posts:delete <id> # Delete post
postiz posts:status <id> --status draft # Move to draft (stops workflow)
postiz posts:status <id> --status schedule # Queue draft for publishing
postiz posts:settings <id> --settings '{}' # Patch a post's settings (merged; DRAFT/QUEUE only)
postiz upload <file> # Upload media

# Analytics
Expand Down
7 changes: 7 additions & 0 deletions src/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,13 @@ export class PostizAPI {
});
}

async updatePostSettings(postId: string, settings: Record<string, any>) {
return this.request(`/public/v1/posts/${postId}/settings`, {
method: 'PUT',
body: JSON.stringify({ settings }),
});
}

async getAnalytics(integrationId: string, date: string) {
return this.request(`/public/v1/analytics/${integrationId}?date=${encodeURIComponent(date)}`, {
method: 'GET',
Expand Down
33 changes: 33 additions & 0 deletions src/commands/posts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,39 @@ export async function changePostStatus(args: any) {
}
}

export async function updatePostSettings(args: any) {
const config = getConfig();
const api = new PostizAPI(config);

if (!args.id) {
console.error('❌ Post ID is required');
process.exit(1);
}

if (!args.settings) {
console.error('❌ --settings is required');
process.exit(1);
}

let settings: any;
try {
settings = JSON.parse(args.settings);
} catch (error: any) {
console.error('❌ Failed to parse settings JSON:', error.message);
process.exit(1);
}

try {
const result = await api.updatePostSettings(args.id, settings);
console.log(`✅ Post ${args.id} settings updated`);
console.log(JSON.stringify(result, null, 2));
return result;
} catch (error: any) {
console.error('❌ Failed to update post settings:', error.message);
process.exit(1);
}
}

export async function deletePost(args: any) {
const config = getConfig();
const api = new PostizAPI(config);
Expand Down
27 changes: 26 additions & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import yargs from 'yargs';
import { hideBin } from 'yargs/helpers';
import { createPost, listPosts, deletePost, getMissingContent, connectPost, changePostStatus } from './commands/posts';
import { createPost, listPosts, deletePost, getMissingContent, connectPost, changePostStatus, updatePostSettings } from './commands/posts';
import { listIntegrations, listGroups, getIntegrationSettings, triggerIntegrationTool } from './commands/integrations';
import { getAnalytics, getPostAnalytics } from './commands/analytics';
import { uploadFile } from './commands/upload';
Expand Down Expand Up @@ -202,6 +202,31 @@ yargs(hideBin(process.argv))
},
changePostStatus as any
)
.command(
'posts:settings <id>',
'Update a post\'s provider-specific settings (merged; only unpublished draft/scheduled posts)',
(yargs: Argv) => {
return yargs
.positional('id', {
describe: 'Post ID',
type: 'string',
})
.option('settings', {
describe: 'Partial settings as a JSON string — only the keys you pass change; do not include __type',
type: 'string',
demandOption: true,
})
.example(
'$0 posts:settings post-123 --settings \'{"content_posting_method":"DIRECT_POST"}\'',
'Switch a TikTok draft to direct publishing'
)
.example(
'$0 posts:settings post-123 --settings \'{"subreddit":[{"value":{"subreddit":"/r/selfhosted","title":"My title","type":"self","is_flair_required":true}}]}\'',
'Set a Reddit post\'s subreddit'
);
},
updatePostSettings as any
)
.command(
'posts:connect <id>',
'Connect a post to its published content by updating the release ID',
Expand Down