--- name: postiz description: Use the Postiz CLI/API to manage self-hosted social media scheduling and analytics for Facebook Pages, Instagram, TikTok, YouTube, X, LinkedIn, Reddit, and other integrations. Draft-first and media-upload-first workflow for Hermes. version: 1.0.0 author: Hermes Agent, adapted from gitroomhq/postiz-agent license: AGPL-3.0 metadata: hermes: tags: [postiz, social-media, facebook-page, instagram, tiktok, scheduling, self-hosted, approval-workflow] homepage: https://github.com/gitroomhq/postiz-agent related_skills: [xurl, paid-ads-agent-platforms] --- # Postiz Social Media Operator Use this skill when Alex asks Hermes to manage, draft, schedule, publish, or analyze organic social media content through Postiz. Postiz is open-source/self-hostable. The CLI package is `postiz` and the audited upstream agent repo was `gitroomhq/postiz-agent` at commit `1ba8fd4d48a8b9ee6ce1ce2dc5b3bae7bd92dfc5`. ## Safety posture for Alex Default to **draft-first** operation: 1. Discover connected integrations/accounts. 2. Prepare content/media. 3. Create a Postiz draft (`-t draft`) or show the exact command/JSON that would be used. 4. Ask Alex for explicit approval before changing a draft to `schedule`, deleting posts, or publishing anything live. Do not publish/schedule/delete social posts blindly from Telegram. For destructive or public-facing changes, identify the target Page/account/post ID and summarize exactly what will happen. ## Security / privacy notes from audit - The CLI stores OAuth credentials at `~/.postiz/credentials.json` with mode `0600`. - `postiz auth:status` prints only the first 8 chars of a token/key, but do not paste even partial tokens into chat unless necessary. - The CLI defaults to hosted Postiz endpoints (`https://api.postiz.com` and `https://cli-auth.postiz.com`) unless `POSTIZ_API_URL` / `POSTIZ_AUTH_SERVER` or stored credentials point elsewhere. - For Alex's free/self-hosted path, prefer `POSTIZ_API_URL=https://` and a self-hosted API key/session. Avoid hosted OAuth unless Alex intentionally chooses Postiz Cloud. - Never read or print `~/.postiz/credentials.json` into the conversation. - `auth:login` opens a browser via `xdg-open`/`open`/`start`; in headless/server sessions prefer API key setup or local browser access to the Postiz web UI. ## Install / verify ```bash npm install -g postiz@2.0.13 postiz --version postiz auth:status ``` If using self-hosted Postiz, set the API URL before commands: ```bash export POSTIZ_API_URL=https://postiz.your-domain.com export POSTIZ_API_KEY=your_api_key_here postiz auth:status ``` For persistent Hermes usage, store non-secret config and secrets safely: - `POSTIZ_API_URL` may go in shell profile or Hermes env. - `POSTIZ_API_KEY` should go only in an uncommitted env file / secret store, not Git, logs, screenshots, or chat. ## Core commands ```bash postiz auth:status postiz integrations:list postiz integrations:settings postiz integrations:trigger -d '{"key":"value"}' postiz posts:list postiz analytics:platform -d 30 postiz analytics:post -d 30 ``` ## Media rule — mandatory Every media file must go through `postiz upload` first. Do **not** pass local paths directly to `-m` or JSON `image` fields. Correct: ```bash MEDIA_URL=$(postiz upload /path/to/image-or-video | jq -r '.path') postiz posts:create \ -c "Caption" \ -m "$MEDIA_URL" \ -s "2026-05-15T19:00:00Z" \ -t draft \ -i "" ``` Wrong: ```bash postiz posts:create -c "Caption" -m /path/to/video.mp4 ... ``` Why: TikTok, Instagram, YouTube, and other providers often require publicly reachable/trusted URLs. In self-hosted Postiz this upload goes to the storage backend we configure, not to paid Postiz Cloud unless using their hosted product. ## Self-hosted Postiz deployment pattern for Alex For UI/source customizations on the self-hosted instance, see `references/self-hosted-ui-customization.md`. Key caution: `/home/avalon/apps/postiz` is deployment config only; the app UI comes from the Docker image, so code changes require an explicit custom-image or upstream-patch strategy with rollback. Known deployed instance: - See `references/facebook-page-oauth-and-draft-smoke.md` for the Facebook Page OAuth scope-error fixes, DB/API verification commands, `/api/public/v1` route gotcha, and safe draft smoke-test payload. - App path: `/home/avalon/apps/postiz` - Public URL: `https://postiz.apps.poofc.com` - Local port: `127.0.0.1:4007 -> container 5000` - Storage: Postiz `local` provider with Docker volume mounted at `/uploads` - Public media URLs: `https://postiz.apps.poofc.com/uploads/...` - nginx must allow large uploads, e.g. `client_max_body_size 500M;` - First boot uses `DISABLE_REGISTRATION=false` so Alex can create the admin user; after admin creation, set `DISABLE_REGISTRATION=true` in `/home/avalon/apps/postiz/docker-compose.yaml` and restart. - For Facebook Page OAuth, Postiz expects redirect URI `https://postiz.apps.poofc.com/integrations/social/facebook`; set `FACEBOOK_APP_ID` and `FACEBOOK_APP_SECRET` in the Compose env, then restart `postiz`. - In the Meta app dashboard, add the Postiz host itself (`postiz.apps.poofc.com`) to **App domains** in addition to any ads/admin hosts. If App domains only contains another host such as `hermes-ads.apps.poofc.com`, OAuth may fail even when the redirect URI and app secret are correct. - Do not handcraft a Facebook OAuth URL unless you also create the matching Postiz Redis state keys. Postiz's `/integrations/social/facebook` callback expects Redis keys like `organization:` and `login:` that are set by Postiz's own integration URL endpoint. Prefer the web UI or authenticated API endpoint to generate the URL. If Temporal exits with a dynamic config YAML parse error, check `/home/avalon/apps/postiz/dynamicconfig/development-sql.yaml`; valid content is: ```yaml system.forceSearchAttributesCacheRefreshOnRead: - value: true constraints: {} ``` ## Facebook Page workflow 1. Confirm Postiz is running and reachable. 2. Create/configure a Meta app and set the valid OAuth redirect URI to `https://postiz.apps.poofc.com/integrations/social/facebook`. 3. Set `FACEBOOK_APP_ID` and `FACEBOOK_APP_SECRET` in the Postiz environment and restart Postiz. 4. In the Postiz web UI, connect provider **Facebook Page** via Meta OAuth. 5. Use a Facebook account that has the Page permission needed to create content. 6. Grant requested Page permissions. 7. Back in CLI: ```bash postiz integrations:list FACEBOOK_ID=$(postiz integrations:list | jq -r '.[] | select(.identifier=="facebook" or .identifier=="facebook-page") | .id' | head -1) postiz integrations:settings "$FACEBOOK_ID" ``` 6. Create drafts first: ```bash postiz posts:create \ -c "Draft Facebook Page post text" \ -s "2026-05-15T19:00:00Z" \ -t draft \ -i "$FACEBOOK_ID" ``` 7. Only after Alex approves: ```bash postiz posts:status --status schedule ``` ## Instagram/TikTok/YouTube patterns Always upload media first: ```bash VIDEO_URL=$(postiz upload ./video.mp4 | jq -r '.path') postiz posts:create -c "Video caption" -m "$VIDEO_URL" -s "2026-05-15T19:00:00Z" -t draft -i "" ``` For Instagram post type examples, use platform settings only after checking `integrations:settings`: ```bash postiz integrations:settings postiz posts:create \ -c "Caption #hashtag" \ -m "$IMAGE_URL" \ -s "2026-05-15T19:00:00Z" \ -t draft \ --settings '{"post_type":"post"}' \ -i "" ``` ## Analytics and missing release IDs If analytics returns `{"missing": true}`, the platform published but did not return a usable post/content ID. Resolve with: ```bash postiz posts:missing postiz posts:connect --release-id "" postiz analytics:post -d 30 ``` ## Pitfalls - Do not assume `auth:login` is self-hosted; by default it talks to Postiz's hosted auth server. - If `docker-compose up -d postiz` fails with Python Compose v1 `KeyError: 'ContainerConfig'` after editing environment/volumes, remove only the stale Postiz service container by compose label, then run compose up again: `ids=$(sudo docker ps -aq --filter label=com.docker.compose.service=postiz); [ -n "$ids" ] && sudo docker rm -f $ids; sudo docker-compose up -d postiz`. - Verify a restart by checking both reachability and env inside the container: `curl -I https://postiz.apps.poofc.com` should usually show a `307` redirect to `/auth` when logged out, and `docker exec postiz sh -lc 'echo "$FACEBOOK_APP_ID ${FACEBOOK_APP_SECRET:+present} $DISABLE_REGISTRATION"'` confirms the Meta env without printing the secret. - If the Postiz public API returns `{"msg":"Invalid API key"}`, the local organization likely has no `apiKey` set yet. The public API middleware checks `Authorization: ` directly. Generate/store a private key locally, set `Organization.apiKey` in Postgres, then use `POSTIZ_API_URL=https://postiz.apps.poofc.com` and `POSTIZ_API_KEY=` for CLI/API calls. Do not paste the key into chat. - Meta ads System User tokens with `ads_read`/`ads_management` are not enough for Postiz Facebook Page publishing. Postiz needs a Facebook OAuth app ID + app secret and a user/Page OAuth connection granting `pages_show_list`, `business_management`, `pages_manage_posts`, `pages_manage_engagement`, `pages_read_engagement`, `pages_read_user_content`, and `read_insights`. If the token debug app ID does not match the available app secret, do not configure Postiz with the wrong app. - If Meta secrets/tokens are present directly in `/home/avalon/apps/postiz/docker-compose.yaml`, treat it as a hardening issue: move them to a private env/secrets file with restrictive permissions and consider rotating the values. Never paste the values into chat/logs. On Alex's VPS the known-good remediation is: replace literal secrets in compose with `${VAR}` placeholders, put actual values in `/home/avalon/apps/postiz/.env` with mode `0600`, add `.env` and `.postiz-api-key` to `.gitignore`, run `sudo docker-compose config -q`, then `sudo docker-compose up -d`, and verify `https://postiz.apps.poofc.com` returns a 307 to `/auth`. - Postiz recognizes many Meta/IG blocking/rate errors but is not itself a conservative anti-block policy. Provider code may allow high concurrency (`FacebookProvider.maxConcurrentJob=100`, `InstagramProvider.maxConcurrentJob=400`), so do not use Postiz as the only rate-limit safeguard; keep draft-first approval and add app-level publish caps/cooldowns where Hermes controls scheduling. - Do not publish directly; use drafts until Alex approves. - If Alex says “Stop”, immediately stop any pending Postiz code/Docker/sudo work and summarize only completed inspection/actions. - Do not publish directly; use drafts until Alex approves. - Do not pass raw local media paths to posts. - Do not print credentials or `~/.postiz/credentials.json`. - Be careful with `posts:delete`; it is destructive. - The upstream SKILL.md has some examples that still show raw filenames; treat those as shorthand only after `postiz upload`.