{"id":"47852fdb-924b-4d47-8d93-47306398f2b4","entityType":"agent","slug":"clawhub-postnextio-postnext-social-manager","name":"postnext-social-manager","canonicalUrl":"https://www.xpersona.co/agent/clawhub-postnextio-postnext-social-manager","canonicalPath":"/agent/clawhub-postnextio-postnext-social-manager","generatedAt":"2026-10-09T13:00:11.761Z","source":"CLAWHUB","claimStatus":"UNCLAIMED","verificationTier":"NONE","summary":{"evidence":{"source":"CLAWHUB","verified":false,"confidence":"medium","updatedAt":"2026-10-09T09:17:28.403Z","emptyReason":null},"description":"Manage social media through PostNext - schedule, publish, and analyze posts across Twitter/X, Instagram, LinkedIn, Threads, YouTube, TikTok, and Bluesky via the PostNext public API. Use when the user wants to draft, schedule, or publish social posts, upload media for posts, check what is queued, cancel a scheduled post or delete a post from PostNext, read social analytics (engagement, best time to post, audience growth) or refresh them with an analytics sync, and has a PostNext account and API key. Publishing, scheduling, cancelling, deleting and uploading always preview first and need the user's approval.","descriptionLabel":"Source description","evidenceSummary":"Capability contract not published. No trust telemetry is available yet. 3.2K downloads reported by the source. Last updated 10/9/2026.","installCommand":"clawhub skill install s17bd5rjn15fnxx5tdp2322sax8az52p:postnext-social-manager","sourceUrl":"https://clawhub.ai/postnextio/postnext-social-manager","homepage":"https://clawhub.ai/postnextio/skills/postnext-social-manager","primaryLinks":[{"label":"View on ClawHub","url":"https://clawhub.ai/postnextio/postnext-social-manager","kind":"source"},{"label":"Homepage","url":"https://clawhub.ai/postnextio/skills/postnext-social-manager","kind":"homepage"}],"safetyScore":84,"overallRank":62,"popularityScore":70,"trustScore":null,"claimedByName":null,"isOwner":false,"seoDescription":"postnext-social-manager technical dossier on Xpersona with agent coverage, OPENCLEW support, and live trust metadata."},"coverage":{"evidence":{"source":"public-profile","verified":false,"confidence":"medium","updatedAt":"2026-10-09T09:17:28.403Z","emptyReason":null},"protocols":[{"protocol":"OPENCLEW","label":"OpenClaw","status":"self-declared","notes":"Declared in the public agent profile."}],"capabilities":[],"verifiedCount":0,"selfDeclaredCount":1,"capabilityMatrix":{"rows":[{"key":"OPENCLEW","type":"protocol","support":"unknown","confidenceSource":"profile","notes":"Listed on profile"}],"flattenedTokens":"protocol:OPENCLEW|unknown|profile"}},"adoption":{"evidence":{"source":"CLAWHUB","verified":false,"confidence":"medium","updatedAt":"2026-10-09T09:17:28.403Z","emptyReason":null},"stars":null,"forks":null,"downloads":3186,"packageName":null,"latestVersion":"0.1.5","tractionLabel":"3.2K downloads"},"release":{"evidence":{"source":"CLAWHUB","verified":false,"confidence":"medium","updatedAt":"2026-10-09T09:17:28.403Z","emptyReason":null},"lastUpdatedAt":"2026-10-09T09:17:28.403Z","lastCrawledAt":"2026-10-09T09:17:28.403Z","lastIndexedAt":null,"nextCrawlAt":"2026-10-10T09:17:28.403Z","lastVerifiedAt":null,"highlights":[{"version":"0.1.5","createdAt":"2026-10-06T06:33:58.875Z","changelog":"Declare bash in requires.bins (the helper is a bash script).","fileCount":12,"zipByteSize":21667},{"version":"0.1.4","createdAt":"2026-10-06T06:09:09.539Z","changelog":"Scan follow-ups: description discloses cancel/delete and analytics sync; allowed-tools declared; asset deletion removed from the references; confirm-first rule on raw post endpoints; corrected a re-schedule guard claim; test dummy key no longer resembles a real key.","fileCount":12,"zipByteSize":21675},{"version":"0.1.3","createdAt":"2026-10-06T05:51:18.363Z","changelog":"Account-changing commands (post, schedule, cancel, delete, upload) now preview first and run only with the --confirm code from that preview; the code is bound to the exact operation. Adds tests/confirm.sh.","fileCount":12,"zipByteSize":21445},{"version":"0.1.2","createdAt":"2026-10-06T05:26:38.103Z","changelog":"Security hardening: API key sent via stdin instead of curl argv and ~/.curlrc skipped (curl -q); upload file names with ; , \" or \\ refused (curl -F split could upload a different file); string-shaped API errors now show the real message.","fileCount":11,"zipByteSize":17909},{"version":"0.1.1","createdAt":"2026-10-03T07:43:27.474Z","changelog":"Security hardening: fixed API base URL (no override), validated ids in request paths, https-only presigned uploads, image/video-only uploads, safety notes and declared requirements","fileCount":11,"zipByteSize":17668},{"version":"0.1.0","createdAt":"2026-07-21T12:20:59.659Z","changelog":"- Initial release of postnext-social-manager skill. - Enables programmatic management of social media via the PostNext public API. - Schedule, publish, and analyze posts across Twitter/X, Instagram, LinkedIn, Threads, YouTube, TikTok, and Bluesky. - Includes helper script and raw curl recipes for key API operations. - Highlights common API pitfalls and how the `postnext` helper addresses them. - Outlines setup steps, usage workflow, and current limitations.","fileCount":13,"zipByteSize":17628}]},"execution":{"evidence":{"source":"CLAWHUB","verified":false,"confidence":"low","updatedAt":null,"emptyReason":"No published capability contract is available yet."},"installCommand":"clawhub skill install s17bd5rjn15fnxx5tdp2322sax8az52p:postnext-social-manager","setupComplexity":"low","setupSteps":["Install using `clawhub skill install s17bd5rjn15fnxx5tdp2322sax8az52p:postnext-social-manager` in an isolated environment before connecting it to live workloads.","No published capability contract is available yet, so validate auth and request/response behavior manually.","Review the upstream CLAWHUB listing at https://clawhub.ai/postnextio/postnext-social-manager before using production credentials."],"contract":{"contractStatus":"missing","authModes":[],"requires":[],"forbidden":[],"supportsMcp":false,"supportsA2a":false,"supportsStreaming":false,"inputSchemaRef":null,"outputSchemaRef":null,"dataRegion":null,"contractUpdatedAt":null,"sourceUpdatedAt":null,"freshnessSeconds":null},"invocationGuide":{"preferredApi":{"snapshotUrl":"https://www.xpersona.co/api/v1/agents/clawhub-postnextio-postnext-social-manager/snapshot","contractUrl":"https://www.xpersona.co/api/v1/agents/clawhub-postnextio-postnext-social-manager/contract","trustUrl":"https://www.xpersona.co/api/v1/agents/clawhub-postnextio-postnext-social-manager/trust"},"curlExamples":["curl -s \"https://www.xpersona.co/api/v1/agents/clawhub-postnextio-postnext-social-manager/snapshot\"","curl -s \"https://www.xpersona.co/api/v1/agents/clawhub-postnextio-postnext-social-manager/contract\"","curl -s \"https://www.xpersona.co/api/v1/agents/clawhub-postnextio-postnext-social-manager/trust\""],"jsonRequestTemplate":{"query":"summarize this repo","constraints":{"maxLatencyMs":2000,"protocolPreference":["OPENCLEW"]}},"jsonResponseTemplate":{"ok":true,"result":{"summary":"...","confidence":0.9},"meta":{"source":"CLAWHUB","generatedAt":"2026-10-09T13:00:11.758Z"}},"retryPolicy":{"maxAttempts":3,"backoffMs":[500,1500,3500],"retryableConditions":["HTTP_429","HTTP_503","NETWORK_TIMEOUT"]}},"endpoints":{"dossierUrl":"https://www.xpersona.co/api/v1/agents/clawhub-postnextio-postnext-social-manager/dossier","snapshotUrl":"https://www.xpersona.co/api/v1/agents/clawhub-postnextio-postnext-social-manager/snapshot","contractUrl":"https://www.xpersona.co/api/v1/agents/clawhub-postnextio-postnext-social-manager/contract","trustUrl":"https://www.xpersona.co/api/v1/agents/clawhub-postnextio-postnext-social-manager/trust"}},"reliability":{"evidence":{"source":"runtime-metrics","verified":false,"confidence":"low","updatedAt":null,"emptyReason":"No trust, reliability, or runtime telemetry is available."},"trust":{"status":"unavailable","handshakeStatus":"UNKNOWN","verificationFreshnessHours":null,"reputationScore":null,"p95LatencyMs":null,"successRate30d":null,"fallbackRate":null,"attempts30d":null,"trustUpdatedAt":null,"trustConfidence":"unknown","sourceUpdatedAt":null,"freshnessSeconds":null},"decisionGuardrails":{"doNotUseIf":["Contract metadata is missing or unavailable for deterministic execution."],"safeUseWhen":[],"riskFlags":["missing_or_unavailable_contract","trust_data_unavailable","schema_references_missing"],"operationalConfidence":"low"},"executionMetrics":{"observedLatencyMsP50":null,"observedLatencyMsP95":null,"estimatedCostUsd":null,"uptime30d":null,"rateLimitRpm":null,"rateLimitBurst":null,"lastVerifiedAt":null,"verificationSource":null},"runtimeMetrics":{"successRate":null,"avgLatencyMs":null,"avgCostUsd":null,"hallucinationRate":null,"retryRate":null,"disputeRate":null,"p50Latency":null,"p95Latency":null,"lastUpdated":null}},"benchmarks":{"evidence":{"source":"no-benchmark-data","verified":false,"confidence":"low","updatedAt":null,"emptyReason":"No benchmark suites or observed failure patterns are available."},"suites":[],"failurePatterns":[]},"artifacts":{"evidence":{"source":"CLAWHUB","verified":false,"confidence":"medium","updatedAt":"2026-10-09T09:17:28.403Z","emptyReason":null},"readme":"Skill: postnext-social-manager\n\nOwner: postnextio\n\nSummary: Manage social media through PostNext - schedule, publish, and analyze posts across Twitter/X, Instagram, LinkedIn, Threads, YouTube, TikTok, and Bluesky via the PostNext public API. Use when the user wants to draft, schedule, or publish social posts, upload media for posts, check what is queued, cancel a scheduled post or delete a post from PostNext, read social analytics (engagement, best time to post, audience growth) or refresh them with an analytics sync, and has a PostNext account and API key. Publishing, scheduling, cancelling, deleting and uploading always preview first and need the user's approval.\n\nTags: latest:0.1.5\n\nVersion history:\n\nv0.1.5 | 2026-10-06T06:33:58.875Z | user\n\nDeclare bash in requires.bins (the helper is a bash script).\n\nv0.1.4 | 2026-10-06T06:09:09.539Z | user\n\nScan follow-ups: description discloses cancel/delete and analytics sync; allowed-tools declared; asset deletion removed from the references; confirm-first rule on raw post endpoints; corrected a re-schedule guard claim; test dummy key no longer resembles a real key.\n\nv0.1.3 | 2026-10-06T05:51:18.363Z | user\n\nAccount-changing commands (post, schedule, cancel, delete, upload) now preview first and run only with the --confirm code from that preview; the code is bound to the exact operation. Adds tests/confirm.sh.\n\nv0.1.2 | 2026-10-06T05:26:38.103Z | user\n\nSecurity hardening: API key sent via stdin instead of curl argv and ~/.curlrc skipped (curl -q); upload file names with ; , \" or \\ refused (curl -F split could upload a different file); string-shaped API errors now show the real message.\n\nv0.1.1 | 2026-10-03T07:43:27.474Z | user\n\nSecurity hardening: fixed API base URL (no override), validated ids in request paths, https-only presigned uploads, image/video-only uploads, safety notes and declared requirements\n\nv0.1.0 | 2026-07-21T12:20:59.659Z | auto\n\n- Initial release of postnext-social-manager skill.\n- Enables programmatic management of social media via the PostNext public API.\n- Schedule, publish, and analyze posts across Twitter/X, Instagram, LinkedIn, Threads, YouTube, TikTok, and Bluesky.\n- Includes helper script and raw curl recipes for key API operations.\n- Highlights common API pitfalls and how the `postnext` helper addresses them.\n- Outlines setup steps, usage workflow, and current limitations.\n\nArchive index:\n\nArchive v0.1.5: 12 files, 21667 bytes\n\nFiles: LICENSE (1065b), postnext (15448b), README.md (2277b), references/analytics.md (2492b), references/channels.md (1836b), references/errors.md (2585b), references/media.md (3147b), references/posts.md (4809b), skill-card.md (2017b), SKILL.md (5170b), tests/confirm.sh (4044b), _meta.json (142b)\n\nFile v0.1.5:SKILL.md\n\n---\nname: postnext-social-manager\ndescription: Manage social media through PostNext - schedule, publish, and analyze posts across Twitter/X, Instagram, LinkedIn, Threads, YouTube, TikTok, and Bluesky via the PostNext public API. Use when the user wants to draft, schedule, or publish social posts, upload media for posts, check what is queued, cancel a scheduled post or delete a post from PostNext, read social analytics (engagement, best time to post, audience growth) or refresh them with an analytics sync, and has a PostNext account and API key. Publishing, scheduling, cancelling, deleting and uploading always preview first and need the user's approval.\nallowed-tools: Bash(./postnext:*), Bash(curl:*), Bash(jq:*), Read\nmetadata:\n  requires:\n    bins: [bash, curl, jq]\n    env: [POSTNEXT_API_KEY]\n---\n\n# PostNext Social Manager\n\nDrive a PostNext account programmatically: connect channels, upload media, compose and schedule or publish posts across platforms, and read analytics. Wraps the PostNext public REST API with an API key.\n\n## Setup\n\n1. The user creates an API key at https://postnext.io/account/api-keys (shown only once).\n2. Export it: `export POSTNEXT_API_KEY=<key>`\n3. Base URL is `https://api-app.postnext.io`. Auth header on every request: `x-api-key: $POSTNEXT_API_KEY`.\n\nTwo ways to use this skill:\n- **Helper (recommended where a shell is available):** the bundled `postnext` script (needs `curl` + `jq`). It handles the fiddly, error-prone parts for you. Run `./postnext help`.\n- **Raw curl (works anywhere, including no-filesystem sandboxes):** every operation has a copy-paste curl recipe in `references/`.\n\n## The core loop\n\n1. **Connect** (one-time, done by the user in the PostNext web app - there is no API to connect a channel). Confirm what is connected: `postnext channels`.\n2. **Upload media** if the post has an image or video: `postnext upload ./clip.mp4` prints an asset URL (or pass the file straight to `--media` in step 3).\n3. **Compose + publish now** or **schedule**:\n   - `postnext post --provider twitter --text \"...\" --media ./a.png`\n   - `postnext schedule --provider instagram --text \"...\" --media ./a.jpg --at 2026-07-25T14:00:00Z`\n\n   Every command that changes the account (`post`, `schedule`, `cancel`, `delete`, `upload`) first prints a **preview and sends nothing** (exit code 3). Show the preview to the user. Only after they approve it, run the identical command again with `--confirm <code>` from the preview. The code covers that exact operation: if the text, media, time or target changes, preview again and get a fresh approval.\n4. **Measure**: `postnext analytics overview`, `postnext analytics best-time twitter`, `postnext results`.\n\n## Safety\n\n- The API key acts on the user's real social accounts. Use a dedicated key, and never paste it into posts, logs or URLs.\n- Confirm with the user before any publish, schedule, cancel, delete or upload. The helper enforces this: without the `--confirm` code from its own preview it sends nothing. Never pass a code the user has not approved, and never reuse one for a changed operation.\n- `delete` removes the post from PostNext only. It does not remove an already-published post from the platform.\n- The helper only talks to `https://api-app.postnext.io`. Do not point raw curl recipes at any other host with the key.\n\n## Four traps this skill exists to prevent\n\nThe PostNext API has four behaviors that silently produce broken posts. The `postnext` helper handles all four; if you use raw curl, you must handle them yourself (see `references/posts.md` and `references/media.md`).\n\n1. **Key posts by the bare provider name** (`twitter`), never `twitter:@handle`. A colon key is rejected at publish. Select the account with the entry's `providerId` + `channelName`.\n2. **Attach media by its URL**, never by `assetId`. A post that carries only `assetIds` publishes with no media and no error. Put the uploaded `asset.url` into `content.media`.\n3. **`providerId` is the connection's `providerId`** (the platform-native account id from `GET /api/connections`), not `uniqueId`. A wrong or missing `providerId` fails at the publish worker silently.\n4. **Only twitter and threads thread.** An array of posts sent to any other provider is silently reduced to the first item.\n\n## When to read which reference\n\n| Task | Read |\n|------|------|\n| Build a post, threads, per-provider rules, char limits | `references/posts.md` |\n| Upload an image or video, size routing, the 3-step flow | `references/media.md` |\n| List or check connected channels, resolve `providerId` | `references/channels.md` |\n| Read analytics, best time to post, sync | `references/analytics.md` |\n| Response envelope shapes, error codes, rate limits | `references/errors.md` |\n\n## Known limitations\n\n- No channel connect over the API (browser-only in PostNext).\n- No team switching: the key operates on its account's default team.\n- One entry per provider per request (no multi-account-per-provider in a single post).\n- Analytics and per-post metrics require PUBLISHED posts.\n- Not supported by this skill: WordPress/blog posts, brand-profile edits, team management (use the PostNext MCP or web app).\n\nFile v0.1.5:README.md\n\n# postnext-social-manager\n\nA Claude Agent Skill for managing social media through [PostNext](https://postnext.io). It lets an agent connect-aware, upload media, compose, schedule, and publish posts across Twitter/X, Instagram, LinkedIn, Threads, YouTube, TikTok, and Bluesky, and read analytics - all via the PostNext public API with a single API key.\n\n## Install\n\nCopy this directory into your skills location (for Claude Code, a `skills/` directory the harness scans), then set your key:\n\n```bash\nexport POSTNEXT_API_KEY=<your key>   # create at https://postnext.io/account/api-keys\n```\n\nThe bundled `postnext` helper needs `curl` and `jq`. In an environment with no shell (for example claude.ai), the skill still works: every operation has a raw curl recipe in `references/`.\n\n## Quick start\n\n```bash\n./postnext channels                                   # what is connected\n./postnext post --provider twitter --text \"Hello\" --media ./hero.png   # prints a preview, sends nothing\n./postnext post --provider twitter --text \"Hello\" --media ./hero.png --confirm <code>   # after the user approves\n./postnext schedule --provider instagram --text \"Launch\" --media ./a.jpg --at 2026-07-25T14:00:00Z\n./postnext analytics best-time twitter\n```\n\nRun `./postnext help` for the full command list. `post`, `schedule`, `cancel`, `delete` and `upload` always preview first and need the `--confirm` code from that preview to run. `bash tests/confirm.sh` checks this offline.\n\n## What is in here\n\n| Path | What |\n|------|------|\n| `SKILL.md` | Skill entry point: setup, the core loop, and a router to the references. |\n| `postnext` | Bash helper (curl + jq) that handles the API's error-prone parts. |\n| `references/` | Per-area docs with raw curl recipes: posts, media, channels, analytics, errors. |\n| `tests/confirm.sh` | Proves the five account-changing commands send nothing without a matching `--confirm` code. |\n| `docs/` | Design spec. |\n\n## Scope\n\nCovers the PostNext social publishing and analytics loop. Out of scope (use the PostNext MCP at `mcp.postnext.io` or the web app): connecting channels, WordPress/blog posts, brand-profile edits, and team management.\n\n## License\n\nMIT. See `LICENSE`. Not affiliated with post-bridge; inspired by the shape of its social-manager skill.\n\nFile v0.1.5:_meta.json\n\n{\n  \"ownerId\": \"kn7cy875pa1wnbqcte11mew4kd8ay84v\",\n  \"slug\": \"postnext-social-manager\",\n  \"version\": \"0.1.5\",\n  \"publishedAt\": 1791268438875\n}\n\nFile v0.1.5:references/analytics.md\n\n# Analytics\n\nAll analytics endpoints return `{success, data}`. Several require a paid tier (noted). Metrics exist only for **PUBLISHED** posts - per-post calls error on drafts or scheduled posts.\n\n## Freshness\n\nStored metrics are periodically snapshotted. To pull fresh numbers on demand:\n\n`POST /api/v1/analytics/sync` (paid) -> `{synced, summary:{twitterSnapped, instagramSnapped, tiktokSnapped, linkedinSnapped}}`. Rate-limited and bounded by per-platform caps. Call it before reading if you need current data.\n\n## Endpoints\n\n| Endpoint | Purpose | Params |\n|----------|---------|--------|\n| `GET /api/v1/analytics` | last-7-day counts of posts/ideas/assets | - |\n| `GET /api/v1/analytics/overview` | totals (impressions, reach, likes, comments, engagementRate) + vsPrevious | `channel`, `period` |\n| `GET /api/v1/analytics/posts` | per-post rows | `channel`, `period`, `sort=engagement|impressions|likes|recent` |\n| `GET /api/v1/analytics/daily` | per-day activity counts | `days` (1-60) |\n| `GET /api/v1/analytics/audience-growth` | follower time series | `channel`, `period` |\n| `GET /api/v1/analytics/engagement-over-time` (paid) | engagement rate per day | `channel`, `period` |\n| `GET /api/v1/analytics/best-time-to-post` (paid) | ranked posting slots from your own performance | `platform` (required), `timezone`, `topN` |\n| `GET /api/v1/analytics/post/{postId}` | latest metrics, one row per provider | - |\n| `GET /api/v1/analytics/post/{postId}/daily` | daily snapshots + day-over-day deltas | - |\n\n`period` is one of `7d`, `30d`, `90d`, `all` (default `30d`). `channel` filters to one provider/channel id; omit or `all` for no filter.\n\n## Capability flag\n\n`overview`, `posts`, and `engagement-over-time` may include a `capability` field: `ok`, `insights_unavailable`, `api_tier_limited`, `no_data_yet`, or `unsupported` (it can also be `null`/absent, which means normal). If it is present and not `ok`, tell the user why the numbers are thin (for example a platform whose API tier does not expose insights) rather than presenting zeros as real.\n\n`engagementRate` is a fraction (multiply by 100 for a percentage).\n\n## Examples\n\n```bash\nBASE=https://api-app.postnext.io\ncurl -sS \"$BASE/api/v1/analytics/overview?period=30d\" -H \"x-api-key: $POSTNEXT_API_KEY\"\ncurl -sS \"$BASE/api/v1/analytics/best-time-to-post?platform=instagram&timezone=Europe/Bucharest&topN=5\" -H \"x-api-key: $POSTNEXT_API_KEY\"\ncurl -sS -X POST \"$BASE/api/v1/analytics/sync\" -H \"x-api-key: $POSTNEXT_API_KEY\"\n```\n\nFile v0.1.5:references/channels.md\n\n# Channels (connections)\n\nChannels are connected in the PostNext web app - there is no API to start an OAuth connection. This skill only lists and reads them.\n\n## List connected channels\n\n`GET /api/connections` returns a **bare JSON array** (no envelope). Each item:\n\n```jsonc\n{\n  \"provider\": \"twitter\",           // one of twitter/instagram/linkedin/threads/youtube/tiktok/bluesky\n  \"channelName\": \"@yourbrand\",     // the connected handle - use this in a post's channelName\n  \"providerId\": \"tw_9f3a1c20e5\",   // platform-native account id - use this in a post's providerId\n  \"uniqueId\": \"...\",                 // internal UUID - do NOT use for posting\n  \"requiresAttention\": true,       // true only when a token refresh actually failed (re-auth needed)\n  \"isActive\": true,\n  \"avatar\": \"...\", \"createdAt\": \"...\", \"tokenExpiry\": \"...\"\n}\n```\n\n## Resolving providerId for a post (trap #3)\n\nTo post to a channel, pick the connection by matching `provider` (and `channelName` if the user named one), then copy its **`providerId`** and `channelName` into the post entry. Never use `uniqueId` - the publish worker needs the platform-native `providerId` or it fails silently.\n\n```bash\n# providerId + channelName for a given provider\ncurl -sS https://api-app.postnext.io/api/connections -H \"x-api-key: $POSTNEXT_API_KEY\" \\\n  | jq -r '.[] | select(.provider==\"twitter\") | \"\\(.channelName)\\t\\(.providerId)\"'\n```\n\nUse `requiresAttention == true` (not `tokenExpiry`) to decide whether a channel needs the user to re-authenticate in the web app before it can publish.\n\n## Check one channel\n\n`GET /api/connections/{provider}/{channelName}/check` returns a flat status object whose HTTP status mirrors the `status` field: `active` -> 200, `expired` -> 401, `invalid` -> 404.\n\n```jsonc\n{ \"status\": \"active\", \"message\": \"...\", \"expiresAt\": \"...\" }\n```\n\nFile v0.1.5:references/errors.md\n\n# Response shapes and errors\n\n## Success envelopes are inconsistent - check per endpoint\n\n| Shape | Endpoints |\n|-------|-----------|\n| `{success, data}` | most Posts, all Analytics, asset create-url/complete/get, single-post GET |\n| `{success, data, meta}` | `GET /api/posts` when `?page`/`?limit` is present |\n| `{success, data, pagination}` | `GET /api/assets/mine` (pagination is top-level, not `meta`) |\n| **bare array** | `GET /api/connections` |\n| **flat object, no wrapper** | `GET /api/v1/account`; connection `/check`; `upload/single` and `upload/url` (`{message, asset}`) |\n\nWhen parsing, do not assume a `data` key. The `postnext` helper unwraps `data` only when present.\n\n## Error shapes - there are three\n\n1. Standard: `{ \"success\": false, \"error\": { \"message\": \"...\" } }` (validation, quota 403).\n2. String variant: `{ \"success\": false, \"error\": \"Authentication required\" }` (401, bad/missing key) or `{ \"success\": false, \"error\": \"Analytics is a paid feature\", \"code\": \"ANALYTICS_PAID_ONLY\" }` (paid-tier gates). Here `error` is a string, not an object.\n3. Payment (402): a separate top-level shape:\n   ```jsonc\n   { \"error\": \"PAYMENT_REQUIRED\", \"code\": \"SUBSCRIPTION_INACTIVE\",\n     \"status\": \"past_due\", \"currency\": \"usd\",\n     \"actions\": { \"retryUrl\": \"...\", \"updateCardUrl\": \"...\", \"downgradeUrl\": \"...\" } }\n   ```\n\n## Status codes\n\n| Code | Meaning | What to tell the user |\n|------|---------|-----------------------|\n| 401 | bad or missing `x-api-key` | check the key |\n| 402 | subscription in a payment-failure state | fix billing (use `actions.updateCardUrl`) |\n| 403 | posting not allowed on the plan, or monthly post quota reached | upgrade or wait for the quota to reset |\n| 413 | media over the size cap (50 MB presigned) | use the multipart single path, or shrink |\n| 415 | uploaded bytes do not match the declared type | re-upload with the correct contentType |\n| 422 | unsupported media for platform (e.g. video on LinkedIn) | remove the video or pick another channel |\n| 429 | rate limited | back off and retry |\n\n## Known quirks (live-verified)\n\n- **GET a missing or just-deleted post returns HTTP 500**, not 404: `{\"success\":false,\"error\":\"Internal Server Error\",\"message\":\"Post group not found\"}`. Treat a 500 whose `message` is `Post group not found` as not-found.\n- A 500 whose body still parses as `{success:false, ..., message}` is a handled not-found/edge case, not necessarily an outage.\n\n## Rate limits\n\nRoughly: standard 100 requests / 15 min; social endpoints ~50 / 15 min; auth ~5 / 60 min. On a 429, pause and retry rather than hammering.\n\nFile v0.1.5:references/media.md\n\n# Media\n\n> Safety: upload only files the user asked to attach (images and video). Use a dedicated API key and never echo it. The `postnext` helper refuses other file types.\n\nUpload an image or video, then put the returned **`asset.url`** into a post's `content.media[].url`. The `assetId` is not used by the publish path.\n\n## Which upload path\n\n| Case | Endpoint | Cap | Needs `userId`? |\n|------|----------|-----|-----------------|\n| image or file <= 50 MB, one of jpeg/png/webp/gif/mp4/webm | 3-step presigned (`create-url` -> PUT -> `complete`) | 50 MB | no (from key) |\n| large video (> 50 MB, up to 500 MB) or other type | multipart `POST /api/assets/upload/single` | 500 MB | **yes** = account `uniqueId` |\n| a remote image URL | `POST /api/assets/upload/url` | 25 MB | **yes**; **image only** (a video URL is stored as .jpg) |\n\nGet the account `uniqueId` from `GET /api/v1/account` (`.uniqueId`).\n\n## 3-step presigned flow (preferred for images and small video)\n\n```bash\nBASE=https://api-app.postnext.io\n# 1. request an upload URL (declares type + exact byte size; pre-charges storage quota)\nINIT=$(curl -sS -X POST $BASE/api/assets/upload/create-url \\\n  -H \"x-api-key: $POSTNEXT_API_KEY\" -H 'Content-Type: application/json' \\\n  -d '{\"filename\":\"hero.png\",\"contentType\":\"image/png\",\"sizeBytes\":48213}')\nUPLOAD_URL=$(echo \"$INIT\" | jq -r .data.uploadUrl)\nUPLOAD_ID=$(echo \"$INIT\" | jq -r .data.uploadId)\n\n# 2. PUT the bytes. Content-Type MUST equal the declared contentType or S3 rejects the signature.\ncurl -sS -X PUT \"$UPLOAD_URL\" -H 'Content-Type: image/png' --data-binary @hero.png\n\n# 3. finalize -> returns the asset; use .data.url in the post\ncurl -sS -X POST $BASE/api/assets/upload/complete \\\n  -H \"x-api-key: $POSTNEXT_API_KEY\" -H 'Content-Type: application/json' \\\n  -d \"{\\\"uploadId\\\":\\\"$UPLOAD_ID\\\"}\" | jq -r .data.url\n```\n\n`contentType` allow-list (presigned): `image/jpeg, image/png, image/webp, image/gif, video/mp4, video/webm`. `sizeBytes` must be the true byte size and <= 50 MB (else 413). `complete` verifies the bytes by magic number (415 on mismatch) and reconciles the storage charge to the real size.\n\n## Multipart single (large video)\n\n```bash\nUID=$(curl -sS https://api-app.postnext.io/api/v1/account -H \"x-api-key: $POSTNEXT_API_KEY\" | jq -r .uniqueId)\ncurl -sS -X POST https://api-app.postnext.io/api/assets/upload/single \\\n  -H \"x-api-key: $POSTNEXT_API_KEY\" \\\n  -F \"file=@big.mp4;type=video/mp4\" -F \"userId=$UID\" | jq -r .asset.url\n```\nNote the different response shape: single/url return a flat `{message, asset}` (no `data` wrapper); presigned `complete` returns `{success, data: asset}`.\n\n## Then attach it\n\n```jsonc\n\"content\": { \"text\": \"...\", \"media\": [{ \"url\": \"<asset.url>\", \"type\": \"IMAGE\" }] }\n```\n`type` is `IMAGE`, `VIDEO`, or `GIF`. For a multi-image Instagram carousel, verify whether a top-level `mediaType: \"CAROUSEL\"` is also required (unverified).\n\n## Manage assets\n\n- `GET /api/assets/mine?page=1&limit=50` -> `{success, data, pagination}` (pagination is top-level).\n- `GET /api/assets/{assetId}` -> `{success, data}`.\n\nDeleting assets is out of scope for this skill; use the PostNext web app.\n\nFile v0.1.5:references/posts.md\n\n# Posts\n\n> Safety: the API key posts to real accounts. Use a dedicated key, never echo it, and confirm with the user before any publish, schedule, cancel or delete.\n\nCreate a post by sending a `providers` map. Each key is a **bare provider name** and each value is one channel's entry.\n\n## Request shape (CreatePostRequest)\n\n```jsonc\n{\n  \"providers\": {\n    \"twitter\": {                              // BARE name. NOT \"twitter:@handle\".\n      \"channelName\": \"@yourbrand\",            // the real connected handle (from GET /connections)\n      \"providerId\":  \"tw_9f3a1c20e5\",         // connection.providerId (NOT uniqueId)\n      \"content\": { \"text\": \"We shipped v2.\",\n                   \"media\": [{ \"url\": \"https://cdn.postnext.io/assets/hero.png\", \"type\": \"IMAGE\" }],\n                   \"hashtags\": [\"launch\"] },\n      \"firstComment\": \"...\"                   // instagram + linkedin ONLY; ignored elsewhere\n    }\n  },\n  \"title\": \"Launch\",                          // optional, internal label\n  \"tags\": [\"launch\"]                          // optional\n}\n```\n\n`providerId` and `channelName` come from `GET /api/connections` (see `channels.md`). Resolve them by matching `provider` + `channelName`. A wrong/missing `providerId` makes the publish worker reject the row silently.\n\n## Three ways to send it\n\n| Goal | Endpoint |\n|------|----------|\n| Save a draft | `POST /api/posts` |\n| Publish immediately | `POST /api/posts/publish` |\n| Schedule | `POST /api/posts/schedule` with `\"scheduledAt\": \"<future ISO8601>\"` |\n\nExisting post by id: `POST /api/posts/{postId}/publish`, `POST /api/posts/{postId}/schedule`, `POST /api/posts/{postId}/cancel-schedule` (back to draft), `DELETE /api/posts/{groupId}` (removes from PostNext; does not delete an already-published post from the platform).\n\n> Safety: every call in this section changes the user's account. Before any publish, schedule, cancel or delete, show the user the exact provider, channel, text, media, time and post id, and wait for their explicit yes. The `postnext` helper enforces this with a preview and a `--confirm` code; with raw curl it is on you.\n\n`userId` and `teamId` are derived from the key - never send them.\n\nNote: fetching a deleted or non-existent post (`GET /api/posts/{id}`) returns HTTP 500 with `message: \"Post group not found\"`, not 404 (see `errors.md`).\n\n## Single post vs thread\n\n`content` is either an object (single post) or an array (thread).\n\n**Single** (`LegacyPostContent`):\n```jsonc\n{ \"text\": \"...\", \"media\": [{ \"url\": \"<asset.url>\", \"type\": \"IMAGE|VIDEO|GIF\" }],\n  \"hashtags\": [\"a\"], \"instagramPostType\": \"feed|post|story|reel\" }\n```\n\n**Thread** (array, `twitter` and `threads` ONLY):\n```jsonc\n[ { \"order\": 1, \"text\": \"1/\", \"media\": [] },\n  { \"order\": 2, \"text\": \"2/\" } ]\n```\n`order` must be exactly 1..N with no gaps. Sending an array to any other provider silently keeps only element `[0]`.\n\nAttach media by URL (`content.media[].url` or the legacy `content.mediaUrls[]`). `assetIds` is never dereferenced - a post with only `assetIds` has no media.\n\n## Per-provider rules enforced at create time\n\n| Provider | Rules |\n|----------|-------|\n| instagram | text or media required; caption <= 2200; <= 30 hashtags; `firstComment` <= 2200; `instagramPostType` in feed/post/story/reel |\n| bluesky | text or image; <= 300 graphemes; <= 4 images; **no video** |\n| linkedin | **no video** (422 UNSUPPORTED_MEDIA_FOR_PLATFORM); IMAGE + GIF ok; `firstComment` <= 1250 |\n| twitter | text required; no char limit enforced at create; polls 2-4 options |\n| threads, tiktok | no create-time content validator |\n| youtube | publish path via this route is UNVERIFIED - test before relying on it |\n\n## Scheduling\n\n`scheduledAt` must be a future ISO8601 datetime (past or now is a 400). `timezone` only affects recurrence and display; it never shifts the fire time.\n\n**Idempotency:** raw `POST /api/posts/{postId}/schedule` will create a duplicate job if called twice, causing a double-publish. Before re-scheduling, list the post's current state and confirm it is not already scheduled. The `postnext` helper refuses past times, and its `--confirm` code covers one exact operation, but it does not detect an existing job for the same post.\n\n## Raw curl\n\n```bash\n# publish now\ncurl -sS -X POST https://api-app.postnext.io/api/posts/publish \\\n  -H \"x-api-key: $POSTNEXT_API_KEY\" -H 'Content-Type: application/json' \\\n  -d '{\"providers\":{\"twitter\":{\"channelName\":\"@brand\",\"providerId\":\"tw_123\",\n       \"content\":{\"text\":\"hi\",\"media\":[{\"url\":\"https://cdn/x.png\",\"type\":\"IMAGE\"}]}}}}'\n\n# list posts (optionally by status)\ncurl -sS \"https://api-app.postnext.io/api/posts?status=SCHEDULED\" -H \"x-api-key: $POSTNEXT_API_KEY\"\n\n# per-channel publish outcomes\ncurl -sS https://api-app.postnext.io/api/posts/results -H \"x-api-key: $POSTNEXT_API_KEY\"\n```\n\nFile v0.1.5:skill-card.md\n\n## Description:\n\nHelps agents draft, schedule, publish, and analyze social posts across connected platforms through a PostNext account.\n\nThis skill is ready for commercial/non-commercial use.\n\n## Publisher:\n\n[postnextio](https://clawhub.ai/user/postnextio)\n\n### License/Terms of Use:\n\nMIT\n\n## Use Case:\n\nSocial media managers and teams with a PostNext account use this skill to prepare and publish posts, manage scheduled content and media, and review engagement and audience analytics across connected channels.\n\n### Deployment Geography for Use:\n\nGlobal\n\n## Known Risks and Mitigations:\n\nRisk: The API key can act on real connected social accounts.\n\nMitigation: Use a dedicated key and do not expose it in posts, logs, or URLs.\n\nRisk: Raw curl examples can change public content without the helper's approval gate.\n\nMitigation: Prefer the bundled helper; require explicit approval of the exact provider, channel, content, media, target, and time before any publish, schedule, cancel, delete, or upload.\n\n## Reference(s):\n\n- [ClawHub skill listing](https://clawhub.ai/postnextio/skills/postnext-social-manager)\n- [Posts and scheduling reference](artifact/references/posts.md)\n- [Media uploads reference](artifact/references/media.md)\n- [Connected channels reference](artifact/references/channels.md)\n- [Analytics reference](artifact/references/analytics.md)\n\n## Skill Output:\n\n**Output Type(s):** [Text, Markdown, Shell commands, Guidance]\n\n**Output Format:** [Text or Markdown with optional shell commands and account results]\n\n**Output Parameters:** [1D]\n\n**Other Properties Related to Output:** [Can include post previews, scheduling outcomes, media URLs, and analytics summaries.]\n\n## Skill Version(s):\n\n0.1.5 (source: ClawHub release metadata)\n\n## Ethical Considerations:\n\nUsers should evaluate whether this skill is appropriate for their environment, review any generated or modified files before relying on them, and apply their organization's safety, security, and compliance requirements before deployment.\n\nFile v0.1.5:LICENSE\n\nMIT License\n\nCopyright (c) 2026 PostNext\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n\nArchive v0.1.4: 12 files, 21675 bytes\n\nFiles: LICENSE (1065b), postnext (15448b), README.md (2277b), references/analytics.md (2492b), references/channels.md (1836b), references/errors.md (2585b), references/media.md (3147b), references/posts.md (4809b), skill-card.md (2049b), SKILL.md (5164b), tests/confirm.sh (4044b), _meta.json (142b)\n\nFile v0.1.4:SKILL.md\n\n---\nname: postnext-social-manager\ndescription: Manage social media through PostNext - schedule, publish, and analyze posts across Twitter/X, Instagram, LinkedIn, Threads, YouTube, TikTok, and Bluesky via the PostNext public API. Use when the user wants to draft, schedule, or publish social posts, upload media for posts, check what is queued, cancel a scheduled post or delete a post from PostNext, read social analytics (engagement, best time to post, audience growth) or refresh them with an analytics sync, and has a PostNext account and API key. Publishing, scheduling, cancelling, deleting and uploading always preview first and need the user's approval.\nallowed-tools: Bash(./postnext:*), Bash(curl:*), Bash(jq:*), Read\nmetadata:\n  requires:\n    bins: [curl, jq]\n    env: [POSTNEXT_API_KEY]\n---\n\n# PostNext Social Manager\n\nDrive a PostNext account programmatically: connect channels, upload media, compose and schedule or publish posts across platforms, and read analytics. Wraps the PostNext public REST API with an API key.\n\n## Setup\n\n1. The user creates an API key at https://postnext.io/account/api-keys (shown only once).\n2. Export it: `export POSTNEXT_API_KEY=<key>`\n3. Base URL is `https://api-app.postnext.io`. Auth header on every request: `x-api-key: $POSTNEXT_API_KEY`.\n\nTwo ways to use this skill:\n- **Helper (recommended where a shell is available):** the bundled `postnext` script (needs `curl` + `jq`). It handles the fiddly, error-prone parts for you. Run `./postnext help`.\n- **Raw curl (works anywhere, including no-filesystem sandboxes):** every operation has a copy-paste curl recipe in `references/`.\n\n## The core loop\n\n1. **Connect** (one-time, done by the user in the PostNext web app - there is no API to connect a channel). Confirm what is connected: `postnext channels`.\n2. **Upload media** if the post has an image or video: `postnext upload ./clip.mp4` prints an asset URL (or pass the file straight to `--media` in step 3).\n3. **Compose + publish now** or **schedule**:\n   - `postnext post --provider twitter --text \"...\" --media ./a.png`\n   - `postnext schedule --provider instagram --text \"...\" --media ./a.jpg --at 2026-07-25T14:00:00Z`\n\n   Every command that changes the account (`post`, `schedule`, `cancel`, `delete`, `upload`) first prints a **preview and sends nothing** (exit code 3). Show the preview to the user. Only after they approve it, run the identical command again with `--confirm <code>` from the preview. The code covers that exact operation: if the text, media, time or target changes, preview again and get a fresh approval.\n4. **Measure**: `postnext analytics overview`, `postnext analytics best-time twitter`, `postnext results`.\n\n## Safety\n\n- The API key acts on the user's real social accounts. Use a dedicated key, and never paste it into posts, logs or URLs.\n- Confirm with the user before any publish, schedule, cancel, delete or upload. The helper enforces this: without the `--confirm` code from its own preview it sends nothing. Never pass a code the user has not approved, and never reuse one for a changed operation.\n- `delete` removes the post from PostNext only. It does not remove an already-published post from the platform.\n- The helper only talks to `https://api-app.postnext.io`. Do not point raw curl recipes at any other host with the key.\n\n## Four traps this skill exists to prevent\n\nThe PostNext API has four behaviors that silently produce broken posts. The `postnext` helper handles all four; if you use raw curl, you must handle them yourself (see `references/posts.md` and `references/media.md`).\n\n1. **Key posts by the bare provider name** (`twitter`), never `twitter:@handle`. A colon key is rejected at publish. Select the account with the entry's `providerId` + `channelName`.\n2. **Attach media by its URL**, never by `assetId`. A post that carries only `assetIds` publishes with no media and no error. Put the uploaded `asset.url` into `content.media`.\n3. **`providerId` is the connection's `providerId`** (the platform-native account id from `GET /api/connections`), not `uniqueId`. A wrong or missing `providerId` fails at the publish worker silently.\n4. **Only twitter and threads thread.** An array of posts sent to any other provider is silently reduced to the first item.\n\n## When to read which reference\n\n| Task | Read |\n|------|------|\n| Build a post, threads, per-provider rules, char limits | `references/posts.md` |\n| Upload an image or video, size routing, the 3-step flow | `references/media.md` |\n| List or check connected channels, resolve `providerId` | `references/channels.md` |\n| Read analytics, best time to post, sync | `references/analytics.md` |\n| Response envelope shapes, error codes, rate limits | `references/errors.md` |\n\n## Known limitations\n\n- No channel connect over the API (browser-only in PostNext).\n- No team switching: the key operates on its account's default team.\n- One entry per provider per request (no multi-account-per-provider in a single post).\n- Analytics and per-post metrics require PUBLISHED posts.\n- Not supported by this skill: WordPress/blog posts, brand-profile edits, team management (use the PostNext MCP or web app).\n\nFile v0.1.4:README.md\n\n# postnext-social-manager\n\nA Claude Agent Skill for managing social media through [PostNext](https://postnext.io). It lets an agent connect-aware, upload media, compose, schedule, and publish posts across Twitter/X, Instagram, LinkedIn, Threads, YouTube, TikTok, and Bluesky, and read analytics - all via the PostNext public API with a single API key.\n\n## Install\n\nCopy this directory into your skills location (for Claude Code, a `skills/` directory the harness scans), then set your key:\n\n```bash\nexport POSTNEXT_API_KEY=<your key>   # create at https://postnext.io/account/api-keys\n```\n\nThe bundled `postnext` helper needs `curl` and `jq`. In an environment with no shell (for example claude.ai), the skill still works: every operation has a raw curl recipe in `references/`.\n\n## Quick start\n\n```bash\n./postnext channels                                   # what is connected\n./postnext post --provider twitter --text \"Hello\" --media ./hero.png   # prints a preview, sends nothing\n./postnext post --provider twitter --text \"Hello\" --media ./hero.png --confirm <code>   # after the user approves\n./postnext schedule --provider instagram --text \"Launch\" --media ./a.jpg --at 2026-07-25T14:00:00Z\n./postnext analytics best-time twitter\n```\n\nRun `./postnext help` for the full command list. `post`, `schedule`, `cancel`, `delete` and `upload` always preview first and need the `--confirm` code from that preview to run. `bash tests/confirm.sh` checks this offline.\n\n## What is in here\n\n| Path | What |\n|------|------|\n| `SKILL.md` | Skill entry point: setup, the core loop, and a router to the references. |\n| `postnext` | Bash helper (curl + jq) that handles the API's error-prone parts. |\n| `references/` | Per-area docs with raw curl recipes: posts, media, channels, analytics, errors. |\n| `tests/confirm.sh` | Proves the five account-changing commands send nothing without a matching `--confirm` code. |\n| `docs/` | Design spec. |\n\n## Scope\n\nCovers the PostNext social publishing and analytics loop. Out of scope (use the PostNext MCP at `mcp.postnext.io` or the web app): connecting channels, WordPress/blog posts, brand-profile edits, and team management.\n\n## License\n\nMIT. See `LICENSE`. Not affiliated with post-bridge; inspired by the shape of its social-manager skill.\n\nFile v0.1.4:_meta.json\n\n{\n  \"ownerId\": \"kn7cy875pa1wnbqcte11mew4kd8ay84v\",\n  \"slug\": \"postnext-social-manager\",\n  \"version\": \"0.1.4\",\n  \"publishedAt\": 1791266949539\n}\n\nFile v0.1.4:references/analytics.md\n\n# Analytics\n\nAll analytics endpoints return `{success, data}`. Several require a paid tier (noted). Metrics exist only for **PUBLISHED** posts - per-post calls error on drafts or scheduled posts.\n\n## Freshness\n\nStored metrics are periodically snapshotted. To pull fresh numbers on demand:\n\n`POST /api/v1/analytics/sync` (paid) -> `{synced, summary:{twitterSnapped, instagramSnapped, tiktokSnapped, linkedinSnapped}}`. Rate-limited and bounded by per-platform caps. Call it before reading if you need current data.\n\n## Endpoints\n\n| Endpoint | Purpose | Params |\n|----------|---------|--------|\n| `GET /api/v1/analytics` | last-7-day counts of posts/ideas/assets | - |\n| `GET /api/v1/analytics/overview` | totals (impressions, reach, likes, comments, engagementRate) + vsPrevious | `channel`, `period` |\n| `GET /api/v1/analytics/posts` | per-post rows | `channel`, `period`, `sort=engagement|impressions|likes|recent` |\n| `GET /api/v1/analytics/daily` | per-day activity counts | `days` (1-60) |\n| `GET /api/v1/analytics/audience-growth` | follower time series | `channel`, `period` |\n| `GET /api/v1/analytics/engagement-over-time` (paid) | engagement rate per day | `channel`, `period` |\n| `GET /api/v1/analytics/best-time-to-post` (paid) | ranked posting slots from your own performance | `platform` (required), `timezone`, `topN` |\n| `GET /api/v1/analytics/post/{postId}` | latest metrics, one row per provider | - |\n| `GET /api/v1/analytics/post/{postId}/daily` | daily snapshots + day-over-day deltas | - |\n\n`period` is one of `7d`, `30d`, `90d`, `all` (default `30d`). `channel` filters to one provider/channel id; omit or `all` for no filter.\n\n## Capability flag\n\n`overview`, `posts`, and `engagement-over-time` may include a `capability` field: `ok`, `insights_unavailable`, `api_tier_limited`, `no_data_yet`, or `unsupported` (it can also be `null`/absent, which means normal). If it is present and not `ok`, tell the user why the numbers are thin (for example a platform whose API tier does not expose insights) rather than presenting zeros as real.\n\n`engagementRate` is a fraction (multiply by 100 for a percentage).\n\n## Examples\n\n```bash\nBASE=https://api-app.postnext.io\ncurl -sS \"$BASE/api/v1/analytics/overview?period=30d\" -H \"x-api-key: $POSTNEXT_API_KEY\"\ncurl -sS \"$BASE/api/v1/analytics/best-time-to-post?platform=instagram&timezone=Europe/Bucharest&topN=5\" -H \"x-api-key: $POSTNEXT_API_KEY\"\ncurl -sS -X POST \"$BASE/api/v1/analytics/sync\" -H \"x-api-key: $POSTNEXT_API_KEY\"\n```\n\nFile v0.1.4:references/channels.md\n\n# Channels (connections)\n\nChannels are connected in the PostNext web app - there is no API to start an OAuth connection. This skill only lists and reads them.\n\n## List connected channels\n\n`GET /api/connections` returns a **bare JSON array** (no envelope). Each item:\n\n```jsonc\n{\n  \"provider\": \"twitter\",           // one of twitter/instagram/linkedin/threads/youtube/tiktok/bluesky\n  \"channelName\": \"@yourbrand\",     // the connected handle - use this in a post's channelName\n  \"providerId\": \"tw_9f3a1c20e5\",   // platform-native account id - use this in a post's providerId\n  \"uniqueId\": \"...\",                 // internal UUID - do NOT use for posting\n  \"requiresAttention\": true,       // true only when a token refresh actually failed (re-auth needed)\n  \"isActive\": true,\n  \"avatar\": \"...\", \"createdAt\": \"...\", \"tokenExpiry\": \"...\"\n}\n```\n\n## Resolving providerId for a post (trap #3)\n\nTo post to a channel, pick the connection by matching `provider` (and `channelName` if the user named one), then copy its **`providerId`** and `channelName` into the post entry. Never use `uniqueId` - the publish worker needs the platform-native `providerId` or it fails silently.\n\n```bash\n# providerId + channelName for a given provider\ncurl -sS https://api-app.postnext.io/api/connections -H \"x-api-key: $POSTNEXT_API_KEY\" \\\n  | jq -r '.[] | select(.provider==\"twitter\") | \"\\(.channelName)\\t\\(.providerId)\"'\n```\n\nUse `requiresAttention == true` (not `tokenExpiry`) to decide whether a channel needs the user to re-authenticate in the web app before it can publish.\n\n## Check one channel\n\n`GET /api/connections/{provider}/{channelName}/check` returns a flat status object whose HTTP status mirrors the `status` field: `active` -> 200, `expired` -> 401, `invalid` -> 404.\n\n```jsonc\n{ \"status\": \"active\", \"message\": \"...\", \"expiresAt\": \"...\" }\n```\n\nFile v0.1.4:references/errors.md\n\n# Response shapes and errors\n\n## Success envelopes are inconsistent - check per endpoint\n\n| Shape | Endpoints |\n|-------|-----------|\n| `{success, data}` | most Posts, all Analytics, asset create-url/complete/get, single-post GET |\n| `{success, data, meta}` | `GET /api/posts` when `?page`/`?limit` is present |\n| `{success, data, pagination}` | `GET /api/assets/mine` (pagination is top-level, not `meta`) |\n| **bare array** | `GET /api/connections` |\n| **flat object, no wrapper** | `GET /api/v1/account`; connection `/check`; `upload/single` and `upload/url` (`{message, asset}`) |\n\nWhen parsing, do not assume a `data` key. The `postnext` helper unwraps `data` only when present.\n\n## Error shapes - there are three\n\n1. Standard: `{ \"success\": false, \"error\": { \"message\": \"...\" } }` (validation, quota 403).\n2. String variant: `{ \"success\": false, \"error\": \"Authentication required\" }` (401, bad/missing key) or `{ \"success\": false, \"error\": \"Analytics is a paid feature\", \"code\": \"ANALYTICS_PAID_ONLY\" }` (paid-tier gates). Here `error` is a string, not an object.\n3. Payment (402): a separate top-level shape:\n   ```jsonc\n   { \"error\": \"PAYMENT_REQUIRED\", \"code\": \"SUBSCRIPTION_INACTIVE\",\n     \"status\": \"past_due\", \"currency\": \"usd\",\n     \"actions\": { \"retryUrl\": \"...\", \"updateCardUrl\": \"...\", \"downgradeUrl\": \"...\" } }\n   ```\n\n## Status codes\n\n| Code | Meaning | What to tell the user |\n|------|---------|-----------------------|\n| 401 | bad or missing `x-api-key` | check the key |\n| 402 | subscription in a payment-failure state | fix billing (use `actions.updateCardUrl`) |\n| 403 | posting not allowed on the plan, or monthly post quota reached | upgrade or wait for the quota to reset |\n| 413 | media over the size cap (50 MB presigned) | use the multipart single path, or shrink |\n| 415 | uploaded bytes do not match the declared type | re-upload with the correct contentType |\n| 422 | unsupported media for platform (e.g. video on LinkedIn) | remove the video or pick another channel |\n| 429 | rate limited | back off and retry |\n\n## Known quirks (live-verified)\n\n- **GET a missing or just-deleted post returns HTTP 500**, not 404: `{\"success\":false,\"error\":\"Internal Server Error\",\"message\":\"Post group not found\"}`. Treat a 500 whose `message` is `Post group not found` as not-found.\n- A 500 whose body still parses as `{success:false, ..., message}` is a handled not-found/edge case, not necessarily an outage.\n\n## Rate limits\n\nRoughly: standard 100 requests / 15 min; social endpoints ~50 / 15 min; auth ~5 / 60 min. On a 429, pause and retry rather than hammering.\n\nFile v0.1.4:references/media.md\n\n# Media\n\n> Safety: upload only files the user asked to attach (images and video). Use a dedicated API key and never echo it. The `postnext` helper refuses other file types.\n\nUpload an image or video, then put the returned **`asset.url`** into a post's `content.media[].url`. The `assetId` is not used by the publish path.\n\n## Which upload path\n\n| Case | Endpoint | Cap | Needs `userId`? |\n|------|----------|-----|-----------------|\n| image or file <= 50 MB, one of jpeg/png/webp/gif/mp4/webm | 3-step presigned (`create-url` -> PUT -> `complete`) | 50 MB | no (from key) |\n| large video (> 50 MB, up to 500 MB) or other type | multipart `POST /api/assets/upload/single` | 500 MB | **yes** = account `uniqueId` |\n| a remote image URL | `POST /api/assets/upload/url` | 25 MB | **yes**; **image only** (a video URL is stored as .jpg) |\n\nGet the account `uniqueId` from `GET /api/v1/account` (`.uniqueId`).\n\n## 3-step presigned flow (preferred for images and small video)\n\n```bash\nBASE=https://api-app.postnext.io\n# 1. request an upload URL (declares type + exact byte size; pre-charges storage quota)\nINIT=$(curl -sS -X POST $BASE/api/assets/upload/create-url \\\n  -H \"x-api-key: $POSTNEXT_API_KEY\" -H 'Content-Type: application/json' \\\n  -d '{\"filename\":\"hero.png\",\"contentType\":\"image/png\",\"sizeBytes\":48213}')\nUPLOAD_URL=$(echo \"$INIT\" | jq -r .data.uploadUrl)\nUPLOAD_ID=$(echo \"$INIT\" | jq -r .data.uploadId)\n\n# 2. PUT the bytes. Content-Type MUST equal the declared contentType or S3 rejects the signature.\ncurl -sS -X PUT \"$UPLOAD_URL\" -H 'Content-Type: image/png' --data-binary @hero.png\n\n# 3. finalize -> returns the asset; use .data.url in the post\ncurl -sS -X POST $BASE/api/assets/upload/complete \\\n  -H \"x-api-key: $POSTNEXT_API_KEY\" -H 'Content-Type: application/json' \\\n  -d \"{\\\"uploadId\\\":\\\"$UPLOAD_ID\\\"}\" | jq -r .data.url\n```\n\n`contentType` allow-list (presigned): `image/jpeg, image/png, image/webp, image/gif, video/mp4, video/webm`. `sizeBytes` must be the true byte size and <= 50 MB (else 413). `complete` verifies the bytes by magic number (415 on mismatch) and reconciles the storage charge to the real size.\n\n## Multipart single (large video)\n\n```bash\nUID=$(curl -sS https://api-app.postnext.io/api/v1/account -H \"x-api-key: $POSTNEXT_API_KEY\" | jq -r .uniqueId)\ncurl -sS -X POST https://api-app.postnext.io/api/assets/upload/single \\\n  -H \"x-api-key: $POSTNEXT_API_KEY\" \\\n  -F \"file=@big.mp4;type=video/mp4\" -F \"userId=$UID\" | jq -r .asset.url\n```\nNote the different response shape: single/url return a flat `{message, asset}` (no `data` wrapper); presigned `complete` returns `{success, data: asset}`.\n\n## Then attach it\n\n```jsonc\n\"content\": { \"text\": \"...\", \"media\": [{ \"url\": \"<asset.url>\", \"type\": \"IMAGE\" }] }\n```\n`type` is `IMAGE`, `VIDEO`, or `GIF`. For a multi-image Instagram carousel, verify whether a top-level `mediaType: \"CAROUSEL\"` is also required (unverified).\n\n## Manage assets\n\n- `GET /api/assets/mine?page=1&limit=50` -> `{success, data, pagination}` (pagination is top-level).\n- `GET /api/assets/{assetId}` -> `{success, data}`.\n\nDeleting assets is out of scope for this skill; use the PostNext web app.\n\nFile v0.1.4:references/posts.md\n\n# Posts\n\n> Safety: the API key posts to real accounts. Use a dedicated key, never echo it, and confirm with the user before any publish, schedule, cancel or delete.\n\nCreate a post by sending a `providers` map. Each key is a **bare provider name** and each value is one channel's entry.\n\n## Request shape (CreatePostRequest)\n\n```jsonc\n{\n  \"providers\": {\n    \"twitter\": {                              // BARE name. NOT \"twitter:@handle\".\n      \"channelName\": \"@yourbrand\",            // the real connected handle (from GET /connections)\n      \"providerId\":  \"tw_9f3a1c20e5\",         // connection.providerId (NOT uniqueId)\n      \"content\": { \"text\": \"We shipped v2.\",\n                   \"media\": [{ \"url\": \"https://cdn.postnext.io/assets/hero.png\", \"type\": \"IMAGE\" }],\n                   \"hashtags\": [\"launch\"] },\n      \"firstComment\": \"...\"                   // instagram + linkedin ONLY; ignored elsewhere\n    }\n  },\n  \"title\": \"Launch\",                          // optional, internal label\n  \"tags\": [\"launch\"]                          // optional\n}\n```\n\n`providerId` and `channelName` come from `GET /api/connections` (see `channels.md`). Resolve them by matching `provider` + `channelName`. A wrong/missing `providerId` makes the publish worker reject the row silently.\n\n## Three ways to send it\n\n| Goal | Endpoint |\n|------|----------|\n| Save a draft | `POST /api/posts` |\n| Publish immediately | `POST /api/posts/publish` |\n| Schedule | `POST /api/posts/schedule` with `\"scheduledAt\": \"<future ISO8601>\"` |\n\nExisting post by id: `POST /api/posts/{postId}/publish`, `POST /api/posts/{postId}/schedule`, `POST /api/posts/{postId}/cancel-schedule` (back to draft), `DELETE /api/posts/{groupId}` (removes from PostNext; does not delete an already-published post from the platform).\n\n> Safety: every call in this section changes the user's account. Before any publish, schedule, cancel or delete, show the user the exact provider, channel, text, media, time and post id, and wait for their explicit yes. The `postnext` helper enforces this with a preview and a `--confirm` code; with raw curl it is on you.\n\n`userId` and `teamId` are derived from the key - never send them.\n\nNote: fetching a deleted or non-existent post (`GET /api/posts/{id}`) returns HTTP 500 with `message: \"Post group not found\"`, not 404 (see `errors.md`).\n\n## Single post vs thread\n\n`content` is either an object (single post) or an array (thread).\n\n**Single** (`LegacyPostContent`):\n```jsonc\n{ \"text\": \"...\", \"media\": [{ \"url\": \"<asset.url>\", \"type\": \"IMAGE|VIDEO|GIF\" }],\n  \"hashtags\": [\"a\"], \"instagramPostType\": \"feed|post|story|reel\" }\n```\n\n**Thread** (array, `twitter` and `threads` ONLY):\n```jsonc\n[ { \"order\": 1, \"text\": \"1/\", \"media\": [] },\n  { \"order\": 2, \"text\": \"2/\" } ]\n```\n`order` must be exactly 1..N with no gaps. Sending an array to any other provider silently keeps only element `[0]`.\n\nAttach media by URL (`content.media[].url` or the legacy `content.mediaUrls[]`). `assetIds` is never dereferenced - a post with only `assetIds` has no media.\n\n## Per-provider rules enforced at create time\n\n| Provider | Rules |\n|----------|-------|\n| instagram | text or media required; caption <= 2200; <= 30 hashtags; `firstComment` <= 2200; `instagramPostType` in feed/post/story/reel |\n| bluesky | text or image; <= 300 graphemes; <= 4 images; **no video** |\n| linkedin | **no video** (422 UNSUPPORTED_MEDIA_FOR_PLATFORM); IMAGE + GIF ok; `firstComment` <= 1250 |\n| twitter | text required; no char limit enforced at create; polls 2-4 options |\n| threads, tiktok | no create-time content validator |\n| youtube | publish path via this route is UNVERIFIED - test before relying on it |\n\n## Scheduling\n\n`scheduledAt` must be a future ISO8601 datetime (past or now is a 400). `timezone` only affects recurrence and display; it never shifts the fire time.\n\n**Idempotency:** raw `POST /api/posts/{postId}/schedule` will create a duplicate job if called twice, causing a double-publish. Before re-scheduling, list the post's current state and confirm it is not already scheduled. The `postnext` helper refuses past times, and its `--confirm` code covers one exact operation, but it does not detect an existing job for the same post.\n\n## Raw curl\n\n```bash\n# publish now\ncurl -sS -X POST https://api-app.postnext.io/api/posts/publish \\\n  -H \"x-api-key: $POSTNEXT_API_KEY\" -H 'Content-Type: application/json' \\\n  -d '{\"providers\":{\"twitter\":{\"channelName\":\"@brand\",\"providerId\":\"tw_123\",\n       \"content\":{\"text\":\"hi\",\"media\":[{\"url\":\"https://cdn/x.png\",\"type\":\"IMAGE\"}]}}}}'\n\n# list posts (optionally by status)\ncurl -sS \"https://api-app.postnext.io/api/posts?status=SCHEDULED\" -H \"x-api-key: $POSTNEXT_API_KEY\"\n\n# per-channel publish outcomes\ncurl -sS https://api-app.postnext.io/api/posts/results -H \"x-api-key: $POSTNEXT_API_KEY\"\n```\n\nFile v0.1.4:skill-card.md\n\n## Description:\n\nHelps agents draft, publish, schedule, and analyze social posts across PostNext-connected accounts, with previews before account-changing actions.\n\nThis skill is ready for commercial/non-commercial use.\n\n## Publisher:\n\n[postnextio](https://clawhub.ai/user/postnextio)\n\n### License/Terms of Use:\n\nMIT\n\n## Use Case:\n\nSocial media managers and creators use this skill to prepare and manage posts, upload media, check connected channels, and review analytics through a PostNext account and API key.\n\n### Deployment Geography for Use:\n\nGlobal\n\n## Known Risks and Mitigations:\n\nRisk: An API key can affect real connected social accounts.\n\nMitigation: Use a dedicated key and keep it out of posts, logs, and URLs.\n\nRisk: Publishing, scheduling, cancellation, deletion, or media uploads may have unintended effects; raw curl commands do not enforce the helper's confirmation gate.\n\nMitigation: Review the exact target and content in each preview and obtain explicit approval before acting, especially when using raw curl.\n\n## Reference(s):\n\n- [ClawHub skill listing](https://clawhub.ai/postnextio/skills/postnext-social-manager)\n- [PostNext API key setup](https://postnext.io/account/api-keys)\n- [Posts reference](references/posts.md)\n- [Media reference](references/media.md)\n- [Channels reference](references/channels.md)\n- [Analytics reference](references/analytics.md)\n- [Errors reference](references/errors.md)\n\n## Skill Output:\n\n**Output Type(s):** [Text, Markdown, Shell commands, Guidance]\n\n**Output Format:** [Markdown with inline shell commands and post previews]\n\n**Output Parameters:** [1D]\n\n**Other Properties Related to Output:** [Account-changing actions require user approval after a preview.]\n\n## Skill Version(s):\n\n0.1.4 (source: ClawHub release metadata)\n\n## Ethical Considerations:\n\nUsers should evaluate whether this skill is appropriate for their environment, review any generated or modified files before relying on them, and apply their organization's safety, security, and compliance requirements before deployment.\n\nFile v0.1.4:LICENSE\n\nMIT License\n\nCopyright (c) 2026 PostNext\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n\nArchive v0.1.3: 12 files, 21445 bytes\n\nFiles: LICENSE (1065b), postnext (15367b), README.md (2277b), references/analytics.md (2492b), references/channels.md (1836b), references/errors.md (2635b), references/media.md (3135b), references/posts.md (4390b), skill-card.md (2242b), SKILL.md (4895b), tests/confirm.sh (4065b), _meta.json (142b)\n\nFile v0.1.3:SKILL.md\n\n---\nname: postnext-social-manager\ndescription: Manage social media through PostNext - schedule, publish, and analyze posts across Twitter/X, Instagram, LinkedIn, Threads, YouTube, TikTok, and Bluesky via the PostNext public API. Use when the user wants to draft, schedule, or publish social posts, upload media for posts, check what is queued, or read social analytics (engagement, best time to post, audience growth) and has a PostNext account and API key.\nmetadata:\n  requires:\n    bins: [curl, jq]\n    env: [POSTNEXT_API_KEY]\n---\n\n# PostNext Social Manager\n\nDrive a PostNext account programmatically: connect channels, upload media, compose and schedule or publish posts across platforms, and read analytics. Wraps the PostNext public REST API with an API key.\n\n## Setup\n\n1. The user creates an API key at https://postnext.io/account/api-keys (shown only once).\n2. Export it: `export POSTNEXT_API_KEY=<key>`\n3. Base URL is `https://api-app.postnext.io`. Auth header on every request: `x-api-key: $POSTNEXT_API_KEY`.\n\nTwo ways to use this skill:\n- **Helper (recommended where a shell is available):** the bundled `postnext` script (needs `curl` + `jq`). It handles the fiddly, error-prone parts for you. Run `./postnext help`.\n- **Raw curl (works anywhere, including no-filesystem sandboxes):** every operation has a copy-paste curl recipe in `references/`.\n\n## The core loop\n\n1. **Connect** (one-time, done by the user in the PostNext web app - there is no API to connect a channel). Confirm what is connected: `postnext channels`.\n2. **Upload media** if the post has an image or video: `postnext upload ./clip.mp4` prints an asset URL (or pass the file straight to `--media` in step 3).\n3. **Compose + publish now** or **schedule**:\n   - `postnext post --provider twitter --text \"...\" --media ./a.png`\n   - `postnext schedule --provider instagram --text \"...\" --media ./a.jpg --at 2026-07-25T14:00:00Z`\n\n   Every command that changes the account (`post`, `schedule`, `cancel`, `delete`, `upload`) first prints a **preview and sends nothing** (exit code 3). Show the preview to the user. Only after they approve it, run the identical command again with `--confirm <code>` from the preview. The code covers that exact operation: if the text, media, time or target changes, preview again and get a fresh approval.\n4. **Measure**: `postnext analytics overview`, `postnext analytics best-time twitter`, `postnext results`.\n\n## Safety\n\n- The API key acts on the user's real social accounts. Use a dedicated key, and never paste it into posts, logs or URLs.\n- Confirm with the user before any publish, schedule, cancel, delete or upload. The helper enforces this: without the `--confirm` code from its own preview it sends nothing. Never pass a code the user has not approved, and never reuse one for a changed operation.\n- `delete` removes the post from PostNext only. It does not remove an already-published post from the platform.\n- The helper only talks to `https://api-app.postnext.io`. Do not point raw curl recipes at any other host with the key.\n\n## Four traps this skill exists to prevent\n\nThe PostNext API has four behaviors that silently produce broken posts. The `postnext` helper handles all four; if you use raw curl, you must handle them yourself (see `references/posts.md` and `references/media.md`).\n\n1. **Key posts by the bare provider name** (`twitter`), never `twitter:@handle`. A colon key is rejected at publish. Select the account with the entry's `providerId` + `channelName`.\n2. **Attach media by its URL**, never by `assetId`. A post that carries only `assetIds` publishes with no media and no error. Put the uploaded `asset.url` into `content.media`.\n3. **`providerId` is the connection's `providerId`** (the platform-native account id from `GET /api/connections`), not `uniqueId`. A wrong or missing `providerId` fails at the publish worker silently.\n4. **Only twitter and threads thread.** An array of posts sent to any other provider is silently reduced to the first item.\n\n## When to read which reference\n\n| Task | Read |\n|------|------|\n| Build a post, threads, per-provider rules, char limits | `references/posts.md` |\n| Upload an image or video, size routing, the 3-step flow | `references/media.md` |\n| List or check connected channels, resolve `providerId` | `references/channels.md` |\n| Read analytics, best time to post, sync | `references/analytics.md` |\n| Response envelope shapes, error codes, rate limits | `references/errors.md` |\n\n## Known limitations\n\n- No channel connect over the API (browser-only in PostNext).\n- No team switching: the key operates on its account's default team.\n- One entry per provider per request (no multi-account-per-provider in a single post).\n- Analytics and per-post metrics require PUBLISHED posts.\n- Not supported by this skill: WordPress/blog posts, brand-profile edits, team management (use the PostNext MCP or web app).\n\nFile v0.1.3:README.md\n\n# postnext-social-manager\n\nA Claude Agent Skill for managing social media through [PostNext](https://postnext.io). It lets an agent connect-aware, upload media, compose, schedule, and publish posts across Twitter/X, Instagram, LinkedIn, Threads, YouTube, TikTok, and Bluesky, and read analytics - all via the PostNext public API with a single API key.\n\n## Install\n\nCopy this directory into your skills location (for Claude Code, a `skills/` directory the harness scans), then set your key:\n\n```bash\nexport POSTNEXT_API_KEY=<your key>   # create at https://postnext.io/account/api-keys\n```\n\nThe bundled `postnext` helper needs `curl` and `jq`. In an environment with no shell (for example claude.ai), the skill still works: every operation has a raw curl recipe in `references/`.\n\n## Quick start\n\n```bash\n./postnext channels                                   # what is connected\n./postnext post --provider twitter --text \"Hello\" --media ./hero.png   # prints a preview, sends nothing\n./postnext post --provider twitter --text \"Hello\" --media ./hero.png --confirm <code>   # after the user approves\n./postnext schedule --provider instagram --text \"Launch\" --media ./a.jpg --at 2026-07-25T14:00:00Z\n./postnext analytics best-time twitter\n```\n\nRun `./postnext help` for the full command list. `post`, `schedule`, `cancel`, `delete` and `upload` always preview first and need the `--confirm` code from that preview to run. `bash tests/confirm.sh` checks this offline.\n\n## What is in here\n\n| Path | What |\n|------|------|\n| `SKILL.md` | Skill entry point: setup, the core loop, and a router to the references. |\n| `postnext` | Bash helper (curl + jq) that handles the API's error-prone parts. |\n| `references/` | Per-area docs with raw curl recipes: posts, media, channels, analytics, errors. |\n| `tests/confirm.sh` | Proves the five account-changing commands send nothing without a matching `--confirm` code. |\n| `docs/` | Design spec. |\n\n## Scope\n\nCovers the PostNext social publishing and analytics loop. Out of scope (use the PostNext MCP at `mcp.postnext.io` or the web app): connecting channels, WordPress/blog posts, brand-profile edits, and team management.\n\n## License\n\nMIT. See `LICENSE`. Not affiliated with post-bridge; inspired by the shape of its social-manager skill.\n\nFile v0.1.3:_meta.json\n\n{\n  \"ownerId\": \"kn7cy875pa1wnbqcte11mew4kd8ay84v\",\n  \"slug\": \"postnext-social-manager\",\n  \"version\": \"0.1.3\",\n  \"publishedAt\": 1791265878363\n}\n\nFile v0.1.3:references/analytics.md\n\n# Analytics\n\nAll analytics endpoints return `{success, data}`. Several require a paid tier (noted). Metrics exist only for **PUBLISHED** posts - per-post calls error on drafts or scheduled posts.\n\n## Freshness\n\nStored metrics are periodically snapshotted. To pull fresh numbers on demand:\n\n`POST /api/v1/analytics/sync` (paid) -> `{synced, summary:{twitterSnapped, instagramSnapped, tiktokSnapped, linkedinSnapped}}`. Rate-limited and bounded by per-platform caps. Call it before reading if you need current data.\n\n## Endpoints\n\n| Endpoint | Purpose | Params |\n|----------|---------|--------|\n| `GET /api/v1/analytics` | last-7-day counts of posts/ideas/assets | - |\n| `GET /api/v1/analytics/overview` | totals (impressions, reach, likes, comments, engagementRate) + vsPrevious | `channel`, `period` |\n| `GET /api/v1/analytics/posts` | per-post rows | `channel`, `period`, `sort=engagement|impressions|likes|recent` |\n| `GET /api/v1/analytics/daily` | per-day activity counts | `days` (1-60) |\n| `GET /api/v1/analytics/audience-growth` | follower time series | `channel`, `period` |\n| `GET /api/v1/analytics/engagement-over-time` (paid) | engagement rate per day | `channel`, `period` |\n| `GET /api/v1/analytics/best-time-to-post` (paid) | ranked posting slots from your own performance | `platform` (required), `timezone`, `topN` |\n| `GET /api/v1/analytics/post/{postId}` | latest metrics, one row per provider | - |\n| `GET /api/v1/analytics/post/{postId}/daily` | daily snapshots + day-over-day deltas | - |\n\n`period` is one of `7d`, `30d`, `90d`, `all` (default `30d`). `channel` filters to one provider/channel id; omit or `all` for no filter.\n\n## Capability flag\n\n`overview`, `posts`, and `engagement-over-time` may include a `capability` field: `ok`, `insights_unavailable`, `api_tier_limited`, `no_data_yet`, or `unsupported` (it can also be `null`/absent, which means normal). If it is present and not `ok`, tell the user why the numbers are thin (for example a platform whose API tier does not expose insights) rather than presenting zeros as real.\n\n`engagementRate` is a fraction (multiply by 100 for a percentage).\n\n## Examples\n\n```bash\nBASE=https://api-app.postnext.io\ncurl -sS \"$BASE/api/v1/analytics/overview?period=30d\" -H \"x-api-key: $POSTNEXT_API_KEY\"\ncurl -sS \"$BASE/api/v1/analytics/best-time-to-post?platform=instagram&timezone=Europe/Bucharest&topN=5\" -H \"x-api-key: $POSTNEXT_API_KEY\"\ncurl -sS -X POST \"$BASE/api/v1/analytics/sync\" -H \"x-api-key: $POSTNEXT_API_KEY\"\n```\n\nFile v0.1.3:references/channels.md\n\n# Channels (connections)\n\nChannels are connected in the PostNext web app - there is no API to start an OAuth connection. This skill only lists and reads them.\n\n## List connected channels\n\n`GET /api/connections` returns a **bare JSON array** (no envelope). Each item:\n\n```jsonc\n{\n  \"provider\": \"twitter\",           // one of twitter/instagram/linkedin/threads/youtube/tiktok/bluesky\n  \"channelName\": \"@yourbrand\",     // the connected handle - use this in a post's channelName\n  \"providerId\": \"tw_9f3a1c20e5\",   // platform-native account id - use this in a post's providerId\n  \"uniqueId\": \"...\",                 // internal UUID - do NOT use for posting\n  \"requiresAttention\": true,       // true only when a token refresh actually failed (re-auth needed)\n  \"isActive\": true,\n  \"avatar\": \"...\", \"createdAt\": \"...\", \"tokenExpiry\": \"...\"\n}\n```\n\n## Resolving providerId for a post (trap #3)\n\nTo post to a channel, pick the connection by matching `provider` (and `channelName` if the user named one), then copy its **`providerId`** and `channelName` into the post entry. Never use `uniqueId` - the publish worker needs the platform-native `providerId` or it fails silently.\n\n```bash\n# providerId + channelName for a given provider\ncurl -sS https://api-app.postnext.io/api/connections -H \"x-api-key: $POSTNEXT_API_KEY\" \\\n  | jq -r '.[] | select(.provider==\"twitter\") | \"\\(.channelName)\\t\\(.providerId)\"'\n```\n\nUse `requiresAttention == true` (not `tokenExpiry`) to decide whether a channel needs the user to re-authenticate in the web app before it can publish.\n\n## Check one channel\n\n`GET /api/connections/{provider}/{channelName}/check` returns a flat status object whose HTTP status mirrors the `status` field: `active` -> 200, `expired` -> 401, `invalid` -> 404.\n\n```jsonc\n{ \"status\": \"active\", \"message\": \"...\", \"expiresAt\": \"...\" }\n```\n\nFile v0.1.3:references/errors.md\n\n# Response shapes and errors\n\n## Success envelopes are inconsistent - check per endpoint\n\n| Shape | Endpoints |\n|-------|-----------|\n| `{success, data}` | most Posts, all Analytics, asset create-url/complete/get, single-post GET |\n| `{success, data, meta}` | `GET /api/posts` when `?page`/`?limit` is present |\n| `{success, data, pagination}` | `GET /api/assets/mine` (pagination is top-level, not `meta`) |\n| **bare array** | `GET /api/connections` |\n| **flat object, no wrapper** | `GET /api/v1/account`; connection `/check`; `upload/single` and `upload/url` (`{message, asset}`); `DELETE /api/assets/{id}` (`{message, success}`) |\n\nWhen parsing, do not assume a `data` key. The `postnext` helper unwraps `data` only when present.\n\n## Error shapes - there are three\n\n1. Standard: `{ \"success\": false, \"error\": { \"message\": \"...\" } }` (validation, quota 403).\n2. String variant: `{ \"success\": false, \"error\": \"Authentication required\" }` (401, bad/missing key) or `{ \"success\": false, \"error\": \"Analytics is a paid feature\", \"code\": \"ANALYTICS_PAID_ONLY\" }` (paid-tier gates). Here `error` is a string, not an object.\n3. Payment (402): a separate top-level shape:\n   ```jsonc\n   { \"error\": \"PAYMENT_REQUIRED\", \"code\": \"SUBSCRIPTION_INACTIVE\",\n     \"status\": \"past_due\", \"currency\": \"usd\",\n     \"actions\": { \"retryUrl\": \"...\", \"updateCardUrl\": \"...\", \"downgradeUrl\": \"...\" } }\n   ```\n\n## Status codes\n\n| Code | Meaning | What to tell the user |\n|------|---------|-----------------------|\n| 401 | bad or missing `x-api-key` | check the key |\n| 402 | subscription in a payment-failure state | fix billing (use `actions.updateCardUrl`) |\n| 403 | posting not allowed on the plan, or monthly post quota reached | upgrade or wait for the quota to reset |\n| 413 | media over the size cap (50 MB presigned) | use the multipart single path, or shrink |\n| 415 | uploaded bytes do not match the declared type | re-upload with the correct contentType |\n| 422 | unsupported media for platform (e.g. video on LinkedIn) | remove the video or pick another channel |\n| 429 | rate limited | back off and retry |\n\n## Known quirks (live-verified)\n\n- **GET a missing or just-deleted post returns HTTP 500**, not 404: `{\"success\":false,\"error\":\"Internal Server Error\",\"message\":\"Post group not found\"}`. Treat a 500 whose `message` is `Post group not found` as not-found.\n- A 500 whose body still parses as `{success:false, ..., message}` is a handled not-found/edge case, not necessarily an outage.\n\n## Rate limits\n\nRoughly: standard 100 requests / 15 min; social endpoints ~50 / 15 min; auth ~5 / 60 min. On a 429, pause and retry rather than hammering.\n\nFile v0.1.3:references/media.md\n\n# Media\n\n> Safety: upload only files the user asked to attach (images and video). Use a dedicated API key and never echo it. The `postnext` helper refuses other file types.\n\nUpload an image or video, then put the returned **`asset.url`** into a post's `content.media[].url`. The `assetId` is not used by the publish path.\n\n## Which upload path\n\n| Case | Endpoint | Cap | Needs `userId`? |\n|------|----------|-----|-----------------|\n| image or file <= 50 MB, one of jpeg/png/webp/gif/mp4/webm | 3-step presigned (`create-url` -> PUT -> `complete`) | 50 MB | no (from key) |\n| large video (> 50 MB, up to 500 MB) or other type | multipart `POST /api/assets/upload/single` | 500 MB | **yes** = account `uniqueId` |\n| a remote image URL | `POST /api/assets/upload/url` | 25 MB | **yes**; **image only** (a video URL is stored as .jpg) |\n\nGet the account `uniqueId` from `GET /api/v1/account` (`.uniqueId`).\n\n## 3-step presigned flow (preferred for images and small video)\n\n```bash\nBASE=https://api-app.postnext.io\n# 1. request an upload URL (declares type + exact byte size; pre-charges storage quota)\nINIT=$(curl -sS -X POST $BASE/api/assets/upload/create-url \\\n  -H \"x-api-key: $POSTNEXT_API_KEY\" -H 'Content-Type: application/json' \\\n  -d '{\"filename\":\"hero.png\",\"contentType\":\"image/png\",\"sizeBytes\":48213}')\nUPLOAD_URL=$(echo \"$INIT\" | jq -r .data.uploadUrl)\nUPLOAD_ID=$(echo \"$INIT\" | jq -r .data.uploadId)\n\n# 2. PUT the bytes. Content-Type MUST equal the declared contentType or S3 rejects the signature.\ncurl -sS -X PUT \"$UPLOAD_URL\" -H 'Content-Type: image/png' --data-binary @hero.png\n\n# 3. finalize -> returns the asset; use .data.url in the post\ncurl -sS -X POST $BASE/api/assets/upload/complete \\\n  -H \"x-api-key: $POSTNEXT_API_KEY\" -H 'Content-Type: application/json' \\\n  -d \"{\\\"uploadId\\\":\\\"$UPLOAD_ID\\\"}\" | jq -r .data.url\n```\n\n`contentType` allow-list (presigned): `image/jpeg, image/png, image/webp, image/gif, video/mp4, video/webm`. `sizeBytes` must be the true byte size and <= 50 MB (else 413). `complete` verifies the bytes by magic number (415 on mismatch) and reconciles the storage charge to the real size.\n\n## Multipart single (large video)\n\n```bash\nUID=$(curl -sS https://api-app.postnext.io/api/v1/account -H \"x-api-key: $POSTNEXT_API_KEY\" | jq -r .uniqueId)\ncurl -sS -X POST https://api-app.postnext.io/api/assets/upload/single \\\n  -H \"x-api-key: $POSTNEXT_API_KEY\" \\\n  -F \"file=@big.mp4;type=video/mp4\" -F \"userId=$UID\" | jq -r .asset.url\n```\nNote the different response shape: single/url return a flat `{message, asset}` (no `data` wrapper); presigned `complete` returns `{success, data: asset}`.\n\n## Then attach it\n\n```jsonc\n\"content\": { \"text\": \"...\", \"media\": [{ \"url\": \"<asset.url>\", \"type\": \"IMAGE\" }] }\n```\n`type` is `IMAGE`, `VIDEO`, or `GIF`. For a multi-image Instagram carousel, verify whether a top-level `mediaType: \"CAROUSEL\"` is also required (unverified).\n\n## Manage assets\n\n- `GET /api/assets/mine?page=1&limit=50` -> `{success, data, pagination}` (pagination is top-level).\n- `GET /api/assets/{assetId}` -> `{success, data}`.\n- `DELETE /api/assets/{assetId}` -> flat `{message, success}`.\n\nFile v0.1.3:references/posts.md\n\n# Posts\n\n> Safety: the API key posts to real accounts. Use a dedicated key, never echo it, and confirm with the user before any publish, schedule, cancel or delete.\n\nCreate a post by sending a `providers` map. Each key is a **bare provider name** and each value is one channel's entry.\n\n## Request shape (CreatePostRequest)\n\n```jsonc\n{\n  \"providers\": {\n    \"twitter\": {                              // BARE name. NOT \"twitter:@handle\".\n      \"channelName\": \"@yourbrand\",            // the real connected handle (from GET /connections)\n      \"providerId\":  \"tw_9f3a1c20e5\",         // connection.providerId (NOT uniqueId)\n      \"content\": { \"text\": \"We shipped v2.\",\n                   \"media\": [{ \"url\": \"https://cdn.postnext.io/assets/hero.png\", \"type\": \"IMAGE\" }],\n                   \"hashtags\": [\"launch\"] },\n      \"firstComment\": \"...\"                   // instagram + linkedin ONLY; ignored elsewhere\n    }\n  },\n  \"title\": \"Launch\",                          // optional, internal label\n  \"tags\": [\"launch\"]                          // optional\n}\n```\n\n`providerId` and `channelName` come from `GET /api/connections` (see `channels.md`). Resolve them by matching `provider` + `channelName`. A wrong/missing `providerId` makes the publish worker reject the row silently.\n\n## Three ways to send it\n\n| Goal | Endpoint |\n|------|----------|\n| Save a draft | `POST /api/posts` |\n| Publish immediately | `POST /api/posts/publish` |\n| Schedule | `POST /api/posts/schedule` with `\"scheduledAt\": \"<future ISO8601>\"` |\n\nExisting post by id: `POST /api/posts/{postId}/publish`, `POST /api/posts/{postId}/schedule`, `POST /api/posts/{postId}/cancel-schedule` (back to draft), `DELETE /api/posts/{groupId}` (removes from PostNext; does not delete an already-published post from the platform).\n\n`userId` and `teamId` are derived from the key - never send them.\n\nNote: fetching a deleted or non-existent post (`GET /api/posts/{id}`) returns HTTP 500 with `message: \"Post group not found\"`, not 404 (see `errors.md`).\n\n## Single post vs thread\n\n`content` is either an object (single post) or an array (thread).\n\n**Single** (`LegacyPostContent`):\n```jsonc\n{ \"text\": \"...\", \"media\": [{ \"url\": \"<asset.url>\", \"type\": \"IMAGE|VIDEO|GIF\" }],\n  \"hashtags\": [\"a\"], \"instagramPostType\": \"feed|post|story|reel\" }\n```\n\n**Thread** (array, `twitter` and `threads` ONLY):\n```jsonc\n[ { \"order\": 1, \"text\": \"1/\", \"media\": [] },\n  { \"order\": 2, \"text\": \"2/\" } ]\n```\n`order` must be exactly 1..N with no gaps. Sending an array to any other provider silently keeps only element `[0]`.\n\nAttach media by URL (`content.media[].url` or the legacy `content.mediaUrls[]`). `assetIds` is never dereferenced - a post with only `assetIds` has no media.\n\n## Per-provider rules enforced at create time\n\n| Provider | Rules |\n|----------|-------|\n| instagram | text or media required; caption <= 2200; <= 30 hashtags; `firstComment` <= 2200; `instagramPostType` in feed/post/story/reel |\n| bluesky | text or image; <= 300 graphemes; <= 4 images; **no video** |\n| linkedin | **no video** (422 UNSUPPORTED_MEDIA_FOR_PLATFORM); IMAGE + GIF ok; `firstComment` <= 1250 |\n| twitter | text required; no char limit enforced at create; polls 2-4 options |\n| threads, tiktok | no create-time content validator |\n| youtube | publish path via this route is UNVERIFIED - test before relying on it |\n\n## Scheduling\n\n`scheduledAt` must be a future ISO8601 datetime (past or now is a 400). `timezone` only affects recurrence and display; it never shifts the fire time.\n\n**Idempotency:** raw `POST /api/posts/{postId}/schedule` will create a duplicate job if called twice, causing a double-publish. Before re-scheduling, check the post is not already scheduled to that time (the `postnext` helper guards a +/- 60s window and refuses past-due times).\n\n## Raw curl\n\n```bash\n# publish now\ncurl -sS -X POST https://api-app.postnext.io/api/posts/publish \\\n  -H \"x-api-key: $POSTNEXT_API_KEY\" -H 'Content-Type: application/json' \\\n  -d '{\"providers\":{\"twitter\":{\"channelName\":\"@brand\",\"providerId\":\"tw_123\",\n       \"content\":{\"text\":\"hi\",\"media\":[{\"url\":\"https://cdn/x.png\",\"type\":\"IMAGE\"}]}}}}'\n\n# list posts (optionally by status)\ncurl -sS \"https://api-app.postnext.io/api/posts?status=SCHEDULED\" -H \"x-api-key: $POSTNEXT_API_KEY\"\n\n# per-channel publish outcomes\ncurl -sS https://api-app.postnext.io/api/posts/results -H \"x-api-key: $POSTNEXT_API_KEY\"\n```\n\nFile v0.1.3:skill-card.md\n\n## Description:\n\nHelps agents draft, schedule, publish, and analyze social posts across connected platforms through PostNext.\n\nThis skill is ready for commercial/non-commercial use.\n\n## Publisher:\n\n[postnextio](https://clawhub.ai/user/postnextio)\n\n### License/Terms of Use:\n\nMIT\n\n## Use Case:\n\nPeople and teams with a PostNext account and API key use this skill to manage connected social channels, prepare and publish or schedule posts, upload media, and review social analytics.\n\n### Deployment Geography for Use:\n\nGlobal\n\n## Known Risks and Mitigations:\n\nRisk: An API key can make changes to real social accounts.\n\nMitigation: Use a dedicated key and keep it out of posts, logs, and URLs.\n\nRisk: Publishing, scheduling, cancelling, deleting, or uploading can change the account; raw curl examples bypass the helper's confirmation gate.\n\nMitigation: Review each preview and get explicit approval before running any account-changing command, including raw API requests.\n\nRisk: Deleting a post from PostNext does not remove an already-published platform post.\n\nMitigation: Check the target and remove the platform post separately if needed.\n\n## Reference(s):\n\n- [PostNext Social Manager on ClawHub](https://clawhub.ai/postnextio/skills/postnext-social-manager)\n- [PostNext](https://postnext.io)\n- [Post and scheduling guidance](artifact/references/posts.md)\n- [Media upload guidance](artifact/references/media.md)\n- [Channel guidance](artifact/references/channels.md)\n- [Analytics guidance](artifact/references/analytics.md)\n- [Errors and rate limits](artifact/references/errors.md)\n\n## Skill Output:\n\n**Output Type(s):** [Text, Markdown, Shell commands, Guidance]\n\n**Output Format:** [Markdown with post previews, commands, and analytics summaries]\n\n**Output Parameters:** [1D]\n\n**Other Properties Related to Output:** [Requires a PostNext account and API key; account-changing actions require user approval.]\n\n## Skill Version(s):\n\n0.1.3 (source: ClawHub release metadata)\n\n## Ethical Considerations:\n\nUsers should evaluate whether this skill is appropriate for their environment, review any generated or modified files before relying on them, and apply their organization's safety, security, and compliance requirements before deployment.\n\nFile v0.1.3:LICENSE\n\nMIT License\n\nCopyright (c) 2026 PostNext\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n\nArchive v0.1.2: 11 files, 17909 bytes\n\nFiles: LICENSE (1065b), postnext (11502b), README.md (1836b), references/analytics.md (2492b), references/channels.md (1836b), references/errors.md (2635b), references/media.md (3135b), references/posts.md (4390b), skill-card.md (1998b), SKILL.md (4293b), _meta.json (142b)\n\nFile v0.1.2:SKILL.md\n\n---\nname: postnext-social-manager\ndescription: Manage social media through PostNext - schedule, publish, and analyze posts across Twitter/X, Instagram, LinkedIn, Threads, YouTube, TikTok, and Bluesky via the PostNext public API. Use when the user wants to draft, schedule, or publish social posts, upload media for posts, check what is queued, or read social analytics (engagement, best time to post, audience growth) and has a PostNext account and API key.\nmetadata:\n  requires:\n    bins: [curl, jq]\n    env: [POSTNEXT_API_KEY]\n---\n\n# PostNext Social Manager\n\nDrive a PostNext account programmatically: connect channels, upload media, compose and schedule or publish posts across platforms, and read analytics. Wraps the PostNext public REST API with an API key.\n\n## Setup\n\n1. The user creates an API key at https://postnext.io/account/api-keys (shown only once).\n2. Export it: `export POSTNEXT_API_KEY=<key>`\n3. Base URL is `https://api-app.postnext.io`. Auth header on every request: `x-api-key: $POSTNEXT_API_KEY`.\n\nTwo ways to use this skill:\n- **Helper (recommended where a shell is available):** the bundled `postnext` script (needs `curl` + `jq`). It handles the fiddly, error-prone parts for you. Run `./postnext help`.\n- **Raw curl (works anywhere, including no-filesystem sandboxes):** every operation has a copy-paste curl recipe in `references/`.\n\n## The core loop\n\n1. **Connect** (one-time, done by the user in the PostNext web app - there is no API to connect a channel). Confirm what is connected: `postnext channels`.\n2. **Upload media** if the post has an image or video: `postnext upload ./clip.mp4` prints an asset URL.\n3. **Compose + publish now** or **schedule**:\n   - `postnext post --provider twitter --text \"...\" --media ./a.png`\n   - `postnext schedule --provider instagram --text \"...\" --media ./a.jpg --at 2026-07-25T14:00:00Z`\n4. **Measure**: `postnext analytics overview`, `postnext analytics best-time twitter`, `postnext results`.\n\n## Safety\n\n- The API key acts on the user's real social accounts. Use a dedicated key, and never paste it into posts, logs or URLs.\n- Confirm with the user before any publish, schedule, cancel or delete. Show the provider, channel, text and time first.\n- `delete` removes the post from PostNext only. It does not remove an already-published post from the platform.\n- The helper only talks to `https://api-app.postnext.io`. Do not point raw curl recipes at any other host with the key.\n\n## Four traps this skill exists to prevent\n\nThe PostNext API has four behaviors that silently produce broken posts. The `postnext` helper handles all four; if you use raw curl, you must handle them yourself (see `references/posts.md` and `references/media.md`).\n\n1. **Key posts by the bare provider name** (`twitter`), never `twitter:@handle`. A colon key is rejected at publish. Select the account with the entry's `providerId` + `channelName`.\n2. **Attach media by its URL**, never by `assetId`. A post that carries only `assetIds` publishes with no media and no error. Put the uploaded `asset.url` into `content.media`.\n3. **`providerId` is the connection's `providerId`** (the platform-native account id from `GET /api/connections`), not `uniqueId`. A wrong or missing `providerId` fails at the publish worker silently.\n4. **Only twitter and threads thread.** An array of posts sent to any other provider is silently reduced to the first item.\n\n## When to read which reference\n\n| Task | Read |\n|------|------|\n| Build a post, threads, per-provider rules, char limits | `references/posts.md` |\n| Upload an image or video, size routing, the 3-step flow | `references/media.md` |\n| List or check connected channels, resolve `providerId` | `references/channels.md` |\n| Read analytics, best time to post, sync | `references/analytics.md` |\n| Response envelope shapes, error codes, rate limits | `references/errors.md` |\n\n## Known limitations\n\n- No channel connect over the API (browser-only in PostNext).\n- No team switching: the key operates on its account's default team.\n- One entry per provider per request (no multi-account-per-provider in a single post).\n- Analytics and per-post metrics require PUBLISHED posts.\n- Not supported by this skill: WordPress/blog posts, brand-profile edits, team management (use the PostNext MCP or web app).\n\nFile v0.1.2:README.md\n\n# postnext-social-manager\n\nA Claude Agent Skill for managing social media through [PostNext](https://postnext.io). It lets an agent connect-aware, upload media, compose, schedule, and publish posts across Twitter/X, Instagram, LinkedIn, Threads, YouTube, TikTok, and Bluesky, and read analytics - all via the PostNext public API with a single API key.\n\n## Install\n\nCopy this directory into your skills location (for Claude Code, a `skills/` directory the harness scans), then set your key:\n\n```bash\nexport POSTNEXT_API_KEY=<your key>   # create at https://postnext.io/account/api-keys\n```\n\nThe bundled `postnext` helper needs `curl` and `jq`. In an environment with no shell (for example claude.ai), the skill still works: every operation has a raw curl recipe in `references/`.\n\n## Quick start\n\n```bash\n./postnext channels                                   # what is connected\n./postnext post --provider twitter --text \"Hello\" --media ./hero.png\n./postnext schedule --provider instagram --text \"Launch\" --media ./a.jpg --at 2026-07-25T14:00:00Z\n./postnext analytics best-time twitter\n```\n\nRun `./postnext help` for the full command list.\n\n## What is in here\n\n| Path | What |\n|------|------|\n| `SKILL.md` | Skill entry point: setup, the core loop, and a router to the references. |\n| `postnext` | Bash helper (curl + jq) that handles the API's error-prone parts. |\n| `references/` | Per-area docs with raw curl recipes: posts, media, channels, analytics, errors. |\n| `docs/` | Design spec. |\n\n## Scope\n\nCovers the PostNext social publishing and analytics loop. Out of scope (use the PostNext MCP at `mcp.postnext.io` or the web app): connecting channels, WordPress/blog posts, brand-profile edits, and team management.\n\n## License\n\nMIT. See `LICENSE`. Not affiliated with post-bridge; inspired by the shape of its social-manager skill.\n\nFile v0.1.2:_meta.json\n\n{\n  \"ownerId\": \"kn7cy875pa1wnbqcte11mew4kd8ay84v\",\n  \"slug\": \"postnext-social-manager\",\n  \"version\": \"0.1.2\",\n  \"publishedAt\": 1791264398103\n}\n\nFile v0.1.2:references/analytics.md\n\n# Analytics\n\nAll analytics endpoints return `{success, data}`. Several require a paid tier (noted). Metrics exist only for **PUBLISHED** posts - per-post calls error on drafts or scheduled posts.\n\n## Freshness\n\nStored metrics are periodically snapshotted. To pull fresh numbers on demand:\n\n`POST /api/v1/analytics/sync` (paid) -> `{synced, summary:{twitterSnapped, instagramSnapped, tiktokSnapped, linkedinSnapped}}`. Rate-limited and bounded by per-platform caps. Call it before reading if you need current data.\n\n## Endpoints\n\n| Endpoint | Purpose | Params |\n|----------|---------|--------|\n| `GET /api/v1/analytics` | last-7-day counts of posts/ideas/assets | - |\n| `GET /api/v1/analytics/overview` | totals (impressions, reach, likes, comments, engagementRate) + vsPrevious | `channel`, `period` |\n| `GET /api/v1/analytics/posts` | per-post rows | `channel`, `period`, `sort=engagement|impressions|likes|recent` |\n| `GET /api/v1/analytics/daily` | per-day activity counts | `days` (1-60) |\n| `GET /api/v1/analytics/audience-growth` | follower time series | `channel`, `period` |\n| `GET /api/v1/analytics/engagement-over-time` (paid) | engagement rate per day | `channel`, `period` |\n| `GET /api/v1/analytics/best-time-to-post` (paid) | ranked posting slots from your own performance | `platform` (required), `timezone`, `topN` |\n| `GET /api/v1/analytics/post/{postId}` | latest metrics, one row per provider | - |\n| `GET /api/v1/analytics/post/{postId}/daily` | daily snapshots + day-over-day deltas | - |\n\n`period` is one of `7d`, `30d`, `90d`, `all` (default `30d`). `channel` filters to one provider/channel id; omit or `all` for no filter.\n\n## Capability flag\n\n`overview`, `posts`, and `engagement-over-time` may include a `capability` field: `ok`, `insights_unavailable`, `api_tier_limited`, `no_data_yet`, or `unsupported` (it can also be `null`/absent, which means normal). If it is present and not `ok`, tell the user why the numbers are thin (for example a platform whose API tier does not expose insights) rather than presenting zeros as real.\n\n`engagementRate` is a fraction (multiply by 100 for a percentage).\n\n## Examples\n\n```bash\nBASE=https://api-app.postnext.io\ncurl -sS \"$BASE/api/v1/analytics/overview?period=30d\" -H \"x-api-key: $POSTNEXT_API_KEY\"\ncurl -sS \"$BASE/api/v1/analytics/best-time-to-post?platform=instagram&timezone=Europe/Bucharest&topN=5\" -H \"x-api-key: $POSTNEXT_API_KEY\"\ncurl -sS -X POST \"$BASE/api/v1/analytics/sync\" -H \"x-api-key: $POSTNEXT_API_KEY\"\n```\n\nFile v0.1.2:references/channels.md\n\n# Channels (connections)\n\nChannels are connected in the PostNext web app - there is no API to start an OAuth connection. This skill only lists and reads them.\n\n## List connected channels\n\n`GET /api/connections` returns a **bare JSON array** (no envelope). Each item:\n\n```jsonc\n{\n  \"provider\": \"twitter\",           // one of twitter/instagram/linkedin/threads/youtube/tiktok/bluesky\n  \"channelName\": \"@yourbrand\",     // the connected handle - use this in a post's channelName\n  \"providerId\": \"tw_9f3a1c20e5\",   // platform-native account id - use this in a post's providerId\n  \"uniqueId\": \"...\",                 // internal UUID - do NOT use for posting\n  \"requiresAttention\": true,       // true only when a token refresh actually failed (re-auth needed)\n  \"isActive\": true,\n  \"avatar\": \"...\", \"createdAt\": \"...\", \"tokenExpiry\": \"...\"\n}\n```\n\n## Resolving providerId for a post (trap #3)\n\nTo post to a channel, pick the connection by matching `provider` (and `channelName` if the user named one), then copy its **`providerId`** and `channelName` into the post entry. Never use `uniqueId` - the publish worker needs the platform-native `providerId` or it fails silently.\n\n```bash\n# providerId + channelName for a given provider\ncurl -sS https://api-app.postnext.io/api/connections -H \"x-api-key: $POSTNEXT_API_KEY\" \\\n  | jq -r '.[] | select(.provider==\"twitter\") | \"\\(.channelName)\\t\\(.providerId)\"'\n```\n\nUse `requiresAttention == true` (not `tokenExpiry`) to decide whether a channel needs the user to re-authenticate in the web app before it can publish.\n\n## Check one channel\n\n`GET /api/connections/{provider}/{channelName}/check` returns a flat status object whose HTTP status mirrors the `status` field: `active` -> 200, `expired` -> 401, `invalid` -> 404.\n\n```jsonc\n{ \"status\": \"active\", \"message\": \"...\", \"expiresAt\": \"...\" }\n```\n\nFile v0.1.2:references/errors.md\n\n# Response shapes and errors\n\n## Success envelopes are inconsistent - check per endpoint\n\n| Shape | Endpoints |\n|-------|-----------|\n| `{success, data}` | most Posts, all Analytics, asset create-url/complete/get, single-post GET |\n| `{success, data, meta}` | `GET /api/posts` when `?page`/`?limit` is present |\n| `{success, data, pagination}` | `GET /api/assets/mine` (pagination is top-level, not `meta`) |\n| **bare array** | `GET /api/connections` |\n| **flat object, no wrapper** | `GET /api/v1/account`; connection `/check`; `upload/single` and `upload/url` (`{message, asset}`); `DELETE /api/assets/{id}` (`{message, success}`) |\n\nWhen parsing, do not assume a `data` key. The `postnext` helper unwraps `data` only when present.\n\n## Error shapes - there are three\n\n1. Standard: `{ \"success\": false, \"error\": { \"message\": \"...\" } }` (validation, quota 403).\n2. String variant: `{ \"success\": false, \"error\": \"Authentication required\" }` (401, bad/missing key) or `{ \"success\": false, \"error\": \"Analytics is a paid feature\", \"code\": \"ANALYTICS_PAID_ONLY\" }` (paid-tier gates). Here `error` is a string, not an object.\n3. Payment (402): a separate top-level shape:\n   ```jsonc\n   { \"error\": \"PAYMENT_REQUIRED\", \"code\": \"SUBSCRIPTION_INACTIVE\",\n     \"status\": \"past_due\", \"currency\": \"usd\",\n     \"actions\": { \"retryUrl\": \"...\", \"updateCardUrl\": \"...\", \"downgradeUrl\": \"...\" } }\n   ```\n\n## Status codes\n\n| Code | Meaning | What to tell the user |\n|------|---------|-----------------------|\n| 401 | bad or missing `x-api-key` | check the key |\n| 402 | subscription in a payment-failure state | fix billing (use `actions.updateCardUrl`) |\n| 403 | posting not allowed on the plan, or monthly post quota reached | upgrade or wait for the quota to reset |\n| 413 | media over the size cap (50 MB presigned) | use the multipart single path, or shrink |\n| 415 | uploaded bytes do not match the declared type | re-upload with the correct contentType |\n| 422 | unsupported media for platform (e.g. video on LinkedIn) | remove the video or pick another channel |\n| 429 | rate limited | back off and retry |\n\n## Known quirks (live-verified)\n\n- **GET a missing or just-deleted post returns HTTP 500**, not 404: `{\"success\":false,\"error\":\"Internal Server Error\",\"message\":\"Post group not found\"}`. Treat a 500 whose `message` is `Post group not found` as not-found.\n- A 500 whose body still parses as `{success:false, ..., message}` is a handled not-found/edge case, not necessarily an outage.\n\n## Rate limits\n\nRoughly: standard 100 requests / 15 min; social endpoints ~50 / 15 min; auth ~5 / 60 min. On a 429, pause and retry rather than hammering.\n\nFile v0.1.2:references/media.md\n\n# Media\n\n> Safety: upload only files the user asked to attach (images and video). Use a dedicated API key and never echo it. The `postnext` helper refuses other file types.\n\nUpload an image or video, then put the returned **`asset.url`** into a post's `content.media[].url`. The `assetId` is not used by the publish path.\n\n## Which upload path\n\n| Case | Endpoint | Cap | Needs `userId`? |\n|------|----------|-----|-----------------|\n| image or file <= 50 MB, one of jpeg/png/webp/gif/mp4/webm | 3-step presigned (`create-url` -> PUT -> `complete`) | 50 MB | no (from key) |\n| large video (> 50 MB, up to 500 MB) or other type | multipart `POST /api/assets/upload/single` | 500 MB | **yes** = account `uniqueId` |\n| a remote image URL | `POST /api/assets/upload/url` | 25 MB | **yes**; **image only** (a video URL is stored as .jpg) |\n\nGet the account `uniqueId` from `GET /api/v1/account` (`.uniqueId`).\n\n## 3-step presigned flow (preferred for images and small video)\n\n```bash\nBASE=https://api-app.postnext.io\n# 1. request an upload URL (declares type + exact byte size; pre-charges storage quota)\nINIT=$(curl -sS -X POST $BASE/api/assets/upload/create-url \\\n  -H \"x-api-key: $POSTNEXT_API_KEY\" -H 'Content-Type: application/json' \\\n  -d '{\"filename\":\"hero.png\",\"contentType\":\"image/png\",\"sizeBytes\":48213}')\nUPLOAD_URL=$(echo \"$INIT\" | jq -r .data.uploadUrl)\nUPLOAD_ID=$(echo \"$INIT\" | jq -r .data.uploadId)\n\n# 2. PUT the bytes. Content-Type MUST equal the declared contentType or S3 rejects the signature.\ncurl -sS -X PUT \"$UPLOAD_URL\" -H 'Content-Type: image/png' --data-binary @hero.png\n\n# 3. finalize -> returns the asset; use .data.url in the post\ncurl -sS -X POST $BASE/api/assets/upload/complete \\\n  -H \"x-api-key: $POSTNEXT_API_KEY\" -H 'Content-Type: application/json' \\\n  -d \"{\\\"uploadId\\\":\\\"$UPLOAD_ID\\\"}\" | jq -r .data.url\n```\n\n`contentType` allow-list (presigned): `image/jpeg, image/png, image/webp, image/gif, video/mp4, video/webm`. `sizeBytes` must be the true byte size and <= 50 MB (else 413). `complete` verifies the bytes by magic number (415 on mismatch) and reconciles the storage charge to the real size.\n\n## Multipart single (large video)\n\n```bash\nUID=$(curl -sS https://api-app.postnext.io/api/v1/account -H \"x-api-key: $POSTNEXT_API_KEY\" | jq -r .uniqueId)\ncurl -sS -X POST https://api-app.postnext.io/api/assets/upload/single \\\n  -H \"x-api-key: $POSTNEXT_API_KEY\" \\\n  -F \"file=@big.mp4;type=video/mp4\" -F \"userId=$UID\" | jq -r .asset.url\n```\nNote the different response shape: single/url return a flat `{message, asset}` (no `data` wrapper); presigned `complete` returns `{success, data: asset}`.\n\n## Then attach it\n\n```jsonc\n\"content\": { \"text\": \"...\", \"media\": [{ \"url\": \"<asset.url>\", \"type\": \"IMAGE\" }] }\n```\n`type` is `IMAGE`, `VIDEO`, or `GIF`. For a multi-image Instagram carousel, verify whether a top-level `mediaType: \"CAROUSEL\"` is also required (unverified).\n\n## Manage assets\n\n- `GET /api/assets/mine?page=1&limit=50` -> `{success, data, pagination}` (pagination is top-level).\n- `GET /api/assets/{assetId}` -> `{success, data}`.\n- `DELETE /api/assets/{assetId}` -> flat `{message, success}`.\n\nFile v0.1.2:references/posts.md\n\n# Posts\n\n> Safety: the API key posts to real accounts. Use a dedicated key, never echo it, and confirm with the user before any publish, schedule, cancel or delete.\n\nCreate a post by sending a `providers` map. Each key is a **bare provider name** and each value is one channel's entry.\n\n## Request shape (CreatePostRequest)\n\n```jsonc\n{\n  \"providers\": {\n    \"twitter\": {                              // BARE name. NOT \"twitter:@handle\".\n      \"channelName\": \"@yourbrand\",            // the real connected handle (from GET /connections)\n      \"providerId\":  \"tw_9f3a1c20e5\",         // connection.providerId (NOT uniqueId)\n      \"content\": { \"text\": \"We shipped v2.\",\n                   \"media\": [{ \"url\": \"https://cdn.postnext.io/assets/hero.png\", \"type\": \"IMAGE\" }],\n                   \"hashtags\": [\"launch\"] },\n      \"firstComment\": \"...\"                   // instagram + linkedin ONLY; ignored elsewhere\n    }\n  },\n  \"title\": \"Launch\",                          // optional, internal label\n  \"tags\": [\"launch\"]                          // optional\n}\n```\n\n`providerId` and `channelName` come from `GET /api/connections` (see `channels.md`). Resolve them by matching `provider` + `channelName`. A wrong/missing `providerId` makes the publish worker reject the row silently.\n\n## Three ways to send it\n\n| Goal | Endpoint |\n|------|----------|\n| Save a draft | `POST /api/posts` |\n| Publish immediately | `POST /api/posts/publish` |\n| Schedule | `POST /api/posts/schedule` with `\"scheduledAt\": \"<future ISO8601>\"` |\n\nExisting post by id: `POST /api/posts/{postId}/publish`, `POST /api/posts/{postId}/schedule`, `POST /api/posts/{postId}/cancel-schedule` (back to draft), `DELETE /api/posts/{groupId}` (removes from PostNext; does not delete an already-published post from the platform).\n\n`userId` and `teamId` are derived from the key - never send them.\n\nNote: fetching a deleted or non-existent post (`GET /api/posts/{id}`) returns HTTP 500 with `message: \"Post group not found\"`, not 404 (see `errors.md`).\n\n## Single post vs thread\n\n`content` is either an object (single post) or an array (thread).\n\n**Single** (`LegacyPostContent`):\n```jsonc\n{ \"text\": \"...\", \"media\": [{ \"url\": \"<asset.url>\", \"type\": \"IMAGE|VIDEO|GIF\" }],\n  \"hashtags\": [\"a\"], \"instagramPostType\": \"feed|post|story|reel\" }\n```\n\n**Thread** (array, `twitter` and `threads` ONLY):\n```jsonc\n[ { \"order\": 1, \"text\": \"1/\", \"media\": [] },\n  { \"order\": 2, \"text\": \"2/\" } ]\n```\n`order` must be exactly 1..N with no gaps. Sending an array to any other provider silently keeps only element `[0]`.\n\nAttach media by URL (`content.media[].url` or the legacy `content.mediaUrls[]`). `assetIds` is never dereferenced - a post with only `assetIds` has no media.\n\n## Per-provider rules enforced at create time\n\n| Provider | Rules |\n|----------|-------|\n| instagram | text or media required; caption <= 2200; <= 30 hashtags; `firstComment` <= 2200; `instagramPostType` in feed/post/story/reel |\n| bluesky | text or image; <= 300 graphemes; <= 4 images; **no video** |\n| linkedin | **no video** (422 UNSUPPORTED_MEDIA_FOR_PLATFORM); IMAGE + GIF ok; `firstComment` <= 1250 |\n| twitter | text required; no char limit enforced at create; polls 2-4 options |\n| threads, tiktok | no create-time content validator |\n| youtube | publish path via this route is UNVERIFIED - test before relying on it |\n\n## Scheduling\n\n`scheduledAt` must be a future ISO8601 datetime (past or now is a 400). `timezone` only affects recurrence and display; it never shifts the fire time.\n\n**Idempotency:** raw `POST /api/posts/{postId}/schedule` will create a duplicate job if called twice, causing a double-publish. Before re-scheduling, check the post is not already scheduled to that time (the `postnext` helper guards a +/- 60s window and refuses past-due times).\n\n## Raw curl\n\n```bash\n# publish now\ncurl -sS -X POST https://api-app.postnext.io/api/posts/publish \\\n  -H \"x-api-key: $POSTNEXT_API_KEY\" -H 'Content-Type: application/json' \\\n  -d '{\"providers\":{\"twitter\":{\"channelName\":\"@brand\",\"providerId\":\"tw_123\",\n       \"content\":{\"text\":\"hi\",\"media\":[{\"url\":\"https://cdn/x.png\",\"type\":\"IMAGE\"}]}}}}'\n\n# list posts (optionally by status)\ncurl -sS \"https://api-app.postnext.io/api/posts?status=SCHEDULED\" -H \"x-api-key: $POSTNEXT_API_KEY\"\n\n# per-channel publish outcomes\ncurl -sS https://api-app.postnext.io/api/posts/results -H \"x-api-key: $POSTNEXT_API_KEY\"\n```\n\nFile v0.1.2:skill-card.md\n\n## Description:\n\nHelps agents draft, publish, schedule, and analyze social posts across connected platforms through PostNext.\n\nThis skill is ready for commercial/non-commercial use.\n\n## Publisher:\n\n[postnextio](https://clawhub.ai/user/postnextio)\n\n### License/Terms of Use:\n\nMIT\n\n## Use Case:\n\nSocial media managers and creators use this skill to prepare and publish posts, schedule content, upload media, check connected accounts, and review analytics for their PostNext account.\n\n### Deployment Geography for Use:\n\nGlobal\n\n## Known Risks and Mitigations:\n\nRisk: The API key can publish, schedule, cancel, or delete real social-media content without a built-in confirmation gate.\n\nMitigation: Preview and explicitly confirm the provider, channel, text, media, time, and target ID before each consequential action.\n\nRisk: A compromised or overprivileged API key could affect connected accounts.\n\nMitigation: Use a dedicated key with no broader account access than needed and keep it out of posts, logs, and URLs.\n\n## Reference(s):\n\n- [PostNext Social Manager on ClawHub](https://clawhub.ai/postnextio/skills/postnext-social-manager)\n- [PostNext](https://postnext.io)\n- [Posts](references/posts.md)\n- [Media](references/media.md)\n- [Channels](references/channels.md)\n- [Analytics](references/analytics.md)\n- [Errors](references/errors.md)\n\n## Skill Output:\n\n**Output Type(s):** [Text, Markdown, Shell commands, JSON]\n\n**Output Format:** [Text or Markdown with commands and structured API results]\n\n**Output Parameters:** [1D]\n\n**Other Properties Related to Output:** [Posts can be published immediately or scheduled; analytics and channel information are returned on request.]\n\n## Skill Version(s):\n\n0.1.2 (source: ClawHub release)\n\n## Ethical Considerations:\n\nUsers should evaluate whether this skill is appropriate for their environment, review any generated or modified files before relying on them, and apply their organization's safety, security, and compliance requirements before deployment.\n\nFile v0.1.2:LICENSE\n\nMIT License\n\nCopyright (c) 2026 PostNext\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n\nArchive v0.1.1: 11 files, 17668 bytes\n\nFiles: LICENSE (1065b), postnext (10977b), README.md (1836b), references/analytics.md (2492b), references/channels.md (1836b), references/errors.md (2635b), references/media.md (3135b), references/posts.md (4390b), skill-card.md (2077b), SKILL.md (4293b), _meta.json (142b)\n\nFile v0.1.1:SKILL.md\n\n---\nname: postnext-social-manager\ndescription: Manage social media through PostNext - schedule, publish, and analyze posts across Twitter/X, Instagram, LinkedIn, Threads, YouTube, TikTok, and Bluesky via the PostNext public API. Use when the user wants to draft, schedule, or publish social posts, upload media for posts, check what is queued, or read social analytics (engagement, best time to post, audience growth) and has a PostNext account and API key.\nmetadata:\n  requires:\n    bins: [curl, jq]\n    env: [POSTNEXT_API_KEY]\n---\n\n# PostNext Social Manager\n\nDrive a PostNext account programmatically: connect channels, upload media, compose and schedule or publish posts across platforms, and read analytics. Wraps the PostNext public REST API with an API key.\n\n## Setup\n\n1. The user creates an API key at https://postnext.io/account/api-keys (shown only once).\n2. Export it: `export POSTNEXT_API_KEY=<key>`\n3. Base URL is `https://api-app.postnext.io`. Auth header on every request: `x-api-key: $POSTNEXT_API_KEY`.\n\nTwo ways to use this skill:\n- **Helper (recommended where a shell is available):** the bundled `postnext` script (needs `curl` + `jq`). It handles the fiddly, error-prone parts for you. Run `./postnext help`.\n- **Raw curl (works anywhere, including no-filesystem sandboxes):** every operation has a copy-paste curl recipe in `references/`.\n\n## The core loop\n\n1. **Connect** (one-time, done by the user in the PostNext web app - there is no API to connect a channel). Confirm what is connected: `postnext channels`.\n2. **Upload media** if the post has an image or video: `postnext upload ./clip.mp4` prints an asset URL.\n3. **Compose + publish now** or **schedule**:\n   - `postnext post --provider twitter --text \"...\" --media ./a.png`\n   - `postnext schedule --provider instagram --text \"...\" --media ./a.jpg --at 2026-07-25T14:00:00Z`\n4. **Measure**: `postnext analytics overview`, `postnext analytics best-time twitter`, `postnext results`.\n\n## Safety\n\n- The API key acts on the user's real social accounts. Use a dedicated key, and never paste it into posts, logs or URLs.\n- Confirm with the user before any publish, schedule, cancel or delete. Show the provider, channel, text and time first.\n- `delete` removes the post from PostNext only. It does not remove an already-published post from the platform.\n- The helper only talks to `https://api-app.postnext.io`. Do not point raw curl recipes at any other host with the key.\n\n## Four traps this skill exists to prevent\n\nThe PostNext API has four behaviors that silently produce broken posts. The `postnext` helper handles all four; if you use raw curl, you must handle them yourself (see `references/posts.md` and `references/media.md`).\n\n1. **Key posts by the bare provider name** (`twitter`), never `twitter:@handle`. A colon key is rejected at publish. Select the account with the entry's `providerId` + `channelName`.\n2. **Attach media by its URL**, never by `assetId`. A post that carries only `assetIds` publishes with no media and no error. Put the uploaded `asset.url` into `content.media`.\n3. **`providerId` is the connection's `providerId`** (the platform-native account id from `GET /api/connections`), not `uniqueId`. A wrong or missing `providerId` fails at the publish worker silently.\n4. **Only twitter and threads thread.** An array of posts sent to any other provider is silently reduced to the first item.\n\n## When to read which reference\n\n| Task | Read |\n|------|------|\n| Build a post, threads, per-provider rules, char limits | `references/posts.md` |\n| Upload an image or video, size routing, the 3-step flow | `references/media.md` |\n| List or check connected channels, resolve `providerId` | `references/channels.md` |\n| Read analytics, best time to post, sync | `references/analytics.md` |\n| Response envelope shapes, error codes, rate limits | `references/errors.md` |\n\n## Known limitations\n\n- No channel connect over the API (browser-only in PostNext).\n- No team switching: the key operates on its account's default team.\n- One entry per provider per request (no multi-account-per-provider in a single post).\n- Analytics and per-post metrics require PUBLISHED posts.\n- Not supported by this skill: WordPress/blog posts, brand-profile edits, team management (use the PostNext MCP or web app).\n\nFile v0.1.1:README.md\n\n# postnext-social-manager\n\nA Claude Agent Skill for managing social media through [PostNext](https://postnext.io). It lets an agent connect-aware, upload media, compose, schedule, and publish posts across Twitter/X, Instagram, LinkedIn, Threads, YouTube, TikTok, and Bluesky, and read analytics - all via the PostNext public API with a single API key.\n\n## Install\n\nCopy this directory into your skills location (for Claude Code, a `skills/` directory the harness scans), then set your key:\n\n```bash\nexport POSTNEXT_API_KEY=<your key>   # create at https://postnext.io/account/api-keys\n```\n\nThe bundled `postnext` helper needs `curl` and `jq`. In an environment with no shell (for example claude.ai), the skill still works: every operation has a raw curl recipe in `references/`.\n\n## Quick start\n\n```bash\n./postnext channels                                   # what is connected\n./postnext post --provider twitter --text \"Hello\" --media ./hero.png\n./postnext schedule --provider instagram --text \"Launch\" --media ./a.jpg --at 2026-07-25T14:00:00Z\n./postnext analytics best-time twitter\n```\n\nRun `./postnext help` for the full command list.\n\n## What is in here\n\n| Path | What |\n|------|------|\n| `SKILL.md` | Skill entry point: setup, the core loop, and a router to the references. |\n| `postnext` | Bash helper (curl + jq) that handles the API's error-prone parts. |\n| `references/` | Per-area docs with raw curl recipes: posts, media, channels, analytics, errors. |\n| `docs/` | Design spec. |\n\n## Scope\n\nCovers the PostNext social publishing and analytics loop. Out of scope (use the PostNext MCP at `mcp.postnext.io` or the web app): connecting channels, WordPress/blog posts, brand-profile edits, and team management.\n\n## License\n\nMIT. See `LICENSE`. Not affiliated with post-bridge; inspired by the shape of its social-manager skill.\n\nFile v0.1.1:_meta.json\n\n{\n  \"ownerId\": \"kn7cy875pa1wnbqcte11mew4kd8ay84v\",\n  \"slug\": \"postnext-social-manager\",\n  \"version\": \"0.1.1\",\n  \"publishedAt\": 1791013407474\n}\n\nFile v0.1.1:references/analytics.md\n\n# Analytics\n\nAll analytics endpoints return `{success, data}`. Several require a paid tier (noted). Metrics exist only for **PUBLISHED** posts - per-post calls error on drafts or scheduled posts.\n\n## Freshness\n\nStored metrics are periodically snapshotted. To pull fresh numbers on demand:\n\n`POST /api/v1/analytics/sync` (paid) -> `{synced, summary:{twitterSnapped, instagramSnapped, tiktokSnapped, linkedinSnapped}}`. Rate-limited and bounded by per-platform caps. Call it before reading if you need current data.\n\n## Endpoints\n\n| Endpoint | Purpose | Params |\n|----------|---------|--------|\n| `GET /api/v1/analytics` | last-7-day counts of posts/ideas/assets | - |\n| `GET /api/v1/analytics/overview` | totals (impressions, reach, likes, comments, engagementRate) + vsPrevious | `channel`, `period` |\n| `GET /api/v1/analytics/posts` | per-post rows | `channel`, `period`, `sort=engagement|impressions|likes|recent` |\n| `GET /api/v1/analytics/daily` | per-day activity counts | `days` (1-60) |\n| `GET /api/v1/analytics/audience-growth` | follower time series | `channel`, `period` |\n| `GET /api/v1/analytics/engagement-over-time` (paid) | engagement rate per day | `channel`, `period` |\n| `GET /api/v1/analytics/best-time-to-post` (paid) | ranked posting slots from your own performance | `platform` (required), `timezone`, `topN` |\n| `GET /api/v1/analytics/post/{postId}` | latest metrics, one row per provider | - |\n| `GET /api/v1/analytics/post/{postId}/daily` | daily snapshots + day-over-day deltas | - |\n\n`period` is one of `7d`, `30d`, `90d`, `all` (default `30d`). `channel` filters to one provider/channel id; omit or `all` for no filter.\n\n## Capability flag\n\n`overview`, `posts`, and `engagement-over-time` may include a `capability` field: `ok`, `insights_unavailable`, `api_tier_limited`, `no_data_yet`, or `unsupported` (it can also be `null`/absent, which means normal). If it is present and not `ok`, tell the user why the numbers are thin (for example a platform whose API tier does not expose insights) rather than presenting zeros as real.\n\n`engagementRate` is a fraction (multiply by 100 for a percentage).\n\n## Examples\n\n```bash\nBASE=https://api-app.postnext.io\ncurl -sS \"$BASE/api/v1/analytics/overview?period=30d\" -H \"x-api-key: $POSTNEXT_API_KEY\"\ncurl -sS \"$BASE/api/v1/analytics/best-time-to-post?platform=instagram&timezone=Europe/Bucharest&topN=5\" -H \"x-api-key: $POSTNEXT_API_KEY\"\ncurl -sS -X POST \"$BASE/api/v1/analytics/sync\" -H \"x-api-key: $POSTNEXT_API_KEY\"\n```\n\nFile v0.1.1:references/channels.md\n\n# Channels (connections)\n\nChannels are connected in the PostNext web app - there is no API to start an OAuth connection. This skill only lists and reads them.\n\n## List connected channels\n\n`GET /api/connections` returns a **bare JSON array** (no envelope). Each item:\n\n```jsonc\n{\n  \"provider\": \"twitter\",           // one of twitter/instagram/linkedin/threads/youtube/tiktok/bluesky\n  \"channelName\": \"@yourbrand\",     // the connected handle - use this in a post's channelName\n  \"providerId\": \"tw_9f3a1c20e5\",   // platform-native account id - use this in a post's providerId\n  \"uniqueId\": \"...\",                 // internal UUID - do NOT use for posting\n  \"requiresAttention\": true,       // true only when a token refresh actually failed (re-auth needed)\n  \"isActive\": true,\n  \"avatar\": \"...\", \"createdAt\": \"...\", \"tokenExpiry\": \"...\"\n}\n```\n\n## Resolving providerId for a post (trap #3)\n\nTo post to a channel, pick the connection by matching `provider` (and `channelName` if the user named one), then copy its **`providerId`** and `channelName` into the post entry. Never use `uniqueId` - the publish worker needs the platform-native `providerId` or it fails silently.\n\n```bash\n# providerId + channelName for a given provider\ncurl -sS https://api-app.postnext.io/api/connections -H \"x-api-key: $POSTNEXT_API_KEY\" \\\n  | jq -r '.[] | select(.provider==\"twitter\") | \"\\(.channelName)\\t\\(.providerId)\"'\n```\n\nUse `requiresAttention == true` (not `tokenExpiry`) to decide whether a channel needs the user to re-authenticate in the web app before it can publish.\n\n## Check one channel\n\n`GET /api/connections/{provider}/{channelName}/check` returns a flat status object whose HTTP status mirrors the `status` field: `active` -> 200, `expired` -> 401, `invalid` -> 404.\n\n```jsonc\n{ \"status\": \"active\", \"message\": \"...\", \"expiresAt\": \"...\" }\n```\n\nFile v0.1.1:references/errors.md\n\n# Response shapes and errors\n\n## Success envelopes are inconsistent - check per endpoint\n\n| Shape | Endpoints |\n|-------|-----------|\n| `{success, data}` | most Posts, all Analytics, asset create-url/complete/get, single-post GET |\n| `{success, data, meta}` | `GET /api/posts` when `?page`/`?limit` is present |\n| `{success, data, pagination}` | `GET /api/assets/mine` (pagination is top-level, not `meta`) |\n| **bare array** | `GET /api/connections` |\n| **flat object, no wrapper** | `GET /api/v1/account`; connection `/check`; `upload/single` and `upload/url` (`{message, asset}`); `DELETE /api/assets/{id}` (`{message, success}`) |\n\nWhen parsing, do not assume a `data` key. The `postnext` helper unwraps `data` only when present.\n\n## Error shapes - there are three\n\n1. Standard: `{ \"success\": false, \"error\": { \"message\": \"...\" } }` (validation, quota 403).\n2. String variant: `{ \"success\": false, \"error\": \"Authentication required\" }` (401, bad/missing key) or `{ \"success\": false, \"error\": \"Analytics is a paid feature\", \"code\": \"ANALYTICS_PAID_ONLY\" }` (paid-tier gates). Here `error` is a string, not an object.\n3. Payment (402): a separate top-level shape:\n   ```jsonc\n   { \"error\": \"PAYMENT_REQUIRED\", \"code\": \"SUBSCRIPTION_INACTIVE\",\n     \"status\": \"past_due\", \"currency\": \"usd\",\n     \"actions\": { \"retryUrl\": \"...\", \"updateCardUrl\": \"...\", \"downgradeUrl\": \"...\" } }\n   ```\n\n## Status codes\n\n| Code | Meaning | What to tell the user |\n|------|---------|-----------------------|\n| 401 | bad or missing `x-api-key` | check the key |\n| 402 | subscription in a payment-failure state | fix billing (use `actions.updateCardUrl`) |\n| 403 | posting not allowed on the plan, or monthly post quota reached | upgrade or wait for the quota to reset |\n| 413 | media over the size cap (50 MB presigned) | use the multipart single path, or shrink |\n| 415 | uploaded bytes do not match the declared type | re-upload with the correct contentType |\n| 422 | unsupported media for platform (e.g. video on LinkedIn) | remove the video or pick another channel |\n| 429 | rate limited | back off and retry |\n\n## Known quirks (live-verified)\n\n- **GET a missing or just-deleted post returns HTTP 500**, not 404: `{\"success\":false,\"error\":\"Internal Server Error\",\"message\":\"Post group not found\"}`. Treat a 500 whose `message` is `Post group not found` as not-found.\n- A 500 whose body still parses as `{success:false, ..., message}` is a handled not-found/edge case, not necessarily an outage.\n\n## Rate limits\n\nRoughly: standard 100 requests / 15 min; social endpoints ~50 / 15 min; auth ~5 / 60 min. On a 429, pause and retry rather than hammering.\n\nFile v0.1.1:references/media.md\n\n# Media\n\n> Safety: upload only files the user asked to attach (images and video). Use a dedicated API key and never echo it. The `postnext` helper refuses other file types.\n\nUpload an image or video, then put the returned **`asset.url`** into a post's `content.media[].url`. The `assetId` is not used by the publish path.\n\n## Which upload path\n\n| Case | Endpoint | Cap | Needs `userId`? |\n|------|----------|-----|-----------------|\n| image or file <= 50 MB, one of jpeg/png/webp/gif/mp4/webm | 3-step presigned (`create-url` -> PUT -> `complete`) | 50 MB | no (from key) |\n| large video (> 50 MB, up to 500 MB) or other type | multipart `POST /api/assets/upload/single` | 500 MB | **yes** = account `uniqueId` |\n| a remote image URL | `POST /api/assets/upload/url` | 25 MB | **yes**; **image only** (a video URL is stored as .jpg) |\n\nGet the account `uniqueId` from `GET /api/v1/account` (`.uniqueId`).\n\n## 3-step presigned flow (preferred for images and small video)\n\n```bash\nBASE=https://api-app.postnext.io\n# 1. request an upload URL (declares type + exact byte size; pre-charges storage quota)\nINIT=$(curl -sS -X POST $BASE/api/assets/upload/create-url \\\n  -H \"x-api-key: $POSTNEXT_API_KEY\" -H 'Content-Type: application/json' \\\n  -d '{\"filename\":\"hero.png\",\"contentType\":\"image/png\",\"sizeBytes\":48213}')\nUPLOAD_URL=$(echo \"$INIT\" | jq -r .data.uploadUrl)\nUPLOAD_ID=$(echo \"$INIT\" | jq -r .data.uploadId)\n\n# 2. PUT the bytes. Content-Type MUST equal the declared contentType or S3 rejects the signature.\ncurl -sS -X PUT \"$UPLOAD_URL\" -H 'Content-Type: image/png' --data-binary @hero.png\n\n# 3. finalize -> returns the asset; use .data.url in the post\ncurl -sS -X POST $BASE/api/assets/upload/complete \\\n  -H \"x-api-key: $POSTNEXT_API_KEY\" -H 'Content-Type: application/json' \\\n  -d \"{\\\"uploadId\\\":\\\"$UPLOAD_ID\\\"}\" | jq -r .data.url\n```\n\n`contentType` allow-list (presigned): `image/jpeg, image/png, image/webp, image/gif, video/mp4, video/webm`. `sizeBytes` must be the true byte size and <= 50 MB (else 413). `complete` verifies the bytes by magic number (415 on mismatch) and reconciles the storage charge to the real size.\n\n## Multipart single (large video)\n\n```bash\nUID=$(curl -sS https://api-app.postnext.io/api/v1/account -H \"x-api-key: $POSTNEXT_API_KEY\" | jq -r .uniqueId)\ncurl -sS -X POST https://api-app.postnext.io/api/assets/upload/single \\\n  -H \"x-api-key: $POSTNEXT_API_KEY\" \\\n  -F \"file=@big.mp4;type=video/mp4\" -F \"userId=$UID\" | jq -r .asset.url\n```\nNote the different response shape: single/url return a flat `{message, asset}` (no `data` wrapper); presigned `complete` returns `{success, data: asset}`.\n\n## Then attach it\n\n```jsonc\n\"content\": { \"text\": \"...\", \"media\": [{ \"url\": \"<asset.url>\", \"type\": \"IMAGE\" }] }\n```\n`type` is `IMAGE`, `VIDEO`, or `GIF`. For a multi-image Instagram carousel, verify whether a top-level `mediaType: \"CAROUSEL\"` is also required (unverified).\n\n## Manage assets\n\n- `GET /api/assets/mine?page=1&limit=50` -> `{success, data, pagination}` (pagination is top-level).\n- `GET /api/assets/{assetId}` -> `{success, data}`.\n- `DELETE /api/assets/{assetId}` -> flat `{message, success}`.\n\nFile v0.1.1:references/posts.md\n\n# Posts\n\n> Safety: the API key posts to real accounts. Use a dedicated key, never echo it, and confirm with the user before any publish, schedule, cancel or delete.\n\nCreate a post by sending a `providers` map. Each key is a **bare provider name** and each value is one channel's entry.\n\n## Request shape (CreatePostRequest)\n\n```jsonc\n{\n  \"providers\": {\n    \"twitter\": {                              // BARE name. NOT \"twitter:@handle\".\n      \"channelName\": \"@yourbrand\",            // the real connected handle (from GET /connections)\n      \"providerId\":  \"tw_9f3a1c20e5\",         // connection.providerId (NOT uniqueId)\n      \"content\": { \"text\": \"We shipped v2.\",\n                   \"media\": [{ \"url\": \"https://cdn.postnext.io/assets/hero.png\", \"type\": \"IMAGE\" }],\n                   \"hashtags\": [\"launch\"] },\n      \"firstComment\": \"...\"                   // instagram + linkedin ONLY; ignored elsewhere\n    }\n  },\n  \"title\": \"Launch\",                          // optional, internal label\n  \"tags\": [\"launch\"]                          // optional\n}\n```\n\n`providerId` and `channelName` come from `GET /api/connections` (see `channels.md`). Resolve them by matching `provider` + `channelName`. A wrong/missing `providerId` makes the publish worker reject the row silently.\n\n## Three ways to send it\n\n| Goal | Endpoint |\n|------|----------|\n| Save a draft | `POST /api/posts` |\n| Publish immediately | `POST /api/posts/publish` |\n| Schedule | `POST /api/posts/schedule` with `\"scheduledAt\": \"<future ISO8601>\"` |\n\nExisting post by id: `POST /api/posts/{postId}/publish`, `POST /api/posts/{postId}/schedule`, `POST /api/posts/{postId}/cancel-schedule` (back to draft), `DELETE /api/posts/{groupId}` (removes from PostNext; does not delete an already-published post from the platform).\n\n`userId` and `teamId` are derived from the key - never send them.\n\nNote: fetching a deleted or non-existent post (`GET /api/posts/{id}`) returns HTTP 500 with `message: \"Post group not found\"`, not 404 (see `errors.md`).\n\n## Single post vs thread\n\n`content` is either an object (single post) or an array (thread).\n\n**Single** (`LegacyPostContent`):\n```jsonc\n{ \"text\": \"...\", \"media\": [{ \"url\": \"<asset.url>\", \"type\": \"IMAGE|VIDEO|GIF\" }],\n  \"hashtags\": [\"a\"], \"instagramPostType\": \"feed|post|story|reel\" }\n```\n\n**Thread** (array, `twitter` and `threads` ONLY):\n```jsonc\n[ { \"order\": 1, \"text\": \"1/\", \"media\": [] },\n  { \"order\": 2, \"text\": \"2/\" } ]\n```\n`order` must be exactly 1..N with no gaps. Sending an array to any other provider silently keeps only element `[0]`.\n\nAttach media by URL (`content.media[].url` or the legacy `content.mediaUrls[]`). `assetIds` is never dereferenced - a post with only `assetIds` has no media.\n\n## Per-provider rules enforced at create time\n\n| Provider | Rules |\n|----------|-------|\n| instagram | text or media required; caption <= 2200; <= 30 hashtags; `firstComment` <= 2200; `instagramPostType` in feed/post/story/reel |\n| bluesky | text or image; <= 300 graphemes; <= 4 images; **no video** |\n| linkedin | **no video** (422 UNSUPPORTED_MEDIA_FOR_PLATFORM); IMAGE + GIF ok; `firstComment` <= 1250 |\n| twitter | text required; no char limit enforced at create; polls 2-4 options |\n| threads, tiktok | no create-time content validator |\n| youtube | publish path via this route is UNVERIFIED - test before relying on it |\n\n## Scheduling\n\n`scheduledAt` must be a future ISO8601 datetime (past or now is a 400). `timezone` only affects recurrence and display; it never shifts the fire time.\n\n**Idempotency:** raw `POST /api/posts/{postId}/schedule` will create a duplicate job if called twice, causing a double-publish. Before re-scheduling, check the post is not already scheduled to that time (the `postnext` helper guards a +/- 60s window and refuses past-due times).\n\n## Raw curl\n\n```bash\n# publish now\ncurl -sS -X POST https://api-app.postnext.io/api/posts/publish \\\n  -H \"x-api-key: $POSTNEXT_API_KEY\" -H 'Content-Type: application/json' \\\n  -d '{\"providers\":{\"twitter\":{\"channelName\":\"@brand\",\"providerId\":\"tw_123\",\n       \"content\":{\"text\":\"hi\",\"media\":[{\"url\":\"https://cdn/x.png\",\"type\":\"IMAGE\"}]}}}}'\n\n# list posts (optionally by status)\ncurl -sS \"https://api-app.postnext.io/api/posts?status=SCHEDULED\" -H \"x-api-key: $POSTNEXT_API_KEY\"\n\n# per-channel publish outcomes\ncurl -sS https://api-app.postnext.io/api/posts/results -H \"x-api-key: $POSTNEXT_API_KEY\"\n```\n\nFile v0.1.1:skill-card.md\n\n## Description:\n\nHelps agents draft, publish, schedule, and analyze social posts through a user's PostNext account.\n\nThis skill is ready for commercial/non-commercial use.\n\n## Publisher:\n\n[postnextio](https://clawhub.ai/user/postnextio)\n\n### License/Terms of Use:\n\nMIT-0\n\n## Use Case:\n\nSocial media managers and other PostNext users with an API key can prepare and publish posts, schedule content, upload media, and review channel performance across supported social platforms.\n\n### Deployment Geography for Use:\n\nGlobal\n\n## Known Risks and Mitigations:\n\nRisk: The API key can act on real social accounts and could be exposed in shared content or logs.\n\nMitigation: Use a dedicated, revocable key and never include it in posts, logs, or URLs.\n\nRisk: The skill can publish, schedule, cancel, or delete posts without an enforced confirmation step.\n\nMitigation: Require user review of the final account, content, media, and time before each consequential action; avoid unattended workflows.\n\nRisk: Repeating a schedule request may create duplicate publication jobs.\n\nMitigation: Check whether a post is already scheduled before retrying a schedule action.\n\n## Reference(s):\n\n- [ClawHub skill release](https://clawhub.ai/postnextio/skills/postnext-social-manager)\n- [PostNext](https://postnext.io)\n- [Posts reference](references/posts.md)\n- [Media reference](references/media.md)\n- [Analytics reference](references/analytics.md)\n\n## Skill Output:\n\n**Output Type(s):** [Text, Markdown, Shell commands, Guidance]\n\n**Output Format:** [Text or Markdown with optional shell commands]\n\n**Output Parameters:** [1D]\n\n**Other Properties Related to Output:** [May report publishing status, queued posts, and analytics from the user's PostNext account.]\n\n## Skill Version(s):\n\n0.1.1 (source: server-resolved ClawHub release)\n\n## Ethical Considerations:\n\nUsers should evaluate whether this skill is appropriate for their environment, review any generated or modified files before relying on them, and apply their organization's safety, security, and compliance requirements before deployment.\n\nFile v0.1.1:LICENSE\n\nMIT License\n\nCopyright (c) 2026 PostNext\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n\nArchive v0.1.0: 13 files, 17628 bytes\n\nFiles: .gitignore (22b), LICENSE (1065b), postnext (10544b), README.md (1836b), references (0b), references/analytics.md (2492b), references/channels.md (1836b), references/errors.md (2635b), references/media.md (2970b), references/posts.md (4233b), skill-card.md (2687b), SKILL.md (3736b), _meta.json (142b)\n\nFile v0.1.0:SKILL.md\n\n---\nname: postnext-social-manager\ndescription: Manage social media through PostNext - schedule, publish, and analyze posts across Twitter/X, Instagram, LinkedIn, Threads, YouTube, TikTok, and Bluesky via the PostNext public API. Use when the user wants to draft, schedule, or publish social posts, upload media for posts, check what is queued, or read social analytics (engagement, best time to post, audience growth) and has a PostNext account and API key.\n---\n\n# PostNext Social Manager\n\nDrive a PostNext account programmatically: connect channels, upload media, compose and schedule or publish posts across platforms, and read analytics. Wraps the PostNext public REST API with an API key.\n\n## Setup\n\n1. The user creates an API key at https://postnext.io/account/api-keys (shown only once).\n2. Export it: `export POSTNEXT_API_KEY=<key>`\n3. Base URL is `https://api-app.postnext.io`. Auth header on every request: `x-api-key: $POSTNEXT_API_KEY`.\n\nTwo ways to use this skill:\n- **Helper (recommended where a shell is available):** the bundled `postnext` script (needs `curl` + `jq`). It handles the fiddly, error-prone parts for you. Run `./postnext help`.\n- **Raw curl (works anywhere, including no-filesystem sandboxes):** every operation has a copy-paste curl recipe in `references/`.\n\n## The core loop\n\n1. **Connect** (one-time, done by the user in the PostNext web app - there is no API to connect a channel). Confirm what is connected: `postnext channels`.\n2. **Upload media** if the post has an image or video: `postnext upload ./clip.mp4` prints an asset URL.\n3. **Compose + publish now** or **schedule**:\n   - `postnext post --provider twitter --text \"...\" --media ./a.png`\n   - `postnext schedule --provider instagram --text \"...\" --media ./a.jpg --at 2026-07-25T14:00:00Z`\n4. **Measure**: `postnext analytics overview`, `postnext analytics best-time twitter`, `postnext results`.\n\n## Four traps this skill exists to prevent\n\nThe PostNext API has four behaviors that silently produce broken posts. The `postnext` helper handles all four; if you use raw curl, you must handle them yourself (see `references/posts.md` and `references/media.md`).\n\n1. **Key posts by the bare provider name** (`twitter`), never `twitter:@handle`. A colon key is rejected at publish. Select the account with the entry's `providerId` + `channelName`.\n2. **Attach media by its URL**, never by `assetId`. A post that carries only `assetIds` publishes with no media and no error. Put the uploaded `asset.url` into `content.media`.\n3. **`providerId` is the connection's `providerId`** (the platform-native account id from `GET /api/connections`), not `uniqueId`. A wrong or missing `providerId` fails at the publish worker silently.\n4. **Only twitter and threads thread.** An array of posts sent to any other provider is silently reduced to the first item.\n\n## When to read which reference\n\n| Task | Read |\n|------|------|\n| Build a post, threads, per-provider rules, char limits | `references/posts.md` |\n| Upload an image or video, size routing, the 3-step flow | `references/media.md` |\n| List or check connected channels, resolve `providerId` | `references/channels.md` |\n| Read analytics, best time to post, sync | `references/analytics.md` |\n| Response envelope shapes, error codes, rate limits | `references/errors.md` |\n\n## Known limitations\n\n- No channel connect over the API (browser-only in PostNext).\n- No team switching: the key operates on its account's default team.\n- One entry per provider per request (no multi-account-per-provider in a single post).\n- Analytics and per-post metrics require PUBLISHED posts.\n- Not supported by this skill: WordPress/blog posts, brand-profile edits, team management (use the PostNext MCP or web app).\n\nFile v0.1.0:README.md\n\n# postnext-social-manager\n\nA Claude Agent Skill for managing social media through [PostNext](https://postnext.io). It lets an agent connect-aware, upload media, compose, schedule, and publish posts across Twitter/X, Instagram, LinkedIn, Threads, YouTube, TikTok, and Bluesky, and read analytics - all via the PostNext public API with a single API key.\n\n## Install\n\nCopy this directory into your skills location (for Claude Code, a `skills/` directory the harness scans), then set your key:\n\n```bash\nexport POSTNEXT_API_KEY=<your key>   # create at https://postnext.io/account/api-keys\n```\n\nThe bundled `postnext` helper needs `curl` and `jq`. In an environment with no shell (for example claude.ai), the skill still works: every operation has a raw curl recipe in `references/`.\n\n## Quick start\n\n```bash\n./postnext channels                                   # what is connected\n./postnext post --provider twitter --text \"Hello\" --media ./hero.png\n./postnext schedule --provider instagram --text \"Launch\" --media ./a.jpg --at 2026-07-25T14:00:00Z\n./postnext analytics best-time twitter\n```\n\nRun `./postnext help` for the full command list.\n\n## What is in here\n\n| Path | What |\n|------|------|\n| `SKILL.md` | Skill entry point: setup, the core loop, and a router to the references. |\n| `postnext` | Bash helper (curl + jq) that handles the API's error-prone parts. |\n| `references/` | Per-area docs with raw curl recipes: posts, media, channels, analytics, errors. |\n| `docs/` | Design spec. |\n\n## Scope\n\nCovers the PostNext social publishing and analytics loop. Out of scope (use the PostNext MCP at `mcp.postnext.io` or the web app): connecting channels, WordPress/blog posts, brand-profile edits, and team management.\n\n## License\n\nMIT. See `LICENSE`. Not affiliated with post-bridge; inspired by the shape of its social-manager skill.\n\nFile v0.1.0:_meta.json\n\n{\n  \"ownerId\": \"kn7cy875pa1wnbqcte11mew4kd8ay84v\",\n  \"slug\": \"postnext-social-manager\",\n  \"version\": \"0.1.0\",\n  \"publishedAt\": 1784636459659\n}\n\nFile v0.1.0:references/analytics.md\n\n# Analytics\n\nAll analytics endpoints return `{success, data}`. Several require a paid tier (noted). Metrics exist only for **PUBLISHED** posts - per-post calls error on drafts or scheduled posts.\n\n## Freshness\n\nStored metrics are periodically snapshotted. To pull fresh numbers on demand:\n\n`POST /api/v1/analytics/sync` (paid) -> `{synced, summary:{twitterSnapped, instagramSnapped, tiktokSnapped, linkedinSnapped}}`. Rate-limited and bounded by per-platform caps. Call it before reading if you need current data.\n\n## Endpoints\n\n| Endpoint | Purpose | Params |\n|----------|---------|--------|\n| `GET /api/v1/analytics` | last-7-day counts of posts/ideas/assets | - |\n| `GET /api/v1/analytics/overview` | totals (impressions, reach, likes, comments, engagementRate) + vsPrevious | `channel`, `period` |\n| `GET /api/v1/analytics/posts` | per-post rows | `channel`, `period`, `sort=engagement|impressions|likes|recent` |\n| `GET /api/v1/analytics/daily` | per-day activity counts | `days` (1-60) |\n| `GET /api/v1/analytics/audience-growth` | follower time series | `channel`, `period` |\n| `GET /api/v1/analytics/engagement-over-time` (paid) | engagement rate per day | `channel`, `period` |\n| `GET /api/v1/analytics/best-time-to-post` (paid) | ranked posting slots from your own performance | `platform` (required), `timezone`, `topN` |\n| `GET /api/v1/analytics/post/{postId}` | latest metrics, one row per provider | - |\n| `GET /api/v1/analytics/post/{postId}/daily` | daily snapshots + day-over-day deltas | - |\n\n`period` is one of `7d`, `30d`, `90d`, `all` (default `30d`). `channel` filters to one provider/channel id; omit or `all` for no filter.\n\n## Capability flag\n\n`overview`, `posts`, and `engagement-over-time` may include a `capability` field: `ok`, `insights_unavailable`, `api_tier_limited`, `no_data_yet`, or `unsupported` (it can also be `null`/absent, which means normal). If it is present and not `ok`, tell the user why the numbers are thin (for example a platform whose API tier does not expose insights) rather than presenting zeros as real.\n\n`engagementRate` is a fraction (multiply by 100 for a percentage).\n\n## Examples\n\n```bash\nBASE=https://api-app.postnext.io\ncurl -sS \"$BASE/api/v1/analytics/overview?period=30d\" -H \"x-api-key: $POSTNEXT_API_KEY\"\ncurl -sS \"$BASE/api/v1/analytics/best-time-to-post?platform=instagram&timezone=Europe/Bucharest&topN=5\" -H \"x-api-key: $POSTNEXT_API_KEY\"\ncurl -sS -X POST \"$BASE/api/v1/analytics/sync\" -H \"x-api-key: $POSTNEXT_API_KEY\"\n```\n\nFile v0.1.0:references/channels.md\n\n# Channels (connections)\n\nChannels are connected in the PostNext web app - there is no API to start an OAuth connection. This skill only lists and reads them.\n\n## List connected channels\n\n`GET /api/connections` returns a **bare JSON array** (no envelope). Each item:\n\n```jsonc\n{\n  \"provider\": \"twitter\",           // one of twitter/instagram/linkedin/threads/youtube/tiktok/bluesky\n  \"channelName\": \"@yourbrand\",     // the connected handle - use this in a post's channelName\n  \"providerId\": \"tw_9f3a1c20e5\",   // platform-native account id - use this in a post's providerId\n  \"uniqueId\": \"...\",                 // internal UUID - do NOT use for posting\n  \"requiresAttention\": true,       // true only when a token refresh actually failed (re-auth needed)\n  \"isActive\": true,\n  \"avatar\": \"...\", \"createdAt\": \"...\", \"tokenExpiry\": \"...\"\n}\n```\n\n## Resolving providerId for a post (trap #3)\n\nTo post to a channel, pick the connection by matching `provider` (and `channelName` if the user named one), then copy its **`providerId`** and `channelName` into the post entry. Never use `uniqueId` - the publish worker needs the platform-native `providerId` or it fails silently.\n\n```bash\n# providerId + channelName for a given provider\ncurl -sS https://api-app.postnext.io/api/connections -H \"x-api-key: $POSTNEXT_API_KEY\" \\\n  | jq -r '.[] | select(.provider==\"twitter\") | \"\\(.channelName)\\t\\(.providerId)\"'\n```\n\nUse `requiresAttention == true` (not `tokenExpiry`) to decide whether a channel needs the user to re-authenticate in the web app before it can publish.\n\n## Check one channel\n\n`GET /api/connections/{provider}/{channelName}/check` returns a flat status object whose HTTP status mirrors the `status` field: `active` -> 200, `expired` -> 401, `invalid` -> 404.\n\n```jsonc\n{ \"status\": \"active\", \"message\": \"...\", \"expiresAt\": \"...\" }\n```\n\nFile v0.1.0:references/errors.md\n\n# Response shapes and errors\n\n## Success envelopes are inconsistent - check per endpoint\n\n| Shape | Endpoints |\n|-------|-----------|\n| `{success, data}` | most Posts, all Analytics, asset create-url/complete/get, single-post GET |\n| `{success, data, meta}` | `GET /api/posts` when `?page`/`?limit` is present |\n| `{success, data, pagination}` | `GET /api/assets/mine` (pagination is top-level, not `meta`) |\n| **bare array** | `GET /api/connections` |\n| **flat object, no wrapper** | `GET /api/v1/account`; connection `/check`; `upload/single` and `upload/url` (`{message, asset}`); `DELETE /api/assets/{id}` (`{message, success}`) |\n\nWhen parsing, do not assume a `data` key. The `postnext` helper unwraps `data` only when present.\n\n## Error shapes - there are three\n\n1. Standard: `{ \"success\": false, \"error\": { \"message\": \"...\" } }` (validation, quota 403).\n2. String variant: `{ \"success\": false, \"error\": \"Authentication required\" }` (401, bad/missing key) or `{ \"success\": false, \"error\": \"Analytics is a paid feature\", \"code\": \"ANALYTICS_PAID_ONLY\" }` (paid-tier gates). Here `error` is a string, not an object.\n3. Payment (402): a separate top-level shape:\n   ```jsonc\n   { \"error\": \"PAYMENT_REQUIRED\", \"code\": \"SUBSCRIPTION_INACTIVE\",\n     \"status\": \"past_due\", \"currency\": \"usd\",\n     \"actions\": { \"retryUrl\": \"...\", \"updateCardUrl\": \"...\", \"downgradeUrl\": \"...\" } }\n   ```\n\n## Status codes\n\n| Code | Meaning | What to tell the user |\n|------|---------|-----------------------|\n| 401 | bad or missing `x-api-key` | check the key |\n| 402 | subscription in a payment-failure state | fix billing (use `actions.updateCardUrl`) |\n| 403 | posting not allowed on the plan, or monthly post quota reached | upgrade or wait for the quota to reset |\n| 413 | media over the size cap (50 MB presigned) | use the multipart single path, or shrink |\n| 415 | uploaded bytes do not match the declared type | re-upload with the correct contentType |\n| 422 | unsupported media for platform (e.g. video on LinkedIn) | remove the video or pick another channel |\n| 429 | rate limited | back off and retry |\n\n## Known quirks (live-verified)\n\n- **GET a missing or just-deleted post returns HTTP 500**, not 404: `{\"success\":false,\"error\":\"Internal Server Error\",\"message\":\"Post group not found\"}`. Treat a 500 whose `message` is `Post group not found` as not-found.\n- A 500 whose body still parses as `{success:false, ..., message}` is a handled not-found/edge case, not necessarily an outage.\n\n## Rate limits\n\nRoughly: standard 100 requests / 15 min; social endpoints ~50 / 15 min; auth ~5 / 60 min. On a 429, pause and retry rather than hammering.\n\nFile v0.1.0:references/media.md\n\n# Media\n\nUpload an image or video, then put the returned **`asset.url`** into a post's `content.media[].url`. The `assetId` is not used by the publish path.\n\n## Which upload path\n\n| Case | Endpoint | Cap | Needs `userId`? |\n|------|----------|-----|-----------------|\n| image or file <= 50 MB, one of jpeg/png/webp/gif/mp4/webm | 3-step presigned (`create-url` -> PUT -> `complete`) | 50 MB | no (from key) |\n| large video (> 50 MB, up to 500 MB) or other type | multipart `POST /api/assets/upload/single` | 500 MB | **yes** = account `uniqueId` |\n| a remote image URL | `POST /api/assets/upload/url` | 25 MB | **yes**; **image only** (a video URL is stored as .jpg) |\n\nGet the account `uniqueId` from `GET /api/v1/account` (`.uniqueId`).\n\n## 3-step presigned flow (preferred for images and small video)\n\n```bash\nBASE=https://api-app.postnext.io\n# 1. request an upload URL (declares type + exact byte size; pre-charges storage quota)\nINIT=$(curl -sS -X POST $BASE/api/assets/upload/create-url \\\n  -H \"x-api-key: $POSTNEXT_API_KEY\" -H 'Content-Type: application/json' \\\n  -d '{\"filename\":\"hero.png\",\"contentType\":\"image/png\",\"sizeBytes\":48213}')\nUPLOAD_URL=$(echo \"$INIT\" | jq -r .data.uploadUrl)\nUPLOAD_ID=$(echo \"$INIT\" | jq -r .data.uploadId)\n\n# 2. PUT the bytes. Content-Type MUST equal the declared contentType or S3 rejects the signature.\ncurl -sS -X PUT \"$UPLOAD_URL\" -H 'Content-Type: image/png' --data-binary @hero.png\n\n# 3. finalize -> returns the asset; use .data.url in the post\ncurl -sS -X POST $BASE/api/assets/upload/complete \\\n  -H \"x-api-key: $POSTNEXT_API_KEY\" -H 'Content-Type: application/json' \\\n  -d \"{\\\"uploadId\\\":\\\"$UPLOAD_ID\\\"}\" | jq -r .data.url\n```\n\n`contentType` allow-list (presigned): `image/jpeg, image/png, image/webp, image/gif, video/mp4, video/webm`. `sizeBytes` must be the true byte size and <= 50 MB (else 413). `complete` verifies the bytes by magic number (415 on mismatch) and reconciles the storage charge to the real size.\n\n## Multipart single (large video)\n\n```bash\nUID=$(curl -sS https://api-app.postnext.io/api/v1/account -H \"x-api-key: $POSTNEXT_API_KEY\" | jq -r .uniqueId)\ncurl -sS -X POST https://api-app.postnext.io/api/assets/upload/single \\\n  -H \"x-api-key: $POSTNEXT_API_KEY\" \\\n  -F \"file=@big.mp4;type=video/mp4\" -F \"userId=$UID\" | jq -r .asset.url\n```\nNote the different response shape: single/url return a flat `{message, asset}` (no `data` wrapper); presigned `complete` returns `{success, data: asset}`.\n\n## Then attach it\n\n```jsonc\n\"content\": { \"text\": \"...\", \"media\": [{ \"url\": \"<asset.url>\", \"type\": \"IMAGE\" }] }\n```\n`type` is `IMAGE`, `VIDEO`, or `GIF`. For a multi-image Instagram carousel, verify whether a top-level `mediaType: \"CAROUSEL\"` is also required (unverified).\n\n## Manage assets\n\n- `GET /api/assets/mine?page=1&limit=50` -> `{success, data, pagination}` (pagination is top-level).\n- `GET /api/assets/{assetId}` -> `{success, data}`.\n- `DELETE /api/assets/{assetId}` -> flat `{message, success}`.\n\nFile v0.1.0:references/posts.md\n\n# Posts\n\nCreate a post by sending a `providers` map. Each key is a **bare provider name** and each value is one channel's entry.\n\n## Request shape (CreatePostRequest)\n\n```jsonc\n{\n  \"providers\": {\n    \"twitter\": {                              // BARE name. NOT \"twitter:@handle\".\n      \"channelName\": \"@yourbrand\",            // the real connected handle (from GET /connections)\n      \"providerId\":  \"tw_9f3a1c20e5\",         // connection.providerId (NOT uniqueId)","readmeExcerpt":"Skill: postnext-social-manager Owner: postnextio Summary: Manage social media through PostNext - schedule, publish, and analyze posts across Twitter/X, Instagram, LinkedIn, Threads, YouTube, TikTok, and Bluesky via the PostNext public API. Use when the user wants to draft, schedule, or publish social posts, upload media for posts, check what is queued, cancel a scheduled post or delete a post from PostNext, read soci","codeSnippets":[],"executableExamples":[{"language":"bash","snippet":"export POSTNEXT_API_KEY=<your key>   # create at https://postnext.io/account/api-keys"},{"language":"bash","snippet":"./postnext channels                                   # what is connected\n./postnext post --provider twitter --text \"Hello\" --media ./hero.png   # prints a preview, sends nothing\n./postnext post --provider twitter --text \"Hello\" --media ./hero.png --confirm <code>   # after the user approves\n./postnext schedule --provider instagram --text \"Launch\" --media ./a.jpg --at 2026-07-25T14:00:00Z\n./postnext analytics best-time twitter"},{"language":"bash","snippet":"curl -sS \"$BASE/api/v1/analytics/overview?period=30d\" -H \"x-api-key: $POSTNEXT_API_KEY\""},{"language":"bash","snippet":"curl -sS \"$BASE/api/v1/analytics/best-time-to-post?platform=instagram&timezone=Europe/Bucharest&topN=5\" -H \"x-api-key: $POSTNEXT_API_KEY\""},{"language":"bash","snippet":"curl -sS -X POST \"$BASE/api/v1/analytics/sync\" -H \"x-api-key: $POSTNEXT_API_KEY\""},{"language":"bash","snippet":"BASE=https://api-app.postnext.io\ncurl -sS \"$BASE/api/v1/analytics/overview?period=30d\" -H \"x-api-key: $POSTNEXT_API_KEY\"\ncurl -sS \"$BASE/api/v1/analytics/best-time-to-post?platform=instagram&timezone=Europe/Bucharest&topN=5\" -H \"x-api-key: $POSTNEXT_API_KEY\"\ncurl -sS -X POST \"$BASE/api/v1/analytics/sync\" -H \"x-api-key: $POSTNEXT_API_KEY\""}],"parameters":null,"dependencies":[],"permissions":[],"extractedFiles":[{"path":"SKILL.md","content":"---\nname: postnext-social-manager\ndescription: Manage social media through PostNext - schedule, publish, and analyze posts across Twitter/X, Instagram, LinkedIn, Threads, YouTube, TikTok, and Bluesky via the PostNext public API. Use when the user wants to draft, schedule, or publish social posts, upload media for posts, check what is queued, cancel a scheduled post or delete a post from PostNext, read social analytics (engagement, best time to post, audience growth) or refresh them with an analytics sync, and has a PostNext account and API key. Publishing, scheduling, cancelling, deleting and uploading always preview first and need the user's approval.\nallowed-tools: Bash(./postnext:*), Bash(curl:*), Bash(jq:*), Read\nmetadata:\n  requires:\n    bins: [bash, curl, jq]\n    env: [POSTNEXT_API_KEY]\n---\n\n# PostNext Social Manager\n\nDrive a PostNext account programmatically: connect channels, upload media, compose and schedule or publish posts across platforms, and read analytics. Wraps the PostNext public REST API with an API key.\n\n## Setup\n\n1. The user creates an API key at https://postnext.io/account/api-keys (shown only once).\n2. Export it: `export POSTNEXT_API_KEY=<key>`\n3. Base URL is `https://api-app.postnext.io`. Auth header on every request: `x-api-key: $POSTNEXT_API_KEY`.\n\nTwo ways to use this skill:\n- **Helper (recommended where a shell is available):** the bundled `postnext` script (needs `curl` + `jq`). It handles the fiddly, error-prone parts for you. Run `./postnext help`.\n- **Raw curl (works anywhere, including no-filesystem sandboxes):** every operation has a copy-paste curl recipe in `references/`.\n\n## The core loop\n\n1. **Connect** (one-time, done by the user in the PostNext web app - there is no API to connect a channel). Confirm what is connected: `postnext channels`.\n2. **Upload media** if the post has an image or video: `postnext upload ./clip.mp4` prints an asset URL (or pass the file straight to `--media` in step 3).\n3. **Compose + publish now** or **schedule**:\n   - `postnext post --provider twitter --text \"...\" --media ./a.png`\n   - `postnext schedule --provider instagram --text \"...\" --media ./a.jpg --at 2026-07-25T14:00:00Z`\n\n   Every command that changes the account (`post`, `schedule`, `cancel`, `delete`, `upload`) first prints a **preview and sends nothing** (exit code 3). Show the preview to the user. Only after they approve it, run the identical command again with `--confirm <code>` from the preview. The code covers that exact operation: if the text, media, time or target changes, preview again and get a fresh approval.\n4. **Measure**: `postnext analytics overview`, `postnext analytics best-time twitter`, `postnext results`.\n\n## Safety\n\n- The API key acts on the user's real social accounts. Use a dedicated key, and never paste it into posts, logs or URLs.\n- Confirm with the user before any publish, schedule, cancel, delete or upload. The helper enforces this: without the `--confirm` code from its own preview it sends nothi"},{"path":"README.md","content":"# postnext-social-manager\n\nA Claude Agent Skill for managing social media through [PostNext](https://postnext.io). It lets an agent connect-aware, upload media, compose, schedule, and publish posts across Twitter/X, Instagram, LinkedIn, Threads, YouTube, TikTok, and Bluesky, and read analytics - all via the PostNext public API with a single API key.\n\n## Install\n\nCopy this directory into your skills location (for Claude Code, a `skills/` directory the harness scans), then set your key:\n\n```bash\nexport POSTNEXT_API_KEY=<your key>   # create at https://postnext.io/account/api-keys\n```\n\nThe bundled `postnext` helper needs `curl` and `jq`. In an environment with no shell (for example claude.ai), the skill still works: every operation has a raw curl recipe in `references/`.\n\n## Quick start\n\n```bash\n./postnext channels                                   # what is connected\n./postnext post --provider twitter --text \"Hello\" --media ./hero.png   # prints a preview, sends nothing\n./postnext post --provider twitter --text \"Hello\" --media ./hero.png --confirm <code>   # after the user approves\n./postnext schedule --provider instagram --text \"Launch\" --media ./a.jpg --at 2026-07-25T14:00:00Z\n./postnext analytics best-time twitter\n```\n\nRun `./postnext help` for the full command list. `post`, `schedule`, `cancel`, `delete` and `upload` always preview first and need the `--confirm` code from that preview to run. `bash tests/confirm.sh` checks this offline.\n\n## What is in here\n\n| Path | What |\n|------|------|\n| `SKILL.md` | Skill entry point: setup, the core loop, and a router to the references. |\n| `postnext` | Bash helper (curl + jq) that handles the API's error-prone parts. |\n| `references/` | Per-area docs with raw curl recipes: posts, media, channels, analytics, errors. |\n| `tests/confirm.sh` | Proves the five account-changing commands send nothing without a matching `--confirm` code. |\n| `docs/` | Design spec. |\n\n## Scope\n\nCovers the PostNext social publishing and analytics loop. Out of scope (use the PostNext MCP at `mcp.postnext.io` or the web app): connecting channels, WordPress/blog posts, brand-profile edits, and team management.\n\n## License\n\nMIT. See `LICENSE`. Not affiliated with post-bridge; inspired by the shape of its social-manager skill."},{"path":"_meta.json","content":"{\n  \"ownerId\": \"kn7cy875pa1wnbqcte11mew4kd8ay84v\",\n  \"slug\": \"postnext-social-manager\",\n  \"version\": \"0.1.5\",\n  \"publishedAt\": 1791268438875\n}"},{"path":"references/analytics.md","content":"# Analytics\n\nAll analytics endpoints return `{success, data}`. Several require a paid tier (noted). Metrics exist only for **PUBLISHED** posts - per-post calls error on drafts or scheduled posts.\n\n## Freshness\n\nStored metrics are periodically snapshotted. To pull fresh numbers on demand:\n\n`POST /api/v1/analytics/sync` (paid) -> `{synced, summary:{twitterSnapped, instagramSnapped, tiktokSnapped, linkedinSnapped}}`. Rate-limited and bounded by per-platform caps. Call it before reading if you need current data.\n\n## Endpoints\n\n| Endpoint | Purpose | Params |\n|----------|---------|--------|\n| `GET /api/v1/analytics` | last-7-day counts of posts/ideas/assets | - |\n| `GET /api/v1/analytics/overview` | totals (impressions, reach, likes, comments, engagementRate) + vsPrevious | `channel`, `period` |\n| `GET /api/v1/analytics/posts` | per-post rows | `channel`, `period`, `sort=engagement|impressions|likes|recent` |\n| `GET /api/v1/analytics/daily` | per-day activity counts | `days` (1-60) |\n| `GET /api/v1/analytics/audience-growth` | follower time series | `channel`, `period` |\n| `GET /api/v1/analytics/engagement-over-time` (paid) | engagement rate per day | `channel`, `period` |\n| `GET /api/v1/analytics/best-time-to-post` (paid) | ranked posting slots from your own performance | `platform` (required), `timezone`, `topN` |\n| `GET /api/v1/analytics/post/{postId}` | latest metrics, one row per provider | - |\n| `GET /api/v1/analytics/post/{postId}/daily` | daily snapshots + day-over-day deltas | - |\n\n`period` is one of `7d`, `30d`, `90d`, `all` (default `30d`). `channel` filters to one provider/channel id; omit or `all` for no filter.\n\n## Capability flag\n\n`overview`, `posts`, and `engagement-over-time` may include a `capability` field: `ok`, `insights_unavailable`, `api_tier_limited`, `no_data_yet`, or `unsupported` (it can also be `null`/absent, which means normal). If it is present and not `ok`, tell the user why the numbers are thin (for example a platform whose API tier does not expose insights) rather than presenting zeros as real.\n\n`engagementRate` is a fraction (multiply by 100 for a percentage).\n\n## Examples\n\n```bash\nBASE=https://api-app.postnext.io\ncurl -sS \"$BASE/api/v1/analytics/overview?period=30d\" -H \"x-api-key: $POSTNEXT_API_KEY\"\ncurl -sS \"$BASE/api/v1/analytics/best-time-to-post?platform=instagram&timezone=Europe/Bucharest&topN=5\" -H \"x-api-key: $POSTNEXT_API_KEY\"\ncurl -sS -X POST \"$BASE/api/v1/analytics/sync\" -H \"x-api-key: $POSTNEXT_API_KEY\"\n```"},{"path":"references/channels.md","content":"# Channels (connections)\n\nChannels are connected in the PostNext web app - there is no API to start an OAuth connection. This skill only lists and reads them.\n\n## List connected channels\n\n`GET /api/connections` returns a **bare JSON array** (no envelope). Each item:\n\n```jsonc\n{\n  \"provider\": \"twitter\",           // one of twitter/instagram/linkedin/threads/youtube/tiktok/bluesky\n  \"channelName\": \"@yourbrand\",     // the connected handle - use this in a post's channelName\n  \"providerId\": \"tw_9f3a1c20e5\",   // platform-native account id - use this in a post's providerId\n  \"uniqueId\": \"...\",                 // internal UUID - do NOT use for posting\n  \"requiresAttention\": true,       // true only when a token refresh actually failed (re-auth needed)\n  \"isActive\": true,\n  \"avatar\": \"...\", \"createdAt\": \"...\", \"tokenExpiry\": \"...\"\n}\n```\n\n## Resolving providerId for a post (trap #3)\n\nTo post to a channel, pick the connection by matching `provider` (and `channelName` if the user named one), then copy its **`providerId`** and `channelName` into the post entry. Never use `uniqueId` - the publish worker needs the platform-native `providerId` or it fails silently.\n\n```bash\n# providerId + channelName for a given provider\ncurl -sS https://api-app.postnext.io/api/connections -H \"x-api-key: $POSTNEXT_API_KEY\" \\\n  | jq -r '.[] | select(.provider==\"twitter\") | \"\\(.channelName)\\t\\(.providerId)\"'\n```\n\nUse `requiresAttention == true` (not `tokenExpiry`) to decide whether a channel needs the user to re-authenticate in the web app before it can publish.\n\n## Check one channel\n\n`GET /api/connections/{provider}/{channelName}/check` returns a flat status object whose HTTP status mirrors the `status` field: `active` -> 200, `expired` -> 401, `invalid` -> 404.\n\n```jsonc\n{ \"status\": \"active\", \"message\": \"...\", \"expiresAt\": \"...\" }\n```"}],"languages":[],"docsSourceLabel":"CLAWHUB","editorialOverview":null,"editorialQuality":{"score":100,"threshold":65,"status":"thin","wordCount":1800,"uniquenessScore":41,"reasons":["uniqueness-below-45"]}},"media":{"evidence":{"source":"no-media","verified":false,"confidence":"low","updatedAt":"2026-10-09T09:17:28.403Z","emptyReason":"No screenshots, media assets, or demo links are available."},"primaryImageUrl":null,"mediaAssetCount":0,"assets":[],"demoUrl":null},"ownerResources":{"evidence":{"source":"unclaimed","verified":false,"confidence":"low","updatedAt":"2026-10-09T09:17:28.403Z","emptyReason":"This page has not been claimed by the agent owner."},"hasCustomPage":false,"customPageUpdatedAt":null,"customLinks":[],"structuredLinks":{"docsUrl":null,"demoUrl":null,"supportUrl":null,"pricingUrl":null,"statusUrl":null},"customPage":null},"relatedAgents":{"evidence":{"source":"protocol-neighbors","verified":false,"confidence":"medium","updatedAt":"2026-10-09T13:00:11.761Z","emptyReason":null},"items":[{"id":"b917f68a-ebff-438e-84f8-3f4b2494c0bc","entityType":"agent","canonicalPath":"/agent/activepieces-activepieces","slug":"activepieces-activepieces","name":"activepieces","description":"AI Agents & MCPs & AI Workflow Automation • (~400 MCP servers for AI agents) • AI Automation / AI Agent with MCPs • AI Workflows & AI Agents • MCPs for AI Agents","url":"https://github.com/activepieces/activepieces","homepage":"https://www.activepieces.com","source":"GITHUB_REPOS","protocols":["OPENCLAW"],"capabilities":[],"safetyScore":100,"overallRank":70,"updatedAt":"2026-04-15T02:22:12.426Z","createdAt":"2026-02-25T03:38:12.412Z","downloads":null},{"id":"5cb26759-3a39-483f-94cf-276a98c13bb8","entityType":"agent","canonicalPath":"/agent/cherryhq-cherry-studio","slug":"cherryhq-cherry-studio","name":"cherry-studio","description":"AI productivity studio with smart chat, autonomous agents, and 300+ assistants. Unified access to frontier LLMs","url":"https://github.com/CherryHQ/cherry-studio","homepage":"https://cherry-ai.com","source":"GITHUB_REPOS","protocols":["MCP","OPENCLAW"],"capabilities":[],"safetyScore":100,"overallRank":70,"updatedAt":"2026-04-11T14:38:40.986Z","createdAt":"2026-02-25T03:38:19.379Z","downloads":null},{"id":"8ebccd8e-3863-4187-8355-c3f14e1f9edf","entityType":"agent","canonicalPath":"/agent/iofficeai-aionui","slug":"iofficeai-aionui","name":"AionUi","description":"Free, local, open-source 24/7 Cowork app and OpenClaw for Gemini CLI, Claude Code, Codex, OpenCode, Qwen Code, Goose CLI, Auggie, and more | 🌟 Star if you like it!","url":"https://github.com/iOfficeAI/AionUi","homepage":"https://www.aionui.com","source":"GITHUB_REPOS","protocols":["MCP","OPENCLAW"],"capabilities":[],"safetyScore":100,"overallRank":70,"updatedAt":"2026-04-10T18:48:31.762Z","createdAt":"2026-02-25T03:38:16.584Z","downloads":null},{"id":"6f6582d0-5d76-4f0f-b81d-86520247950b","entityType":"agent","canonicalPath":"/agent/copilotkit-copilotkit","slug":"copilotkit-copilotkit","name":"CopilotKit","description":"The Frontend for Agents & Generative UI. React + Angular","url":"https://github.com/CopilotKit/CopilotKit","homepage":"https://docs.copilotkit.ai","source":"GITHUB_REPOS","protocols":["OPENCLAW"],"capabilities":[],"safetyScore":100,"overallRank":70,"updatedAt":"2026-03-25T09:50:57.846Z","createdAt":"2026-02-25T03:39:14.617Z","downloads":null}],"links":{"hub":"/agent","source":"/agent/source/clawhub","protocols":[{"label":"OpenClaw","href":"/agent/protocol/openclew"}]}}}