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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased]

### Added
- `media:list` - List media already uploaded to the media library via `GET /public/v1/media` (newest first, 18 per page, `--search` by original file name, `--page`), so existing uploads can be reused without uploading again.
- `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
Expand Down
1 change: 1 addition & 0 deletions FEATURES.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ The Postiz CLI **fully supports** the complete API structure including:
- Each comment can have **its own images** (separate MediaDto arrays)
- Support for various image formats (PNG, JPG, JPEG, GIF)
- Media can be URLs or uploaded files
- `media:list` finds already-uploaded files so their URLs can be reused

#### ✅ Multi-Platform Posting
- Post to multiple platforms in one request
Expand Down
6 changes: 6 additions & 0 deletions QUICK_START.md
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,12 @@ postiz integrations:list
postiz upload ./path/to/image.png
```

### List Uploaded Media

```bash
postiz media:list --search banner
```

## Common Workflows

### 1. Check What's Connected
Expand Down
20 changes: 16 additions & 4 deletions SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@ postiz posts:create ... -m "$URL" ...

If you see `-m "something.jpg"` anywhere below, treat it as shorthand for "the `.path` you got back from `postiz upload something.jpg`" — never a raw local file.

A file that was already uploaded does not need uploading again: find it with `postiz media:list -s <name>` and reuse its `.path`.

**Rule 3 — When posting to TikTok, `content_posting_method` MUST be `"DIRECT_POST"`** unless the user has explicitly asked to finish the post inside the TikTok app. `"UPLOAD"` does not publish — it drops the media into the account's TikTok inbox to be completed manually within 24 hours, while the Postiz API still reports success. A user saying "upload this video to TikTok" means `"DIRECT_POST"`.

**Rule 4 — Fetch `postiz integrations:settings <id>` before scheduling and honor the returned `rules` and per-field `description`s.** They state which settings apply and when. A setting that doesn't apply (wrong posting method, wrong media type, etc.) is **silently discarded**, not rejected — the post still reports success, so this is your only chance to catch it.
Expand Down Expand Up @@ -72,7 +74,7 @@ The fundamental pattern for using Postiz CLI:
1. **Authenticate** - Verify or set up authentication (see above)
2. **Discover** - List integrations and get their settings
3. **Fetch** - Use integration tools to retrieve dynamic data (flairs, playlists, companies)
4. **Prepare** - Upload media files if needed
4. **Prepare** - Upload media files if needed (or reuse an existing `.path` from `media:list`)
5. **Post** - Create posts with content, media, and platform-specific settings
6. **Analyze** - Track performance with platform and post-level analytics
7. **Resolve** - If analytics returns `{"missing": true}`, run `posts:missing` to list provider content, then `posts:connect` to link it
Expand All @@ -91,6 +93,7 @@ postiz integrations:trigger <integration-id> <method> -d '{"key":"value"}'

# 4. Prepare
postiz upload image.jpg
postiz media:list -s image # or reuse an already-uploaded file

# 5. Post
postiz posts:create -c "Content" -m "image.jpg" -i "<integration-id>"
Expand Down Expand Up @@ -166,7 +169,11 @@ postiz posts:create -c "Content" -s "2024-12-31T12:00:00Z" -t draft -i "integrat
# Post with media (upload each file FIRST — see Rule 2)
IMG1=$(postiz upload img1.jpg | jq -r '.path')
IMG2=$(postiz upload img2.jpg | jq -r '.path')
postiz posts:create -c "Content" -m "$IMG1,$IMG2" -s "2024-12-31T12:00:00Z" -i "integration-id"

# Reuse something already in the media library instead of uploading again
EXISTING=$(postiz media:list -s banner | jq -r '.results[0].path // empty')
[ -z "$EXISTING" ] && echo "No uploaded media matches 'banner'" && exit 1
postiz posts:create -c "Content" -m "$IMG1,$IMG2,$EXISTING" -s "2024-12-31T12:00:00Z" -i "integration-id"

# Post with comments (each with own media — every file uploaded first)
MAIN=$(postiz upload main.jpg | jq -r '.path')
Expand Down Expand Up @@ -272,7 +279,7 @@ Returns an empty array if the provider doesn't support this feature or if the po

### Media Upload

**⚠️ IMPORTANT:** Always upload files to Postiz before using them in posts. Many platforms (TikTok, Instagram, YouTube) **require verified URLs** and will reject external links.
**⚠️ IMPORTANT:** Always upload files to Postiz before using them in posts (or reuse a `.path` already in the media library via `media:list`). Many platforms (TikTok, Instagram, YouTube) **require verified URLs** and will reject external links.

