Skip to content

Public API ​

The Public API provides programmatic access for third-party integrations and automation. It is split into two groups:

  • Public API v1 — /public/v1/*, authenticated with an API key or OAuth token.
  • Compatibility public routes — /public/* outside /public/v1, kept for n8n/Zapier/Make compatibility where noted.

All v1 routes are org-scoped. Mutating routes support idempotency keys, and reads are rate-limited per org.

OpenAPI specification ​

The full machine-readable spec is committed at openapi.yml in the repository root, and a running instance serves the same document as browsable Swagger UI at /docs.

It is generated from the controllers — request and response schemas are inferred from the TypeScript types — so it tracks the code rather than drifting from it. CI fails the build if the committed file stops matching (.github/workflows/boot-guard.yml).

If you are contributing, do not hand-edit openapi.yml. Change the controllers or DTOs, then:

bash
pnpm run build:backend
pnpm run openapi:generate

Generating constructs the Nest application, so it needs DATABASE_URL, REDIS_URL, and JWT_SECRET in the environment. To improve a route's documentation, add @ApiOperation / @Api*Response decorators or JSDoc to the controller and regenerate.

Authentication ​

Pass the credential in the Authorization header as a raw string (no Bearer prefix):

http
Authorization: pm_live_xxxxxxxx...

Two credential types are accepted:

TypePrefixWhere to create
API keypm_live_Settings → Developers
OAuth access tokenpos_OAuth app authorization flow

API keys are per-user, per-org. The resolved org and user's role are attached to the request, so RBAC and billing gates still apply. OAuth tokens are constrained to the scopes the user approved (mcp:read, mcp:posts:write).

When billing is enabled (billingEnabled() — at least one payment provider configured), the org must have an active subscription; otherwise the request returns 401.

Rate limiting ​

Public v1 routes are rate-limited by API_LIMIT (default 600 requests/hour per org). Sensitive endpoints carry tighter @Throttle overrides, for example:

  • POST /oauth/token — 20/min
  • GET /public/v1/analytics/overview — 60/min
  • POST /public/t — 60/min

Pagination ​

GET /public/v1/posts returns at most 100 posts per call (also the default page size). The response is always the paged shape { posts, cursor }; use the cursor offset for paging. cursor is null on the last page.

ParamDescription
?limit=1–100; defaults to 100, hard-capped at 100.
?cursor=Opaque offset returned by the previous page.

Idempotency ​

Mutating v1 endpoints accept an optional Idempotency-Key header. Repeating the same key within 24 hours replays the first response instead of re-running the mutation. Keys are scoped per-org, so the same string from a different org is independent. Supported on:

  • POST /public/v1/posts
  • POST /public/v1/upload
  • POST /public/v1/upload-from-url
  • DELETE /public/v1/posts/:id
  • DELETE /public/v1/posts/group/:group
  • DELETE /public/v1/integrations/:id

If Redis is unavailable the header is ignored and the request proceeds normally. A concurrent duplicate returns 409.

Posts ​

Base: /public/v1

MethodPathPurpose
POST/public/v1/postsCreate a post
GET/public/v1/postsList posts
DELETE/public/v1/posts/:idDelete a post by post ID (resolves group)
DELETE/public/v1/posts/group/:groupDelete a post by group ID
PUT/public/v1/posts/:id/statusChange post status
PUT/public/v1/posts/:id/release-idUpdate external release ID
GET/public/v1/posts/:id/missingGet missing content to fill for a post
GET/public/v1/find-slot/:idFind next free posting time slot

Creating or scheduling a post validates that every referenced channel is connected and does not need reauthentication. Drafts are allowed on disabled channels so they can be reconnected before promotion.

POST /public/v1/posts is gated on api:create and posts_per_month:create billing policies.

Media ​

Base: /public/v1

MethodPathPurpose
POST/public/v1/uploadUpload a media file (multipart/form-data, field file)
POST/public/v1/upload-from-urlImport media from a URL
POST/public/v1/generate-videoGenerate AI video
GET/public/v1/generate-video/:idPoll an async video generation job
POST/public/v1/video/functionCall a provider tool, currently only loadVoices

POST /public/v1/generate-video accepts type (text-to-video, image-to-video, video-to-video) and provider params in customParams. Both the POST and the polling GET return the same shape: { id, status, artifactUrl, provider, error }.

  • If the artifact is returned synchronously, id is null, status is completed, and artifactUrl is the URL.
  • If a job is queued, id is the job id and status is pending; poll GET /public/v1/generate-video/:id with it. The poll normalizes any non-terminal provider status to pending.
  • Terminal statuses are completed (with artifactUrl) and failed (with error set to the failure reason).

Integrations ​

Base: /public/v1

MethodPathPurpose
GET/public/v1/integrationsList connected integrations
DELETE/public/v1/integrations/:idDelete a channel integration
GET/public/v1/integration-settings/:idGet integration rules, max length, settings schema, tools
GET/public/v1/social/:integrationGet OAuth authorization URL for a provider
POST/public/v1/integration-trigger/:idTrigger a dynamic provider tool
GET/public/v1/is-connectedCheck whether the org has any active integration

GET /public/v1/social/:integration requires an explicit provider version:

  • Pass providerId@version as the path param (e.g. x@v1).
  • Or pass ?version=v1 with a bare provider id.
  • A bare id without a version is rejected.
  • An unknown or retired version returns 404 or 410.

Analytics ​

Base: /public/v1/analytics

The full analytics surface is public — the same routes serve API-key/OAuth clients (throttled 60/min per route, no RBAC/entitlement gate) and the dashboard (session cookie, RBAC + entitlement enforced, x-csrf-token required on mutations). See Analytics API for parameters and response details.

MethodPathPurpose
GET/public/v1/analytics/overviewOrg overview with optional date range
GET/public/v1/analytics/channel/:integrationIdSingle channel analytics
GET/public/v1/analytics/channel/:integrationId/metric/:metricSpecific metric for a channel
GET/public/v1/analytics/campaign/:idCampaign-scoped analytics
GET/public/v1/analytics/postsPaginated post list with metrics
GET/public/v1/analytics/post/:postIdSingle post detail
GET/public/v1/analytics/metric/:metricMetric detail across date range
GET/public/v1/analytics/dayDay-level detail
GET/public/v1/analytics/best-timeBest-time-to-post heatmap
GET/public/v1/analytics/recommendationsAI-powered recommendations
GET/public/v1/analytics/healthData-health panel
POST/public/v1/analytics/refresh/:integrationIdOn-demand live channel refresh (6/hour)
GET/public/v1/analytics/content-insightsContent-attribute intelligence
POST/public/v1/analytics/narrateLLM-narrated summary
GET/public/v1/analytics/exportCSV/JSON export
GET/public/v1/analytics/anomaliesDetected anomalies for the org
POST/public/v1/analytics/anomalies/:id/dismissDismiss an anomaly
GET/POST/PUT/DELETE/public/v1/analytics/alert-rules*User-defined alert rules CRUD
GET/POST/DELETE/public/v1/analytics/shareOrg public share-link management
GET/POST/PUT/DELETE/public/v1/analytics/watchlist*Watched accounts CRUD + /:id/series
GET/public/v1/analytics/shortlinksShort-link aggregate stats
GET/public/v1/analytics/shortlinks/timeseriesShort-link click time series

GET /public/v1/analytics/overview validates from/to, requires to >= from, and caps the window at 400 days.

Notifications ​

Base: /public/v1

MethodPathPurpose
GET/public/v1/notificationsGet paginated org notifications

Compatibility public routes ​

Base: /public

MethodPathAuthPurpose
POST/public/tNoneTrack analytics/behaviour event
POST/public/modify-subscriptionJWTModify subscription billing
GET/public/streamNoneProxy-stream an external MP4 (SSRF-safe)
GET/public/integrations/listNoneAnonymous integrations catalogue (see below)

Integrations catalogue ​

GET /public/integrations/list[?domain=<social|comms|ai|media|storage|shortlink|vpn>] is the source of truth for "what does Postmill integrate with": every registered provider in the seven product-facing domains, with counts, capability flags, descriptions, website and an absolute icon URL. The marketing site reads it instead of hardcoding provider tables. No session, API key or CSRF token is involved; the response is Cache-Control: public, max-age=300, stale-while-revalidate=3600 with a weak ETag and Access-Control-Allow-Origin: *, so it can be fetched from any origin (without credentials). Throttled at 120 requests/minute per IP.

json
{
  "generatedAt": "2026-09-15T16:28:31.012Z",
  "total": 166,
  "domains": [
    {
      "id": "social",
      "label": "Channels",
      "count": 45,
      "capabilityKeys": ["analytics", "comments", "firstComment", "video", "..."],
      "providers": [
        {
          "id": "x",
          "domain": "social",
          "name": "X",
          "description": { "en": "X (formerly Twitter) — real-time public posting and conversation." },
          "website": "https://x.com",
          "icon": "https://app.postmill.ai/icons/platforms/x.png",
          "capabilities": ["analytics", "comments", "firstComment", "poll", "video", "linkPreview", "refreshToken", "watchlist"],
          "maxMedia": 4,
          "beta": false,
          "featured": false,
          "editor": "normal",
          "authType": "oauth1",
          "selfHosted": false,
          "web3": false,
          "chromeExtension": false,
          "comments": { "read": true, "reply": true, "like": true },
          "mentions": true,
          "autoPlugs": true
        }
      ]
    }
  ]
}
  • capabilities lists the keys that are true in the provider's manifest capability object; each domain's capabilityKeys is the union in first-seen order (a ready-made matrix legend).
  • id is the kernel providerId (storage ids are lower-snake, e.g. backblaze_b2).
  • kind (direct | hub | action) is set for AI/media providers, storageKind (built-in | proprietary | s3-compatible) for storage, mediaCategories for media; the editor … autoPlugs block is channels-only.
  • beta mirrors the in-app Beta badge (built without live-key verification); featured / featuredSortOrder are the super-admin curated flags.
  • Icons: channels and comms apps point at the frontend's /icons/platforms/<id>.png set (YouTube is .svg); every other domain points at /icons/providers/<id>.svg, exported from the app's ProviderIcon glyph map by node tools/icons/export-provider-icons.mjs (a backend spec fails CI when a registered provider has no file).
  • Deliberately not included: provider versions, status, credential fields, setup notes and sunset dates — anything that fingerprints the deployment's exact release. That detail stays on the authenticated GET /providers/catalog. The auth, email, contentpack and payments domains are deployment plumbing and are not listed.

Internal integrations API ​

These routes sit under /integrations and are authenticated via cookie session. Mutating routes require CSRF protection and the appropriate RBAC permission.

Integration management ​

MethodPathPermissionPurpose
POST/integrations/provider/:id/connectchannels:createSave a provider page after two-step auth
GET/integrations/:identifier/internal-plugs—Get internal plug definitions for a provider
GET/integrations/list—Full integration list with capabilities
POST/integrations/:id/settingschannels:updateUpdate provider additional settings
POST/integrations/:id/nicknamechannels:updateSet nickname and/or avatar
GET/integrations/social/:integrationchannels:createGenerate OAuth authorization URL
POST/integrations/:id/timechannels:updateConfigure posting time slots
POST/integrations/mentionschannels:updateSearch @mentions across providers
POST/integrations/functionchannels:updateCall a dynamic provider function
POST/integrations/disablechannels:updateDisable a channel
POST/integrations/enablechannels:updateEnable a channel
DELETE/integrations/channels:deleteDelete a channel

GET /integrations/social/:integration accepts ?campaign=<uuid> to auto-tag a newly connected channel onto that campaign.

Plugs ​

MethodPathPermissionPurpose
GET/integrations/plug/list—List all available plug definitions
GET/integrations/:id/plugs—Get plugs configured for an integration
POST/integrations/:id/plugschannels:createCreate or update plug configuration
PUT/integrations/plugs/:id/activatechannels:updateToggle plug active/inactive

Provider-specific ​

MethodPathPurpose
GET/integrations/telegram/updatesPoll Telegram bot for connect message
POST/integrations/moltbook/registerRegister a Moltbook agent
GET/integrations/moltbook/statusCheck Moltbook agent claim status

No-auth integration endpoints ​

Base: /integrations

Used during OAuth callbacks and public provider-page connection.

MethodPathPurpose
GET/integrations/All integration definitions (provider registry)
POST/integrations/social-connect/:integrationOAuth callback handler
POST/integrations/public/provider/:id/connectSave provider page (public, state-gated)
POST/integrations/extension-refreshRefresh Chrome extension cookies

Errors ​

Common status codes:

  • 400 — Validation error, missing fields, or malformed dates.
  • 401 — Missing or invalid API key/OAuth token, or no active subscription.
  • 403 — Insufficient OAuth scope or RBAC permission.
  • 404 — Unknown resource.
  • 409 — Idempotency conflict.
  • 410 — Requested provider version has been retired.
  • 429 — Rate limit exceeded.

Verified against v1.3.0

The AI-native social media management platform — postmill.ai