{"openapi":"3.1.0","info":{"title":"Fuxux API","version":"1.1.0","description":"Schedule and publish to 12 social platforms, manage connected accounts, upload media, and generate AI captions from your own apps, agents, or CI pipelines. Every endpoint below mirrors something you can do in the Fuxux dashboard.\n\n## Access\nThe API is part of the **API add-on** ($5/month on any paid plan: Creator, Growth or Pro). API keys aren't available on the Free plan.\n\n## Authentication\nSend your **personal API key** on every authenticated request:\n\n`Authorization: Bearer fx_live_…`\n\nCreate keys on the **API keys** page of the dashboard. The full secret is shown **only once** when you create or rotate a key; store it in an environment variable. You can have up to **10 active keys** and create or rotate up to **20 a day**. Use one key per integration: rotating a key revokes the old one, so a shared key breaks every client that uses it.\n\n`GET /api/settings/profile` is the recommended connection test: it's cheap and has no side effects. A key on an account without the API add-on is refused with 401, like a wrong key.\n\n## Posting\n`POST /api/posts` publishes immediately, schedules with `scheduled_at`, or saves a draft with `draft: true`. Send `scheduled_at` with `Z` or an offset (`2027-01-15T09:30:00-08:00`): a time without one is read as UTC, not as the account's time zone. `captions` maps each platform id to its caption. Add `targets` to choose exact accounts and per-platform options; it is how you pick between several accounts on one platform. Platform ids: `twitter`, `instagram`, `linkedin`, `facebook`, `tiktok`, `youtube`, `bluesky`, `threads`, `pinterest`, `discord`, `kick`, `twitch`. Reddit is not available through the API: Reddit posts are created in the Fuxux web app. Google Business Profile is coming soon and can't be connected or posted to yet.\n\nEach platform's own rules still apply (for example, Instagram and TikTok need media). Connect accounts in Fuxux first, then list them with `GET /api/accounts`.\n\n## Rate limits and polling\n**120 requests/min per key** and **300/min per account** across all keys; creating posts is limited to **30 per 5 minutes**. Over a limit you get **429** with `Retry-After` (seconds) — wait that long. Don't poll: publishing settles on the order of minutes, so check a post's status at most once a minute (or use a webhook).\n\n## Webhooks\nSet a webhook URL on the API keys page to receive a `post.finished` event when a post's publishing completes. Its `status` is `published` when at least one platform succeeded, so read the per-platform results too. A delivery is tried once. Each delivery is signed: `X-Fuxux-Signature: sha256=<hex>` is the HMAC-SHA256 of the raw body with your webhook secret.\n\n## Errors\nStandard HTTP status codes with JSON `{ \"error\": \"…\" }` bodies:\n- **400** — validation, including a platform rule (such as Instagram needing media), no connected account for any captioned platform (without `targets`, platforms with no account are skipped: compare `results` with your captions), or editing a post that is not a draft or scheduled\n- **401** — missing, invalid or revoked key, or the API add-on isn't active\n- **403** — see `code`: `post_quota_reached` (the plan's post allowance is used up), `x_allowance` (the month's X allowance is used up) or `reddit_web_only` (Reddit can only be posted to from the web app)\n- **404** — the post doesn't exist in your workspace\n- **409** — the post started publishing during your request, or a delete was sent while it is publishing\n- **429** — rate limited; honor `Retry-After`\n\nRotate any key that is ever exposed.\n\n## Other ways in\nPrefer not to write HTTP yourself? The [MCP server](/mcp/docs) connects Claude Code, Claude Desktop, Cursor and [Manus/Cue](/cue); the [agent skill](/agent-skill) packages the API for OpenClaw; [Zapier](/zapier) connects without code; the [WordPress plugin](/platforms/wordpress) cross-posts when you publish; and the [browser extensions](/extensions) share any page to your accounts."},"servers":[{"url":"https://www.fuxux.com","description":"Production"}],"tags":[{"name":"Dashboard","description":"Home dashboard summary"},{"name":"Usage","description":"Plan limits and meters"},{"name":"Posts","description":"Compose, schedule, publish, list"},{"name":"Accounts","description":"Connected social accounts"},{"name":"AI","description":"In-app AI caption generation (authenticated)"},{"name":"Analytics","description":"Publishing analytics"},{"name":"Settings","description":"Profile, password, API keys, account deletion"},{"name":"Media","description":"Upload files for posts"},{"name":"Tools","description":"Unauthenticated helpers; rate-limited per IP"}],"components":{"schemas":{"Error":{"type":"object","properties":{"error":{"type":"string"},"code":{"type":"string","description":"Present on some refusals, for example `post_quota_reached`, `x_allowance`, `reddit_web_only`."}}},"ProfileResponse":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"email":{"type":"string","format":"email","nullable":true},"full_name":{"type":"string"},"plan":{"type":"string","enum":["free","creator","growth","pro"]},"created_at":{"type":"string"},"onboarding_completed":{"type":"boolean"},"onboarding_step":{"type":"integer","minimum":0},"email_verified_at":{"type":"string","format":"date-time","nullable":true},"email_confirmed":{"type":"boolean","description":"Whether the account's email address has completed Supabase Auth confirmation."},"has_api_access":{"type":"boolean","description":"Whether the plan includes the API access add-on. An API key on an account without the add-on is rejected with 401, the same answer as a wrong key."},"subscription_status":{"type":"string","nullable":true,"description":"Live Stripe subscription status (e.g. `trialing`, `active`, `canceled`), or `null` if there is no subscription."},"trial_end":{"type":"string","format":"date-time","nullable":true,"description":"End of the Stripe trial period, or `null` if not currently trialing."}}},"DashboardResponse":{"type":"object","properties":{"profile":{"type":"object","properties":{"full_name":{"type":"string"}}},"stats":{"type":"object","properties":{"postsThisMonth":{"type":"integer"},"postsLastMonth":{"type":"integer"},"momChange":{"type":"integer","nullable":true,"description":"Month-over-month % change; null if no prior month data"},"totalPublished":{"type":"integer"},"upcomingThisWeek":{"type":"integer"},"connectedAccounts":{"type":"integer"}}},"upcomingPosts":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"scheduled_at":{"type":"string","format":"date-time"},"status":{"type":"string"},"raw_prompt":{"type":"string","nullable":true},"post_variants":{"type":"array","items":{"type":"object","properties":{"platform":{"type":"string"},"content":{"type":"string"}}}}}}},"platforms":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"icon":{"type":"string"},"connected":{"type":"boolean"},"posts":{"type":"integer"},"username":{"type":"string","nullable":true}}}}}},"UsageResponse":{"type":"object","properties":{"plan":{"type":"string","enum":["free","creator","growth","pro"]},"limits":{"type":"object","properties":{"accounts":{"type":"integer","description":"-1 means unlimited."},"aiPerMonth":{"type":"integer","nullable":true},"postsPerMonth":{"type":"integer","nullable":true,"description":"Published posts allowed per month on the plan of the space's owner; null when unlimited."},"xUnitsPerMonth":{"type":"integer","nullable":true,"description":"Monthly allowance for publishing to X. 0 means X is not included in the plan. A post is 1 unit, a post with a link 13."}}},"usage":{"type":"object","properties":{"accounts":{"type":"integer"},"aiThisMonth":{"type":"integer"},"postsThisMonth":{"type":"integer"},"xUnitsThisMonth":{"type":"integer"}}}}},"PostVariant":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"platform":{"type":"string"},"content":{"type":"string"},"status":{"type":"string"},"error_message":{"type":"string","nullable":true},"platform_post_id":{"type":"string","nullable":true},"published_at":{"type":"string","format":"date-time","nullable":true}}},"PostRow":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"status":{"type":"string","enum":["draft","scheduled","publishing","published","failed"]},"raw_prompt":{"type":"string","nullable":true},"scheduled_at":{"type":"string","format":"date-time","nullable":true},"published_at":{"type":"string","format":"date-time","nullable":true},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time","description":"When the post last changed. Only present on GET /api/posts when `include=updated_at` is sent."},"post_variants":{"type":"array","items":{"$ref":"#/components/schemas/PostVariant"}}}},"PostsListResponse":{"type":"object","properties":{"posts":{"type":"array","items":{"$ref":"#/components/schemas/PostRow"}},"hasMore":{"type":"boolean","description":"Whether another page exists."},"nextOffset":{"type":"integer","description":"The `offset` to send for the next page."},"counts":{"type":"object","properties":{"total":{"type":"integer"},"scheduled":{"type":"integer"},"published":{"type":"integer"},"drafts":{"type":"integer"},"failed":{"type":"integer"}}}}},"PostCreate":{"type":"object","description":"`captions` is required unless `draft` is `true`, in which case `prompt` or at least one caption must be present instead.","required":["captions"],"properties":{"prompt":{"type":"string","maxLength":500,"description":"Optional idea stored on the post"},"captions":{"type":"object","additionalProperties":{"type":"string"},"description":"Map of platform id → caption. Platform ids: `twitter`, `instagram`, `linkedin`, `facebook`, `tiktok`, `youtube`, `bluesky`, `threads`, `pinterest`, `discord`, `kick`, `twitch`. Unknown platform keys are ignored. Without `targets`, each captioned platform posts to its first connected account. Reddit is not available through the API: Reddit posts are created in the Fuxux web app, and a `reddit` caption or target is refused with 403 `reddit_web_only`. Google Business Profile is coming soon and can't be connected or posted to yet."},"targets":{"type":"array","items":{"$ref":"#/components/schemas/PostTarget"},"description":"Optional: exactly which connected accounts to post to, with per-platform options. Each target needs a caption for its platform in `captions`. Needed for picking between several accounts on one platform."},"scheduled_at":{"type":"string","format":"date-time","description":"If set, post is scheduled (must be in the future). If omitted, the app publishes immediately. Mutually exclusive with `draft`."},"media_urls":{"type":"array","items":{"type":"string","format":"uri"},"maxItems":4,"description":"Public URLs from `POST /api/media/upload`"},"draft":{"type":"boolean","description":"If `true`, saves the post as a draft instead of scheduling or publishing it. `captions` is not required when `draft` is `true`, but `prompt` or at least one caption must be present. Mutually exclusive with `scheduled_at`."}}},"PostTarget":{"type":"object","required":["account_id","platform"],"properties":{"account_id":{"type":"string","format":"uuid","description":"Connected account id from `GET /api/accounts`"},"platform":{"type":"string","enum":["twitter","instagram","linkedin","facebook","tiktok","youtube","bluesky","threads","pinterest","discord","kick","twitch"],"description":"Must match the account's platform"},"format":{"type":"string","enum":["feed","story"],"description":"Instagram and Facebook only; defaults to `feed`"},"boardId":{"type":"string","description":"Pinterest only: id of the board to pin to. If omitted, the pin goes to the account's first public board."},"tiktokOptions":{"type":"object","description":"TikTok only. `mode` `inbox` (default) sends the video to the creator's TikTok inbox to finish; `direct` publishes it (needs Direct Post access on the account).","properties":{"mode":{"type":"string","enum":["inbox","direct"]},"privacyLevel":{"type":"string","description":"One of the privacy levels TikTok offers the account (for example `SELF_ONLY`)"},"disableComment":{"type":"boolean"},"disableDuet":{"type":"boolean"},"disableStitch":{"type":"boolean"},"brandOrganic":{"type":"boolean","description":"Promotional content (your own brand)"},"brandContent":{"type":"boolean","description":"Paid partnership (branded content); can't be private"}}},"category":{"type":"object","description":"Twitch and Kick only: stream category set with the title (Twitch game id or Kick category id)","required":["id","name"],"properties":{"id":{"type":"string"},"name":{"type":"string"}}}}},"PublishResult":{"type":"object","properties":{"platform":{"type":"string"},"account_id":{"type":"string","nullable":true},"account_label":{"type":"string","nullable":true,"description":"Display label for the account, e.g. \"@brand\"."},"success":{"type":"boolean"},"platform_post_id":{"type":"string","nullable":true},"error":{"type":"string","nullable":true},"still_processing":{"type":"boolean","description":"Some networks confirm publish status asynchronously. If true, `success` is false but the post was accepted and may still go live — check the post's status again shortly rather than treating this as a hard failure."},"inbox_pending":{"type":"boolean","description":"The platform accepted the media into an inbox/draft flow (e.g. TikTok's video upload inbox) rather than publishing directly. Counted as success, but the post is not necessarily live yet — the creator may need to finish posting from within that platform's own app."}}},"AnalyticsResponse":{"type":"object","properties":{"chartDays":{"type":"integer","enum":[7,14,30,90]},"summary":{"type":"object","properties":{"totalPublished":{"type":"integer"},"publishedThisMonth":{"type":"integer"},"publishedLastMonth":{"type":"integer"},"momChange":{"type":"integer","nullable":true},"scheduled":{"type":"integer"},"successRate":{"type":"number","nullable":true,"description":"% of variants published successfully in the selected window"}}},"dailyPosts":{"type":"array","items":{"type":"object","properties":{"date":{"type":"string","description":"YYYY-MM-DD"},"count":{"type":"integer"}}}},"platformBreakdown":{"type":"array","items":{"type":"object","properties":{"platform":{"type":"string"},"name":{"type":"string"},"icon":{"type":"string"},"published":{"type":"integer"},"failed":{"type":"integer"},"engagement":{"$ref":"#/components/schemas/EngagementTotals"},"postsWithInsights":{"type":"integer","description":"How many of this platform's published posts in the window have synced engagement data"}}}},"engagement":{"allOf":[{"$ref":"#/components/schemas/EngagementTotals"},{"type":"object","properties":{"postsWithInsights":{"type":"integer","description":"How many published posts in the window have synced engagement data"}}}],"description":"Aggregated real engagement (likes/comments/shares/views/impressions/reach) synced from the platforms for posts published in the selected window. engagement_rate is not summed here since it's a per-post percentage; it's only present per-post on recentPosts[].post_variants[].insights."},"recentPosts":{"type":"array","items":{"type":"object","additionalProperties":true,"description":"Each item's post_variants[] may include an `insights` object (likes, comments, shares, views, impressions, reach, engagement_rate, and other platform-specific fields) and `insights_updated_at`, where synced."}}}},"EngagementTotals":{"type":"object","properties":{"likes":{"type":"integer"},"comments":{"type":"integer"},"shares":{"type":"integer"},"views":{"type":"integer"},"impressions":{"type":"integer"},"reach":{"type":"integer"}}},"AiCaptionsResponse":{"type":"object","properties":{"captions":{"type":"object","additionalProperties":{"type":"string"},"description":"Platform id → generated caption"}}}}},"paths":{"/api/dashboard":{"get":{"tags":["Dashboard"],"summary":"Dashboard summary","description":"Profile name, stats, next scheduled posts, and per-platform connection snapshot.","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DashboardResponse"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many requests"}}}},"/api/usage":{"get":{"tags":["Usage"],"summary":"Plan limits and current usage","description":"Returns the current plan, active workspace, and usage counters for account and AI limits.","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UsageResponse"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/posts":{"get":{"tags":["Posts"],"summary":"List posts","parameters":[{"name":"month","in":"query","schema":{"type":"string","pattern":"^[0-9]{4}-[0-9]{2}$","example":"2026-04"},"description":"When set, returns posts scheduled in that calendar month, plus posts published immediately (no `scheduled_at`) in that month. Boundaries are widened by 14 hours each side so local-time edge days aren't dropped."},{"name":"status","in":"query","schema":{"type":"string","enum":["scheduled","published","draft","failed","all"]},"description":"Filter by post status; omit or `all` for no status filter."},{"name":"limit","in":"query","schema":{"type":"integer","minimum":1,"maximum":50},"description":"Max posts to return, clamped to 1-50 (a non-numeric value means 20). If omitted: 50, or the whole month (up to 999 posts) when `month` is set. `hasMore` and `nextOffset` in the response tell you whether another page exists."},{"name":"offset","in":"query","schema":{"type":"integer","minimum":0,"default":0},"description":"Number of posts to skip for pagination, paired with `limit`. Non-numeric or negative values are treated as 0."},{"name":"order","in":"query","schema":{"type":"string","enum":["published_at_desc","created_at_desc","updated_at_desc"]},"description":"Optional sort order. `published_at_desc` returns the most recently published posts first (unpublished posts last); `created_at_desc` the most recently created first; `updated_at_desc` the most recently changed first (useful for catching failures on older posts). Omit for the default order (soonest scheduled first)."},{"name":"include","in":"query","schema":{"type":"string","enum":["updated_at"]},"description":"Optional. `updated_at` adds each post's `updated_at` to the response: the time the post last changed, which moves when it is published or fails. Pair it with `order=updated_at_desc` when polling for failures."}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PostsListResponse"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many requests"},"500":{"description":"Server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"tags":["Posts"],"summary":"Create post — schedule or publish now","description":"If `draft` is `true`, the post is stored with status `draft` and nothing is scheduled or published. Otherwise, if `scheduled_at` is set, variants are stored as scheduled and returned without publishing. If neither is set, the server attempts immediate publish to each platform in `captions`.","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PostCreate"},"examples":{"publishNow":{"value":{"prompt":"Shipped v2 today","captions":{"twitter":"Shipped v2 today 🚀","linkedin":"Today we shipped v2 — here is what changed…"}}},"schedule":{"value":{"captions":{"instagram":"Tomorrow: live AMA"},"media_urls":["https://media.fuxux.com/your-upload.jpg"],"scheduled_at":"2026-12-01T15:00:00.000Z"}},"draft":{"value":{"prompt":"Ideas for next week","captions":{"twitter":"Draft: thinking about..."},"draft":true}}}}}},"responses":{"200":{"description":"Draft saved, scheduled post, or publish outcome","content":{"application/json":{"schema":{"oneOf":[{"type":"object","properties":{"post":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"status":{"type":"string","const":"draft"}},"required":["id","status"]}},"required":["post"]},{"type":"object","properties":{"post":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"status":{"type":"string","const":"scheduled"},"scheduled_at":{"type":"string","format":"date-time"}},"required":["id","status","scheduled_at"]}},"required":["post"]},{"type":"object","properties":{"post":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"status":{"type":"string","description":"`publishing` means a platform is still processing: read `GET /api/posts/{id}` after a minute. `published` means at least one platform succeeded; check `results` for the rest.","enum":["published","publishing","failed"]}},"required":["id","status"]},"results":{"type":"array","items":{"$ref":"#/components/schemas/PublishResult"}}},"required":["post","results"]}]}}}},"400":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Refused; nothing was created. See `code`: `post_quota_reached` (the plan's post allowance for this month would be exceeded), `x_allowance` (the month's X allowance is used up) or `reddit_web_only` (Reddit can only be posted to from the web app).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many requests"},"500":{"description":"Server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/posts/{id}":{"get":{"tags":["Posts"],"summary":"Get a post","description":"One post with its per-platform variants, including drafts (which the month-scoped list can miss). Variant ids are what PATCH `variant_updates` uses.","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"post":{"$ref":"#/components/schemas/PostRow"}},"required":["post"]}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Post not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"patch":{"tags":["Posts"],"summary":"Update a post","description":"Edit a draft or scheduled post before it publishes. Provide at least one of `scheduled_at` (scheduled posts only, must be in the future), `variant_updates`, or `media_urls` (replaces media).","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"scheduled_at":{"type":"string","format":"date-time"},"variant_updates":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"content":{"type":"string"}},"required":["id","content"]}},"media_urls":{"type":"array","maxItems":4,"items":{"type":"string","format":"uri"}}}}}}},"responses":{"200":{"description":"Updated","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","const":true},"id":{"type":"string"},"post":{"$ref":"#/components/schemas/PostRow"}},"required":["success","id"]}}}},"400":{"description":"Invalid request or post not editable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Post not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"The post started publishing during the request. A post that is already publishing or published is refused with 400.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"delete":{"tags":["Posts"],"summary":"Delete a post","description":"Deletes the post from Fuxux. A draft or scheduled post will never publish. For a published post this removes only Fuxux's record, not the post on the social platform. Returns 409 while the post is publishing.","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Deleted","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","const":true}},"required":["success"]}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/accounts":{"get":{"tags":["Accounts"],"summary":"List connected social accounts","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"accounts":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"platform":{"type":"string"},"platform_username":{"type":"string","nullable":true},"platform_avatar_url":{"type":"string","nullable":true},"token_expires_at":{"type":"string","format":"date-time","nullable":true},"created_at":{"type":"string","format":"date-time"},"status":{"type":"string","enum":["connected","expired"],"description":"\"expired\" means the account must be reconnected in the Fuxux dashboard before it can publish again."}}}}},"required":["accounts"]}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many requests"},"500":{"description":"Server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/ai/generate":{"post":{"tags":["AI"],"summary":"Generate platform captions from a prompt","description":"Enforces the plan's AI limits: a monthly number on Free, and fair-use limits per day and per month on paid plans. When a limit is reached the response is **429** with `code: \"ai_limit\"` and an `ai_limit` object (`reason`, `used`, `limit`, `resetsAt`). Valid platform ids: `twitter`, `instagram`, `linkedin`, `facebook`, `tiktok`, `youtube`, `bluesky`, `threads`, `pinterest`, `discord`, `twitch`, `kick`, `google-business`. Reddit is not available here. Ids that are not recognised are ignored; repeated ids count once.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["prompt","platforms"],"properties":{"prompt":{"type":"string","maxLength":500},"platforms":{"type":"array","items":{"type":"string"},"maxItems":13,"description":"Platforms to generate in one call"},"regenerate":{"type":"string","description":"If set, only this platform id is regenerated; `platforms` is ignored for selection."}}}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AiCaptionsResponse"}}}},"400":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"User rate limit or monthly AI cap","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Misconfiguration","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"502":{"description":"Upstream model error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/analytics":{"get":{"tags":["Analytics"],"summary":"Publishing analytics","parameters":[{"name":"days","in":"query","schema":{"type":"integer","enum":[7,14,30,90],"default":30},"description":"Sliding window length for chart and breakdown; invalid values default to 30."}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AnalyticsResponse"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/media/upload":{"post":{"tags":["Media"],"summary":"Upload media for posts","description":"Multipart form field `file`. Fuxux allows up to **50 MB**, but the hosting platform hard-caps any single request body at **~4.5 MB** — larger files fail here regardless of the 50 MB limit; host them yourself and pass a public HTTPS URL in `media_urls` instead. Allowed types: JPEG, PNG, GIF, WebP, MP4, MOV, WebM.","requestBody":{"required":true,"content":{"multipart/form-data":{"schema":{"type":"object","required":["file"],"properties":{"file":{"type":"string","format":"binary"}}}}}},"responses":{"200":{"description":"Public URL","content":{"application/json":{"schema":{"type":"object","required":["url","path"],"properties":{"url":{"type":"string","format":"uri"},"path":{"type":"string"}}}}}},"400":{"description":"Missing file or disallowed type/size","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Upload failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/settings/profile":{"get":{"tags":["Settings"],"summary":"Get current profile","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProfileResponse"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/tools/youtube-tags":{"post":{"tags":["Tools"],"summary":"Suggest YouTube tags","description":"No authentication. Rate limited per IP (per hour).","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["topic"],"properties":{"topic":{"type":"string","minLength":3,"maxLength":500,"description":"Video topic / title / description"}}}}}},"responses":{"200":{"description":"Tag list","content":{"application/json":{"schema":{"type":"object","properties":{"tags":{"type":"array","items":{"type":"string"},"minItems":8,"maxItems":20}},"required":["tags"]}}}},"400":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many requests"},"502":{"description":"Generation or parse error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"Service unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/tools/tiktok-caption":{"post":{"tags":["Tools"],"summary":"Suggest TikTok captions","description":"No authentication. Rate limited per IP (per hour). Optional `tone`: `playful`, `bold`, or `calm`.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["topic"],"properties":{"topic":{"type":"string","minLength":3,"maxLength":400},"tone":{"type":"string","enum":["playful","bold","calm"],"default":"playful"}}}}}},"responses":{"200":{"description":"Caption variants","content":{"application/json":{"schema":{"type":"object","properties":{"captions":{"type":"array","items":{"type":"string"},"minItems":3,"maxItems":5}},"required":["captions"]}}}},"400":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many requests"},"502":{"description":"Generation or parse error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"Service unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}}}}