# Mumro API — complete reference (automation & AI agents) This is the comprehensive, automation-oriented reference for the Mumro public HTTP API. It is written so that a developer **or an AI agent** can drive the product end-to-end: connect channels, generate and publish content (posts, reels, shorts, stories, films), run the article autopilot and publish to a website, manage comments, and read analytics and usage. - **Base URL:** `https://app.mumro.io` - **Machine-readable contract (authoritative):** `https://app.mumro.io/openapi.json` (OpenAPI 3) and interactive `https://app.mumro.io/docs` (Swagger UI). When this page and the live schema disagree, the live schema wins. - **Media format:** JSON request/response (`Content-Type: application/json; charset=utf-8`), except file upload endpoints that use `multipart/form-data`. > Scope: this page documents the **tenant-facing** API (everything a workspace > can do with an API key). Platform-operator endpoints under `/api/admin/*` and > browser-only OAuth callbacks (`/api//oauth/*`) are intentionally > excluded — an API key cannot call them. ## 1. Authentication Two ways to authenticate; both resolve to a single workspace (organization). ### Public API workspace key (recommended for HTTP automation / agents) 1. In the app open **Settings → API keys**, choose **Public API (creator role)** and create a key. The plaintext `smm_…` token is shown **once** — store it securely. 2. Send it on every request: ``` Authorization: Bearer smm_xxxxxxxxxxxxxxxxxxxxxxxx ``` The key is **workspace-scoped**: it already identifies the organization, so you do **not** send a workspace header with it. A key with the **Codex / MCP** audience is rejected by `/api/*`; use the separate [Codex private-beta guide](codex-private-beta.md) for that surface. Keys can be created and revoked via `GET/POST/DELETE /api/api-keys`. If a key is pasted into a chat, ticket, log or other untrusted location, revoke it after the test and generate a replacement. Do not copy API keys into test reports. ### Session access token (for human/SPA flows) Obtain a JWT via `POST /auth/password/login` (or Google OAuth), then send `Authorization: Bearer `. A session token can belong to several workspaces, so select the active one with a header: ``` X-Workspace-Id: ``` (The legacy alias `X-Organization-Id` is also accepted.) ### What auth controls - **401** `missing_bearer_token` / `invalid_api_key` — no/invalid credential. - **403** `module_disabled` — the workspace plan does not include the feature module (e.g. `reels_generator`, `shorts`, `website_articles`). Ask the workspace owner to enable the plan/module. - **403** role errors — write operations require the `member` role or higher; reads allow `viewer`. - **402** `insufficient_credits` (and related) — a metered AI/render action has no balance. See **Usage & billing**. ## 2. Conventions - **Idempotency:** mutating endpoints that create billable or external effects accept an `Idempotency-Key` header (UUIDv4 or 16–128 printable ASCII). A replay with the **same** key + same payload returns the original result; reusing a key with a **different** payload returns `422` `idempotency_key_conflict`. Always set it on publish/create/generate calls and on retries. See [Idempotency](idempotency.md). - **Errors:** every error is a JSON envelope `{ "error_code": "...", "error_message": "...", "request_id": "...", "details": {…} }`. Branch on `error_code`, never on message text. See [Errors](errors.md). - **Pagination:** list endpoints take `limit` and `offset` (and often filters like `status`, `platform[]`, `account_id`); responses include a `total`. - **Rate limits:** `429` with a `Retry-After` header on throttling. See [Rate limits](rate-limits.md). - **Async jobs:** generation/rendering is asynchronous. Create the job, then poll its `GET …/jobs/{id}` until a terminal `status` (`completed`/`failed`), reading `progress` and (where present) a live `research` feed. - **Workspace isolation:** every object is scoped to the workspace of your credential. Cross-workspace access returns `404`. ## 3. How an AI agent should drive the API 1. Authenticate with a **Public API** workspace key (section 1). This section describes an agent calling `/api/*`, not the bounded MCP plugin. 2. Discover live capabilities/balance before acting: - `GET /api/usage/capabilities` and `GET /api/usage/balance` — what is enabled and whether there is credit. - `GET /api/publications/capabilities` — platform × content-type matrix, caption/media limits, platform options. - `GET /api/accounts` — connected channels and their `account_id`s. 3. Pull the exact request schema for any endpoint from `https://app.mumro.io/openapi.json` (resolve `$ref`s under `components.schemas`). Treat it as the source of truth for fields. 4. For content with media, upload first (section "Media & uploads"), then reference the returned numeric `media_asset_id`. 5. Use `Idempotency-Key` on every create/publish/generate and on retries. 6. Poll async jobs to completion; fetch outputs via their video/clip endpoints. The rest of this page is the endpoint catalog. Columns: **role** (minimum role for session tokens; a Public API key follows its creator's current workspace role), **idem** (honors `Idempotency-Key`). ## 4. Channels (social accounts) A "channel" is a connected provider profile/page (`account_id`). Currently a workspace allows at most one Instagram surface and one Facebook Page. Connect a channel (agent starts the flow, a human finishes consent in a browser): `GET /api/oauth/{platform}/start` returns the provider authorization URL; the provider redirects back to the server callback. Supported providers: `facebook`, `instagram` (via Facebook), `youtube`, `tiktok`, `twitter`/`x`, `linkedin`. | Method | Path | Role | Purpose | |--------|------|------|---------| | GET | `/api/accounts` | viewer | List connected channels (+ `account_id`). | | GET | `/api/accounts/{id}` | viewer | Channel detail. | | POST | `/api/accounts/sync` · `/api/accounts/{id}/sync` | member | Refresh metadata/metrics from the provider. | | POST | `/api/accounts/{id}/sync-published` | member | Reconcile already-published videos. | | POST | `/api/accounts/reorder` | member | Reorder channels. | | PUT/DELETE | `/api/accounts/{id}` | member | Update / remove a channel. | | GET/PUT | `/api/accounts/{id}/schedule` | member | Read/replace the channel posting schedule. | | GET/PUT | `/api/accounts/{id}/publishing-defaults` | member | Default caption/options for the channel. | | GET/PUT | `/api/accounts/{id}/comment-tone` | member | AI reply tone for the channel. | | GET | `/api/accounts/{id}/videos` | viewer | Videos attached to the channel. | | GET/POST/PATCH/DELETE | `/api/accounts/{id}/members…` | member/owner | Per-channel access members. | (Comment and video sub-routes under `/api/accounts/{id}/…` are also covered in **Comments** and **Media** below.) ## 5. Media & uploads Upload once, get a numeric `media_asset_id`, then reference it from any publication. Reuse the same asset for IG+FB rather than uploading twice. | Method | Path | Role | Purpose | |--------|------|------|---------| | POST | `/api/uploads/init` | member | Start a presigned upload (single PUT or multipart). | | POST | `/api/uploads/{public_id}/complete` | member | Finalize a presigned upload → asset `ready`. | | POST | `/api/uploads/{public_id}/abort` | member | Cancel an in-progress upload. | | POST | `/api/uploads/direct` | member | Server-streamed `multipart/form-data` upload (≤100 MB); images return expiring `download_url` and stable `public_url`. | | POST | `/api/uploads/from-url` | member | Server-side fetch+store from an allowlisted URL. | | GET | `/api/videos/{id}` · `/api/videos/{id}/preview` | viewer | Video record / preview metadata. | | GET | `/api/videos/{id}/preview-file` | viewer | Stream the preview file. | | POST | `/api/videos/{id}/publish` · `/retry` · `/copy` · `/archive` | member | Publish-now / retry / duplicate / archive a video. | | PUT/DELETE | `/api/videos/{id}` | member | Edit / delete a video. | For SRT captions, upload the `.srt` as a `subtitle` asset and pass its id as `subtitle_asset_id` (Facebook Reels only). See [Endpoints](endpoints.md) media rules. ## 6. Publications & publishing queue (core) The unified way to publish to one or many channels. One publication fans out into one `PublishJob` per target; a target can fail without retrying the others. | Method | Path | Role | Idem | Purpose | |--------|------|------|------|---------| | POST | `/api/publications` | member | ✅ | Create a unified publication (body `PublicationCreate`). | | GET | `/api/publications/capabilities` | viewer | — | Platform × type matrix, limits, platform options. | | GET | `/api/publications/preview-schedule` | viewer | — | Next free slot for `account_id`+`content_type`; use `is_trial=true` for an Instagram Trial Reel. | | GET | `/api/posts/schedule-preview` | viewer | — | Schedule preview for posts. | | GET | `/api/publish/status/{idempotency_key}` | viewer | — | Look up a publication result by its idempotency key. | | GET | `/api/publish-jobs` | viewer | — | Queue + history (filters: `status[]`, `platform[]`, `account_id[]`, `scheduled_after/before`, `sort`, `limit`, `offset`). | | GET | `/api/publish-jobs/{id}` | viewer | — | Single job (`PublishJobRead`, incl. `schedule_source`). | | POST | `/api/publish-jobs/{id}/cancel` · `/retry` · `/move` | member | — | Cancel / requeue / reorder a job. | | POST | `/api/publish-jobs/{id}/retry-first-comment` | member | — | Queue retry of only the first-comment operation after a successful post (`202`). | | PATCH | `/api/publish-jobs/{id}` | member | — | Edit caption/title or switch auto/manual schedule. | | POST | `/api/publish-jobs/bulk/cancel` · `/bulk/retry` | member | — | Cancel/retry up to 500 jobs (`{ ids: number[] }`). | The full `PublicationCreate` schema, `schedule_mode` contract, platform-native options and `PublicationCreateResponse` are documented in [Endpoints](endpoints.md) — read that page for publication bodies. For `post`, `story` and `reel`, the `job_id` returned by `POST /api/publications` is the same public queue id returned as `id` by `GET /api/publish-jobs` and accepted by every `/api/publish-jobs/{id}` route. The retired `/api/films/*` compatibility surface is not part of the public API. Use the unified publication response and the type-specific job endpoints advertised by the live OpenAPI. ## 7. Reels generator Text/voiceover → vertical reel, rendered asynchronously by the reels tool. Module `reels_generator`; rendering is metered (`reel.render`). Full request schema: `ReelJobCreate` in the OpenAPI. | Method | Path | Role | Purpose | |--------|------|------|---------| | POST | `/api/reels/jobs` | member | Create a render job (body `ReelJobCreate`). Returns a job to poll. | | GET | `/api/reels/jobs` · `/jobs/{id}` | viewer | List / poll jobs (`status`, `progress`, live `research`). | | GET | `/api/reels/jobs/{id}/video` | viewer | Download/stream the finished MP4. | | POST | `/api/reels/jobs/{id}/rerender` · `/cancel` | member | Re-render with tweaks / cancel. | | POST | `/api/reels/script` | member | Generate just the script/narration (no render). | | GET | `/api/reels/styles` | viewer | Available visual styles (incl. designed-text: terminal, breaking news, kinetic, neon). | | GET | `/api/reels/voices` · `/elevenlabs-models` · `/image-models` · `/ai-providers` | viewer | Catalogs for voice/model/image/text-AI selection. | | GET/PUT | `/api/reels/settings` | member | Generator settings (BYOK keys: text-AI, web-search, image-AI). | | POST | `/api/reels/settings/test-elevenlabs` | member | Validate the ElevenLabs key. | | GET/POST/PUT/DELETE | `/api/reels/schedules…` | member | Recurring "reels from research" schedules (every N days). | | POST | `/api/reels/schedules/{id}/run-now` | member | Generate immediately from a schedule. | Minimal research-based reel (web mode): ```json POST /api/reels/jobs { "name": "AI news daily", "platform": "instagram", "language": "pl", "style_template_id": "breaking_news", "research": { "brief": "najważniejsze wydarzenia ze świata AI", "source": "web", "source_profile_id": 42, "max_items": 5, "recency_window_days": 7 }, "voice": {}, "captions": { "enabled": true } } ``` Reel built from your own published articles (portal mode — no web search, and it advertises the source article on-screen): set `"research": { "source": "portal", "brief": "…", "max_items": 5 }`. The reel pulls `reel_ready` data from the workspace's website articles. `source_profile_id` is optional and references a profile configured in the Mumro UI. An RSS-backed profile can work without a web-search key and is resolved with quality gates before the render job is accepted. Profile management is not part of the stable Public API v1. ## 8. Shorts generator Long video (YouTube link or https file URL) → captioned vertical clips. Module `shorts`; metered (`shorts.render`). Full schema: the shorts `…/jobs` create body in the OpenAPI. | Method | Path | Role | Purpose | |--------|------|------|---------| | POST | `/api/shorts/jobs` | member | Create a cut job from a source URL. Returns a job to poll. | | GET | `/api/shorts/jobs` · `/jobs/{id}` | viewer | List / poll jobs and produced clips. | | GET | `/api/shorts/jobs/{id}/clips/{clip_id}/video` | viewer | Download/stream a produced clip. | | POST | `/api/shorts/jobs/{id}/descriptions` | member | AI-generate titles/descriptions for the clips. | | POST | `/api/shorts/jobs/{id}/publish` | member | Publish/schedule selected clips to a channel (→ normal publish pipeline). | | POST | `/api/shorts/jobs/{id}/cancel` | member | Cancel a running job. | ```json POST /api/shorts/jobs { "source_url": "https://www.youtube.com/watch?v=…", "language": "pl" } ``` (Confirm the exact field names/options against the live `…/shorts/jobs` request schema in the OpenAPI before sending.) ## 9. Stories | Method | Path | Role | Purpose | |--------|------|------|---------| | POST | `/api/stories` · `/stories/bulk` | member | Create one / many stories. | | POST | `/api/stories/upload-and-publish` | member | Upload media and publish a story in one call. | | GET | `/api/stories` · `/stories/{id}` | viewer | List / get story status. | | POST | `/api/stories/{id}/retry` | member | Retry a failed story. | | DELETE | `/api/stories/{id}` | member | Cancel a story. | ## 10. Long video Create long-video publications through `POST /api/publications` with `publication_type=film`. The former `/api/films/*`, local upload and raw file serving routes were retired because they predated workspace authorization. ## 11. Post generator (text + image) | Method | Path | Role | Purpose | |--------|------|------|---------| | POST | `/api/post-generator/generate` | member | AI-generate post copy variants. | | POST | `/api/post-generator/generate-image` | member | AI-generate an image for a post. | ## 12. Article autopilot (research → article) Generates a full article from a brief (optionally grounded in an RSS/domain profile and a BYOK web-search key), saves it as a website article and can push it to the connected CMS. Module `website_articles`; primary generation is metered as `article.generate` and every optional article-only translation as `article.translate`. Config schema: `ArticleAutopilotConfig`. | Method | Path | Role | Purpose | |--------|------|------|---------| | GET | `/api/article-autopilot/research-readiness` | viewer | Secret-free status of web search and the article-writing model (`ai_configured`, `ai_provider`, `ai_source`). | | POST | `/api/article-autopilot/generate` | member | Synchronous one-off run; returns the result. | | POST | `/api/article-autopilot/generate-async` | member | Async run (202 + job) with a live research feed. | | GET | `/api/article-autopilot/jobs` · `/jobs/{id}` | viewer | List / poll async jobs. | | GET/POST/PUT/DELETE | `/api/article-autopilot/schedules…` | member | Recurring "articles from research" schedules (every N days). | | POST | `/api/article-autopilot/schedules/{id}/run-now` | member | Generate immediately from a schedule. | ```json POST /api/article-autopilot/generate-async { "site_id": 1, "topic": "najważniejsze wiadomości ze świata AI z ostatnich dni", "language": "en", "article_translation_languages": ["pl"], "tone": "dziennikarski", "length": "medium", "research": { "enabled": true, "source_profile_id": 42, "max_sources": 5, "recency_window_days": 7 }, "action": "publish" } ``` `action`: `local_draft` (Mumro only), `cms_draft` (remote draft), `publish` (live now) or `schedule` (Mumro-managed scheduled publish). The article also stores a `reel_ready` block + HTML `summary_cards` so a portal-mode reel can promote it. With `research.enabled=true`, generation is fail-closed. Missing RSS/search configuration returns `article_web_search_not_configured`; zero usable results returns `article_research_no_sources`; unmet profile gates return `article_research_quality_failed`. None creates or publishes an article. Search selection is balanced across the main topic, explicit `research.queries` and the first editorial priority in `extra_instructions`. The live feed may expose `published`, `relevance_score` and `query` for each source. A mandatory post-writing source review removes unsupported news claims; `article_grounding_review_failed` stops the run before save/publish when that review cannot be completed. Set `research.enabled=false` only for deliberate evergreen content. ## 13. Website articles & CMS publishing Lightweight CMS layer: create/edit article drafts and publish them to the connected website (WordPress REST or a custom endpoint). Publishing is queued and retried Mumro-side. Module `website_articles`. | Method | Path | Role | Idem | Purpose | |--------|------|------|------|---------| | GET | `/api/website-articles` | viewer | — | List drafts/history (`site_id`, `status`, `q`, `limit`, `offset`). | | POST | `/api/website-articles` | member | ✅ | Create a draft; supports tenant-scoped `featured_image_asset_id`, alt and caption (idempotent replay returns the existing article). | | GET/PUT/DELETE | `/api/website-articles/{id}` | viewer/member | — | Get / update / archive. | | POST | `/api/website-articles/{id}/publish` | member | — | Send current version to the CMS (`remote_status` = `publish`/`draft`/`future`); blocks unresolved `X-Amz-Expires` image URLs. | | POST | `/api/website-articles/{id}/schedule` | member | — | Queue for Mumro-managed publishing at `scheduled_at` (retry + idempotency). | | POST | `/api/website-articles/{id}/reel` | member | ✅ | Turn the article into a vertical text reel (module `reels_generator`, metered `reel.render`). Returns a reel job to poll. | | POST | `/api/website-articles/{id}/social-post` | member | ✅ | Save a promotion draft or enqueue it via the publishing pipeline (module `social_publishing`). | | GET | `/api/website-analytics/sites` | viewer | — | JSON array of connected sites + their `site_id` and CMS status. | | POST/PUT/DELETE | `/api/website-analytics/sites…` | member | — | Add / update / remove a site. | | PUT | `/api/website-analytics/sites/{id}/cms` | member | — | Configure WordPress or custom-CMS credentials. | | POST | `/api/website-analytics/sites/{id}/cms/test` | member | — | Test the saved CMS connection. | `social-post` accepts `link_placement=smart` and `image_mode=smart` (the recommended defaults) plus an optional `target_overrides` object keyed by selected account id. Smart routing sends the article carousel to Instagram and Facebook when available, one image to LinkedIn, and one image to X. An explicit X carousel is capped at four images. The response and the article's `metadata_json.social_last_publication` expose one independent job/status per target; a partial failure never republishes targets that already succeeded. ### Testimonial wall (curate → embeddable public wall) Curate positive feedback (manually or imported from a social comment) and expose the **approved** ones through a public, embeddable wall. The public endpoint and embed are identified by a non-secret wall `public_key` (rotatable), and only ever return display fields (author name, text, rating, source) of approved testimonials on an enabled wall. | Method | Path | Role | Purpose | |--------|------|------|---------| | GET/PUT | `/api/testimonials/wall` | viewer/member | Read / update wall config (`title`, `enabled`, `accent_color`, `rotate_key`). | | GET | `/api/testimonials` | viewer | List all testimonials (approved or not). | | POST | `/api/testimonials` | member | Create a testimonial. Body: `{ author_name, text, author_handle?, source?, source_url?, rating?, approved?, sort_order? }`. | | POST | `/api/testimonials/from-comment` | member | Create from an owned social comment. Body: `{ comment_id, approved? }`. | | PUT/DELETE | `/api/testimonials/{id}` | member | Update / soft-delete. | | GET | `/api/testimonials/public/{public_key}` | public | **Unauthenticated.** Approved-only wall payload `{ title, accent_color, items[] }`. Disabled/unknown wall → 404. | | GET | `/api/testimonials/widget.js?w={public_key}` | public | Embed script; renders the wall into `#mumro-testimonials`. | Embed: ```html
``` ### Link-in-bio / mini-landing A hosted, single mini-landing per workspace (title, bio, avatar, accent + link buttons) published at a public URL — ideal for a social-profile bio link. Link URLs are restricted to http(s) and every value is escaped on render. The public page/JSON is identified by a non-secret, rotatable `public_key`. | Method | Path | Role | Purpose | |--------|------|------|---------| | GET/PUT | `/api/link-in-bio/page` | viewer/member | Read / update page config (`title`, `bio`, `avatar_url`, `accent_color`, `enabled`, `rotate_key`). | | GET/POST | `/api/link-in-bio/links` | viewer/member | List / add link buttons (`{ label, url, sort_order?, enabled? }`). | | PUT/DELETE | `/api/link-in-bio/links/{id}` | member | Update / soft-delete a link. | | GET | `/api/link-in-bio/public/{public_key}` | public | **Unauthenticated** JSON: `{ title, bio, avatar_url, accent_color, links[] }` (enabled links only; disabled/unknown → 404). | | GET | `/api/link-in-bio/p/{public_key}` | public | Server-rendered hosted page — the bio link itself. | | GET | `/api/website-analytics/dashboard` | viewer | — | Website analytics summary. | Full create/update/publish schemas, WordPress behavior and the **custom-CMS receiving-endpoint contract** (what to add to your own site): see [Website articles API and CMS integration](website-articles.md). The `/api/website-articles/{id}/reel` endpoint turns an article into a vertical text reel: it uses the article's `reel_ready` metadata as the narration when present (otherwise drafts a script from the article), renders one of the designed-text styles, and advertises the article's live URL on screen. It is a thin bridge over the reels generator, so the same `reels_generator` module and `reel.render` metering apply — poll the returned job like any other reel job. For connecting a website to **analytics** (the tracking snippet, how Mumro identifies and validates traffic, privacy and CSP), see [Website analytics integration](website-analytics.md). ## 14. Comments (inbox + AI replies) | Method | Path | Role | Purpose | |--------|------|------|---------| | GET | `/api/comments` | viewer | Unified workspace inbox. | | GET | `/api/comments/platform-counts` · `/unread-count` | viewer | Counters. | | PATCH | `/api/comments/{id}` | member | Update triage status. | | POST | `/api/comments/{id}/generate` · `/reply` | member | AI-draft or save/send a reply. | | POST | `/api/comments/bulk/classify` · `/bulk/generate` · `/bulk/send` · `/bulk/handle` | member | Bulk classify / draft / send / handle. | | POST | `/api/accounts/{id}/comments/fetch` | member | Fetch comments for one channel, scoped to the active workspace. | `bulk/generate` and `bulk/send` require an explicit, non-empty list of 1–200 `comment_ids`. An empty list never means “all comments”; this prevents a stale or filtered UI from generating or publishing replies outside the user's visible selection. Every ID is revalidated against the active workspace, and bulk send accepts only comments with a non-empty `draft` or `edited` reply. Classification may still use an empty list to select its bounded workspace/account scope. Comment automation policy & plans: | Method | Path | Role | Purpose | |--------|------|------|---------| | GET/PUT | `/api/comment-policy` | member | Workspace auto-reply policy. | | GET/POST/PUT/DELETE | `/api/comment-plans…` | member | Scheduled author-comment plans. | | POST | `/api/comment-plans/{id}/run` | member | Run a plan now. | | GET | `/api/comment-plans/{id}/preview-targets` | viewer | Preview which posts a plan targets. | Reply templates (reusable canned replies for the inbox): | Method | Path | Role | Purpose | |--------|------|------|---------| | GET | `/api/reply-templates` | viewer | List the workspace's reply templates, ordered by `sort_order` then id. | | POST | `/api/reply-templates` | member | Create a template. Body: `{ title, body, category?, sort_order? }`. | | PUT | `/api/reply-templates/{id}` | member | Update a template (partial; only provided fields change). | | DELETE | `/api/reply-templates/{id}` | member | Soft-delete a template. | Unified inbox (one merged stream of social comments + chatbot conversations): | Method | Path | Role | Purpose | |--------|------|------|---------| | GET | `/api/inbox` | viewer | Newest-first merged inbox. Items: `{ type (comment\|chatbot), id, source, account_id, chatbot_id, author, preview, status, category, needs_human, has_reply, url, created_at }`. Query: `account_id` (comments for one channel; excludes chatbot), `source` (`comment`/`chatbot`), `only_unhandled`, `limit` (1–200), `offset`. Comments honour the per-account access allow-list; chatbot conversations are workspace-level. | ## 15. Analytics & dashboard | Method | Path | Role | Purpose | |--------|------|------|---------| | GET | `/api/analytics/overview` · `/per-account` · `/timeseries` · `/top-posts` | viewer | Core analytics. | | GET | `/api/analytics/follower-growth` · `/best-hours` · `/hashtags` · `/content-type` · `/velocity` · `/comments` | viewer | Detailed breakdowns. | | GET | `/api/analytics/posting-suggestions` | viewer | Ranked best weekday/hour slots to publish, derived from the workspace's own post history. Query: `top_n` (1–24, default 5), `min_posts` (default 1), `language` (`pl`/`en`), optional `account_id`. | | GET | `/api/analytics/channel-engagement` | viewer | Per-channel engagement summary for the window: posts, likes/views, comments/shares/saves/reach/impressions (from the latest snapshot per post), current followers, `interactions`, `avg_engagement` and an `engagement_rate` (interactions ÷ reach, falling back to ÷ followers). Returns `{ channels, totals, window_days }`. Query: `days` (1–365, default 30), optional `account_id`. | | GET | `/api/analytics/report` | viewer | Single **period digest** composing the above: `{ totals, deltas, channels, engagement_totals, top_posts, best_times, top_hashtags, follower_net_change, highlights }` plus `period_start`/`period_end`. `highlights` is a deterministic plain-language summary (Polish). Query: `days` (1–365, default 30), optional `account_id`. No AI cost. | | GET | `/api/analytics/hashtag-suggestions` | viewer | Hashtags ranked by historical engagement, split into `proven` / `experimental` by confidence. Query: `limit` (1–100, default 15), `days` (1–365, default 90), `min_posts`, optional `account_id`. | | GET | `/api/analytics/recycling-candidates` | viewer | Best-performing **older** posts worth republishing. Returns posts whose age is inside `[days_min, days_max]`, ranked by `metric`. Query: `metric` (`likes`/`views`), `limit` (1–50, default 10), `days_min` (default 30), `days_max` (default 365), optional `account_id`. Each item includes `age_days` and `media_asset_id` for re-publishing. | | GET | `/api/dashboard/overview` · `/activity` · `/upcoming` · `/attention` · `/suggestions` | viewer | Dashboard widgets. | | GET/POST | `/api/dashboard/ai-audit` | viewer/member | Read / trigger the AI dashboard audit. | | GET | `/api/dashboard/content-strategy` | viewer | Read the latest, approved and historical strategy versions for the workspace or one accessible channel. | | GET | `/api/dashboard/content-strategy/setup-guide` | viewer | Get the localized first-setup or refresh interview. Query: optional `account_id`, `refresh` and `lang` (`pl`/`en`). No state change. | | POST | `/api/dashboard/content-strategy` · `/api/dashboard/content-strategy/from-audit` | member | Save a new versioned strategy draft manually or from the latest ready dashboard audit. | | POST | `/api/dashboard/content-strategy/{id}/approve` | member / owner | Approve the intended version. An active `autonomous_schedule` delegation requires the workspace owner; controlled strategies remain `member+`. | | POST | `/api/dashboard/content-strategy/{id}/kill-switch` | member | Atomically approve a new stopped version with its kill switch active. | | GET | `/api/stats` | viewer | Global workspace stats. | ## 16. Usage, billing & plans | Method | Path | Role | Purpose | |--------|------|------|---------| | GET | `/api/usage/balance` | viewer | Current credit balance (incl. `batch_available_credits` from purchased packages). | | GET | `/api/usage/capabilities` | viewer | What the workspace can do (enabled modules/products). | | POST | `/api/usage/estimate` | viewer | Pre-flight cost preview: `{product_key, units?, byok?}` → predicted credits + `would_block`, without holding budget. | | GET | `/api/usage/summary` · `/usage/events` | viewer | Spend summary / metered event log. | | GET | `/api/plans` | public | Plan catalog. | | GET | `/api/billing` | member | Workspace billing state. | | POST | `/api/billing/checkout` | owner | Start a self-serve purchase (no charge until Stripe enabled). | Check `usage/balance` + `usage/capabilities` before any metered generate/render. ## 17. Workspaces, members & settings | Method | Path | Role | Purpose | |--------|------|------|---------| | GET | `/api/organizations` · `/organizations/current` · `/organizations/me` | viewer | List / active workspace / your memberships. | | POST/PUT/DELETE | `/api/organizations…` | owner | Create / update / delete a workspace. | | GET/POST/DELETE | `/api/organizations/current/invitations…` | owner | Manage member invitations. | | GET/PUT/DELETE | `/api/organizations/current/members…` | owner | Manage members & roles. | | POST | `/api/organizations/current/ownership/transfer` | owner | Transfer ownership. | | GET | `/api/organizations/current/entitlements` | viewer | Enabled feature modules for the workspace. | | GET/PUT/DELETE/POST | `/api/ai-settings…` | member | BYOK / managed AI configuration (`/ai-settings/test` validates a key). | | GET | `/api/ai-providers` · `/ai-models` · `/ai-tones` | viewer | AI catalogs. | | GET/PUT | `/api/user-preferences` · `/user-preferences/features` | self | Per-user UI preferences. | | GET/PUT | `/api/brand-voice` | viewer / member | Read (any role) / update (member+) the workspace **Brand Voice Profile** — shared tone, audience, key facts, keywords and do/don't examples. Injected as the **style base** into post generator, article autopilot, chatbot and comment AI (not reels/shorts). Comment-channel settings only tune reply tactics. PUT is partial (omitted fields unchanged). Free; no metering. See `BRAND-VOICE-DISCUSSION.md`. | | GET/PUT | `/api/campaigns/settings` | viewer / member | Read (any role) / update (member+) workspace **UTM/campaign** templates (`utm_source`, `utm_medium`, `utm_campaign`, `utm_content`, `utm_term`, `auto_tag_enabled`). | | POST | `/api/campaigns/tag-url` | viewer | Tag a URL with UTM parameters. Body: `{ url, utm_source?, utm_medium?, utm_campaign?, utm_content?, utm_term? }`. With no explicit params and `auto_tag_enabled=true`, applies the workspace defaults. Preserves existing non-UTM query params. | | GET | `/api/logs` | viewer | Workspace operational logs. | | GET/POST/DELETE | `/api/api-keys…` | member | Manage workspace API keys. | | GET | `/api/webhooks/event-types` | viewer | List the event types you can subscribe to. | | GET/POST | `/api/webhooks` | member | List / register outbound webhook endpoints. | | PUT/DELETE | `/api/webhooks/{id}` | member | Update / soft-delete an endpoint. | | POST | `/api/webhooks/{id}/test` | member | Send a signed `ping` to the endpoint and report delivery. | ### Outbound webhooks Register HTTPS receivers that Mumro POSTs **signed** event payloads to. Manage them under `/api/webhooks` (member+). The signing secret is write-only — it is accepted on create/update but never returned; responses expose `has_secret`. Create body: ```json { "url": "https://example.com/hooks/mumro", "secret": "whsec_...", // optional; signs deliveries "event_types": ["publication.succeeded", "publication.failed"], "active": true, "description": "Prod CRM" } ``` `event_types` is an allow-list; an **empty list means every event**. Current event types: `publication.succeeded`, `publication.failed`, `lead.captured`, `article.published`, `comment.received`, `reel.completed`, `ping` (`GET /api/webhooks/event-types` is the source of truth). **Delivery contract** (what your receiver gets): - `POST` with JSON body `{ "event": "", "organization_id": , "data": {…}, "ts": "" }`. - Header `X-Mumro-Event: `. - Header `X-Mumro-Signature: sha256=` when a secret is set — HMAC-SHA256 of the **raw request body** using your secret. Verify it before trusting the payload; reject on mismatch. - Delivery is best-effort with retry (worker-backed). Respond `2xx` to ack; non-2xx / timeout triggers retries. Targets must be publicly routable (SSRF-guarded — private/loopback hosts are rejected at registration). - `publication.succeeded` / `publication.failed` `data`: `{ job_id, job_type, status, video_id, film_id, account_id, error }`. - `lead.captured` `data`: `{ lead_id, chatbot_id, name, email, phone, conversation_id, page_url }`. - `article.published` `data`: `{ article_id, site_id, title, external_url, external_id }`. - `comment.received` `data`: `{ account_id, new_comments }` (batched per fetch — fires once when new comments arrive, not per comment). - `reel.completed` `data`: `{ reel_job_id, status, external_job_id }` (fires once on the transition to `completed`). Verify the signature (Python): ```python import hmac, hashlib expected = "sha256=" + hmac.new(secret.encode(), raw_body, hashlib.sha256).hexdigest() assert hmac.compare_digest(expected, request.headers["X-Mumro-Signature"]) ``` ## 18. Auth & health (no workspace credential needed) | Method | Path | Purpose | |--------|------|---------| | POST | `/auth/password/signup` · `/password/login` · `/refresh` · `/logout` | Email/password auth + token refresh. | | POST | `/auth/email/verify-request` · `/email/verify` | Email verification. | | POST | `/auth/password/forgot` · `/password/reset` | Password reset. | | GET | `/auth/google/start` · `/auth/google/callback` | Google OAuth (browser). | | GET | `/auth/capabilities` | Which auth methods are enabled. | | GET | `/health` · `/healthz` · `/readyz` | Liveness / readiness probes (public). | ## 19. Not in this reference - `/api/admin/*` — platform-operator only; rejected for API keys. - `/api//oauth/*` and `/auth/google/callback` — browser redirect flows, not directly agent-callable (start the flow via `/api/oauth/{platform}/start`). - Retired raw media routes (`/api/video-file/…`, `/api/film-file/…`) do not exist. Use the documented `…/video`, `…/preview-file` and clip endpoints. For exact field-level request/response schemas of any endpoint, resolve it in the live OpenAPI: `https://app.mumro.io/openapi.json`.