```bash
# Upload file and get URL
Expand All @@ -285,6 +292,10 @@ postiz upload image.jpg
VIDEO=$(postiz upload video.mp4)
VIDEO_PATH=$(echo "$VIDEO" | jq -r '.path')
postiz posts:create -c "Content" -s "2024-12-31T12:00:00Z" -m "$VIDEO_PATH" -i "tiktok-id"

# List what is already uploaded (newest first, 18 per page) and reuse a path
postiz media:list -s banner
postiz media:list -p 2
```

---
Expand Down Expand Up @@ -762,7 +773,7 @@ https://clawhub.ai/nevo-david/agent-media
1. **Not authenticated** - Run `postiz auth:login` or `export POSTIZ_API_KEY=key` before using CLI
2. **Invalid integration ID** - Run `integrations:list` to get current IDs
3. **Settings schema mismatch** - Check `integrations:settings` for required fields
4. **Media MUST be uploaded to Postiz first** - ⚠️ **CRITICAL (Rule 2):** Every value passed to `-m` or to an `image`/media field in JSON mode must be a `.path` returned by `postiz upload`. Raw local filenames (`image.jpg`) and external URLs (`https://...`) will be rejected — TikTok, Instagram, YouTube and most other providers only accept Postiz-verified URLs. No exceptions: even a "quick test post" needs the upload step.
4. **Media MUST be uploaded to Postiz first** - ⚠️ **CRITICAL (Rule 2):** Every value passed to `-m` or to an `image`/media field in JSON mode must be a `.path` returned by `postiz upload`. Raw local filenames (`image.jpg`) and external URLs (`https://...`) will be rejected — TikTok, Instagram, YouTube and most other providers only accept Postiz-verified URLs. No exceptions: even a "quick test post" needs the upload step — or a `.path` of a file already uploaded (find it with `media:list -s <name>`).
5. **JSON escaping in shell** - Use single quotes for JSON: `--settings '{...}'`
6. **Date format** - Must be ISO 8601: `"2024-12-31T12:00:00Z"` and is REQUIRED
7. **Tool not found** - Check available tools in `integrations:settings` output
Expand Down Expand Up @@ -805,6 +816,7 @@ postiz posts:status <id> --status draft # Move to draft (stops workflo
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
postiz media:list -s <name> # Find already-uploaded media to reuse

# Analytics
postiz analytics:platform <id> # Platform analytics (7 days)
Expand Down
11 changes: 11 additions & 0 deletions src/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,17 @@ export class PostizAPI {
});
}

async listMedia(page?: number, search?: string) {
const params = new URLSearchParams();
if (page) params.append('page', String(page));
if (search) params.append('search', search);
const queryString = params.toString();
return this.request(
queryString ? `/public/v1/media?${queryString}` : '/public/v1/media',
{ method: 'GET' }
);
}

async upload(file: Buffer, filename: string) {
const formData = new FormData();
const extension = filename.split('.').pop()?.toLowerCase() || '';
Expand Down
14 changes: 14 additions & 0 deletions src/commands/upload.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,3 +24,17 @@ export async function uploadFile(args: any) {
process.exit(1);
}
}

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

try {
const result = await api.listMedia(args.page, args.search);
console.log(JSON.stringify(result, null, 2));
return result;
} catch (error: any) {
console.error('❌ Failed to list media:', error.message);
process.exit(1);
}
}
23 changes: 22 additions & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { hideBin } from 'yargs/helpers';
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';
import { uploadFile, listMedia } from './commands/upload';
import { authLogin, authLogout, authStatus } from './commands/auth';
import type { Argv } from 'yargs';

Expand Down Expand Up @@ -389,6 +389,27 @@ yargs(hideBin(process.argv))
},
uploadFile as any
)
.command(
'media:list',
'List media already uploaded to the media library (newest first, 18 per page)',
(yargs: Argv) => {
return yargs
.option('search', {
alias: 's',
type: 'string',
describe: 'Filter by original file name (case-insensitive)',
})
.option('page', {
alias: 'p',
type: 'number',
describe: 'Page number, starting at 1',
default: 1,
})
Comment thread
coderabbitai[bot] marked this conversation as resolved.
.example('$0 media:list', 'List the most recent uploads')
.example('$0 media:list -s banner', 'Find uploads whose name contains "banner"');
},
listMedia as any
)
.command(
'auth:login',
'Authenticate using OAuth2 (device flow)',
Expand Down