{"openapi":"3.1.0","paths":{"/v1/auth/me":{"get":{"operationId":"AuthController_getMe","parameters":[],"responses":{"200":{"description":""}},"tags":["auth"],"security":[{"firebaseAuth":[]}]},"patch":{"operationId":"AuthController_updateMe","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateProfileDto"}}}},"responses":{"200":{"description":""}},"tags":["auth"],"security":[{"firebaseAuth":[]}]},"delete":{"operationId":"AuthController_deleteMe","parameters":[],"responses":{"200":{"description":""}},"tags":["auth"],"security":[{"firebaseAuth":[]}]}},"/v1/auth/me/audit-log":{"get":{"operationId":"AuthController_getMyAuditLog","parameters":[{"name":"limit","required":false,"in":"query","description":"Page size (1–200, default 50)","schema":{"type":"number"}},{"name":"cursor","required":false,"in":"query","description":"Opaque cursor from a prior response's nextCursor","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"tags":["auth"],"security":[{"firebaseAuth":[]}]}},"/v1/auth/send-verification":{"post":{"operationId":"AuthController_sendVerification","parameters":[],"responses":{"201":{"description":""}},"tags":["auth"],"security":[{"firebaseAuth":[]}]}},"/v1/auth/change-email":{"post":{"operationId":"AuthController_changeEmail","parameters":[],"responses":{"201":{"description":""}},"tags":["auth"],"security":[{"firebaseAuth":[]}]}},"/v1/auth/change-email-unverified":{"post":{"operationId":"AuthController_changeEmailUnverified","parameters":[],"responses":{"201":{"description":""}},"tags":["auth"],"security":[{"firebaseAuth":[]}]}},"/v1/auth/usage":{"get":{"operationId":"AuthController_getUsage","parameters":[],"responses":{"200":{"description":""}},"tags":["auth"],"security":[{"firebaseAuth":[]}]}},"/v1/profiles":{"post":{"operationId":"ProfilesController_create","summary":"Create a brand/profile","description":"Creates a new profile (brand workspace) for the authenticated account. Body: CreateProfileDto { name, avatar?, brandColor? }. Enforces the plan profile limit (409 when exceeded).","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateProfileDto"}}}},"responses":{"200":{"description":"ProfileRecord: { id, name, avatar?, brandColor?, createdAt, updatedAt }."}},"tags":["profiles"],"security":[{"apiKey":[]},{"firebaseAuth":[]}]},"get":{"operationId":"ProfilesController_findAll","summary":"List accessible profiles","description":"Flat union of the caller's own profiles plus profiles an org owner granted them, keyed off the real caller so the picker lists every accessible profile across workspaces.","parameters":[],"responses":{"200":{"description":"AccessibleProfile[] — each with ownerUid + the caller's role tag."}},"tags":["profiles"],"security":[{"apiKey":[]},{"firebaseAuth":[]}]}},"/v1/profiles/links":{"get":{"operationId":"ProfilesController_getAllLinks","summary":"List all profile links","description":"Every parent→child profile link (inheritance edge) for the authenticated account.","parameters":[],"responses":{"200":{"description":"ProfileLinkRecord[] — { id, parentId, childId, inheritanceRules, createdAt }."}},"tags":["profiles"],"security":[{"apiKey":[]},{"firebaseAuth":[]}]},"post":{"operationId":"ProfilesController_createLink","summary":"Create a profile link","description":"Links a parent profile to a child profile with optional inheritance rules. Body: CreateProfileLinkDto { parentId, childId, inheritanceRules? { autoRepost, languageOverride, tagPrefix } }. Rejects cycles.","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateProfileLinkDto"}}}},"responses":{"200":{"description":"ProfileLinkRecord — { id, parentId, childId, inheritanceRules, createdAt }."}},"tags":["profiles"],"security":[{"apiKey":[]},{"firebaseAuth":[]}]}},"/v1/profiles/links/history":{"get":{"operationId":"ProfilesController_getLinkHistory","summary":"Profile link history","description":"Previously-removed profile links (soft-deleted edges), each tagged with when it was removed.","parameters":[],"responses":{"200":{"description":"Array of ProfileLinkRecord & { removedAt }."}},"tags":["profiles"],"security":[{"apiKey":[]},{"firebaseAuth":[]}]}},"/v1/profiles/links/{linkId}":{"delete":{"operationId":"ProfilesController_deleteLink","summary":"Delete a profile link","description":"Soft-deletes one parent→child profile link by its id.","parameters":[{"name":"linkId","required":true,"in":"path","description":"Profile link id to remove.","schema":{"type":"string"}}],"responses":{"200":{"description":"No content (204-style empty body)."}},"tags":["profiles"],"security":[{"apiKey":[]},{"firebaseAuth":[]}]}},"/v1/profiles/{id}":{"get":{"operationId":"ProfilesController_findOne","summary":"Get one profile","description":"Fetches a single profile by id for the authenticated account.","parameters":[{"name":"id","required":true,"in":"path","description":"Profile id.","schema":{"type":"string"}}],"responses":{"200":{"description":"ProfileRecord: { id, name, avatar?, brandColor?, createdAt, updatedAt }."}},"tags":["profiles"],"security":[{"apiKey":[]},{"firebaseAuth":[]}]},"patch":{"operationId":"ProfilesController_update","summary":"Update a profile","description":"Partially updates a profile. Body: UpdateProfileDto { name?, avatar?, brandColor? }.","parameters":[{"name":"id","required":true,"in":"path","description":"Profile id.","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateProfileDto"}}}},"responses":{"200":{"description":"Updated ProfileRecord: { id, name, avatar?, brandColor?, createdAt, updatedAt }."}},"tags":["profiles"],"security":[{"apiKey":[]},{"firebaseAuth":[]}]},"delete":{"operationId":"ProfilesController_remove","summary":"Delete a profile","description":"Removes a profile by id for the authenticated account.","parameters":[{"name":"id","required":true,"in":"path","description":"Profile id.","schema":{"type":"string"}}],"responses":{"200":{"description":"No content (204-style empty body)."}},"tags":["profiles"],"security":[{"apiKey":[]},{"firebaseAuth":[]}]}},"/v1/profiles/{id}/children":{"get":{"operationId":"ProfilesController_getChildren","summary":"List a profile's child links","description":"The child profile links (inheritance edges) whose parent is the given profile id.","parameters":[{"name":"id","required":true,"in":"path","description":"Parent profile id.","schema":{"type":"string"}}],"responses":{"200":{"description":"ProfileLinkRecord[] — { id, parentId, childId, inheritanceRules, createdAt }."}},"tags":["profiles"],"security":[{"apiKey":[]},{"firebaseAuth":[]}]}},"/v1/admin/feature-flags":{"get":{"operationId":"FeatureFlagsController_listAll","parameters":[],"responses":{"200":{"description":""}}}},"/v1/admin/feature-flags/{key}":{"get":{"operationId":"FeatureFlagsController_getOne","parameters":[{"name":"key","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":""}}},"patch":{"operationId":"FeatureFlagsController_update","parameters":[{"name":"key","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":""}}}},"/v1/platforms/{platform}/auth-url":{"get":{"operationId":"PlatformController_getAuthUrl","parameters":[{"name":"platform","required":true,"in":"path","schema":{"type":"string"}},{"name":"profileId","required":true,"in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"tags":["platforms"],"security":[{"firebaseAuth":[]}]}},"/v1/platforms/{platform}/callback":{"get":{"operationId":"PlatformController_handleCallback","parameters":[{"name":"platform","required":true,"in":"path","schema":{"type":"string"}},{"name":"code","required":true,"in":"query","schema":{"type":"string"}},{"name":"state","required":true,"in":"query","schema":{"type":"string"}},{"name":"error","required":true,"in":"query","schema":{"type":"string"}},{"name":"error_description","required":true,"in":"query","schema":{"type":"string"}},{"name":"logid","required":true,"in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"tags":["platforms"],"security":[{"firebaseAuth":[]}]}},"/v1/platforms/bluesky/connect":{"post":{"operationId":"PlatformController_connectBluesky","parameters":[],"responses":{"201":{"description":""}},"tags":["platforms"],"security":[{"firebaseAuth":[]}]}},"/v1/platforms/accounts":{"get":{"operationId":"PlatformController_listAccounts","parameters":[{"name":"profileId","required":true,"in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"tags":["platforms"],"security":[{"firebaseAuth":[]}]}},"/v1/platforms/accounts/all":{"get":{"operationId":"PlatformController_listAllAccounts","parameters":[],"responses":{"200":{"description":""}},"tags":["platforms"],"security":[{"firebaseAuth":[]}]}},"/v1/platforms/facebook/pages":{"get":{"operationId":"PlatformController_listFacebookPages","parameters":[{"name":"profileId","required":true,"in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"tags":["platforms"],"security":[{"firebaseAuth":[]}]}},"/v1/platforms/facebook/switch-page":{"post":{"operationId":"PlatformController_switchFacebookPage","parameters":[],"responses":{"201":{"description":""}},"tags":["platforms"],"security":[{"firebaseAuth":[]}]}},"/v1/platforms/linkedin/pages":{"get":{"operationId":"PlatformController_listLinkedInPages","parameters":[{"name":"profileId","required":true,"in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"tags":["platforms"],"security":[{"firebaseAuth":[]}]}},"/v1/platforms/linkedin/connect-page":{"post":{"operationId":"PlatformController_connectLinkedInPage","parameters":[],"responses":{"201":{"description":""}},"tags":["platforms"],"security":[{"firebaseAuth":[]}]}},"/v1/platforms/accounts/{accountId}/health":{"get":{"operationId":"PlatformController_checkHealth","parameters":[{"name":"accountId","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"tags":["platforms"],"security":[{"firebaseAuth":[]}]}},"/v1/platforms/accounts/{accountId}/affected-posts":{"get":{"operationId":"PlatformController_affectedPosts","parameters":[{"name":"accountId","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"tags":["platforms"],"security":[{"firebaseAuth":[]}]}},"/v1/platforms/accounts/{accountId}":{"delete":{"operationId":"PlatformController_disconnectAccount","parameters":[{"name":"accountId","required":true,"in":"path","schema":{"type":"string"}},{"name":"action","required":true,"in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"tags":["platforms"],"security":[{"firebaseAuth":[]}]}},"/v1/platforms/twitter/quota":{"get":{"operationId":"PlatformController_getTwitterQuota","parameters":[],"responses":{"200":{"description":""}},"tags":["platforms"],"security":[{"firebaseAuth":[]}]}},"/v1/platforms/meta/data-deletion":{"post":{"operationId":"MetaCallbackController_dataDeletion","parameters":[],"responses":{"200":{"description":""}}}},"/v1/platforms/meta/deauthorize":{"post":{"operationId":"MetaCallbackController_deauthorize","parameters":[],"responses":{"204":{"description":""}}}},"/v1/platforms/meta/data-deletion/status":{"get":{"operationId":"MetaCallbackController_deletionStatus","parameters":[{"name":"code","required":true,"in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":""}}}},"/v1/media/upload":{"post":{"operationId":"MediaController_upload","summary":"Upload a media file (multipart)","description":"Streams a multipart/form-data file to storage. Allowed: mp4, mov, avi, webm, jpg/jpeg, png, webp, gif. Max 500MB. Extension must match the content type.","parameters":[],"responses":{"200":{"description":"UploadResult: { url, storagePath, fileName, size, contentType }."}},"tags":["media"],"security":[{"apiKey":[]},{"firebaseAuth":[]}]}},"/v1/media/presign-upload":{"post":{"operationId":"MediaController_presignUpload","summary":"Get a signed upload URL","description":"Issues a signed URL the caller PUTs the file to directly. Body: { fileName, contentType } (both required; contentType must be an allowed media type). URL expires in 15 minutes.","parameters":[],"responses":{"200":{"description":"{ uploadUrl, storagePath, expiresAt (ISO) }."}},"tags":["media"],"security":[{"apiKey":[]},{"firebaseAuth":[]}]}},"/v1/media/presign-upload/batch":{"post":{"operationId":"MediaController_presignUploadBatch","summary":"Batch signed upload URLs","description":"Requests up to 50 signed upload URLs at once. Body: { files: [{ fileName, contentType }, ...] } — each contentType must be an allowed media type.","parameters":[],"responses":{"200":{"description":"{ files: [{ uploadUrl, storagePath, expiresAt }] }."}},"tags":["media"],"security":[{"apiKey":[]},{"firebaseAuth":[]}]}},"/v1/media/proxy/{token}":{"get":{"operationId":"MediaProxyController_proxy","parameters":[{"name":"token","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":""}}}},"/v1/connections/providers":{"get":{"operationId":"ConnectionsController_list","summary":"List connectable providers","description":"Capability listing of every supported platform adapter — its default OAuth scopes and capability flags. Used by the SDK and dashboard.","parameters":[],"responses":{"200":{"description":"{ providers: [{ platform, defaultScopes, capabilities }] }."}},"tags":["connections"],"security":[{"apiKey":[]},{"firebaseAuth":[]}]}},"/v1/connections/oauth-url":{"post":{"operationId":"ConnectionsController_createOAuthUrl","summary":"Mint an OAuth connect URL","description":"Builds a signed-state authorization URL for the given platform. Body: { platform, profileId, redirectUrl (all required), finalRedirectUrl?, headless?, inviteToken? }.","parameters":[],"responses":{"200":{"description":"{ authUrl, state, expiresAt (ISO) }."}},"tags":["connections"],"security":[{"apiKey":[]},{"firebaseAuth":[]}]}},"/v1/connections/{accountId}/upgrade-scopes":{"post":{"operationId":"ConnectionsController_scopeUpgrade","summary":"Mint an incremental scope-upgrade URL","description":"Builds an auth URL for granting additional scopes on an existing connection. Body: { scopes[] (required), redirectUrl (required), finalRedirectUrl?, headless? }. Same response shape as /oauth-url.","parameters":[{"name":"accountId","required":true,"in":"path","description":"Connected account id to upgrade.","schema":{"type":"string"}}],"responses":{"200":{"description":"{ authUrl, state, expiresAt (ISO) }."}},"tags":["connections"],"security":[{"apiKey":[]},{"firebaseAuth":[]}]}},"/v1/connections/{accountId}":{"delete":{"operationId":"ConnectionsController_disconnect","summary":"Disconnect an account","description":"User-initiated disconnect (soft-delete) of a connected account. Provider revoke webhooks are handled separately.","parameters":[{"name":"accountId","required":true,"in":"path","description":"Connected account id to disconnect.","schema":{"type":"string"}}],"responses":{"200":{"description":"{ success: true }."}},"tags":["connections"],"security":[{"apiKey":[]},{"firebaseAuth":[]}]}},"/v1/oauth/callback/{platform}":{"get":{"operationId":"OAuthCallbackController_callback","summary":"OAuth provider callback (public)","description":"Public redirect target the provider bounces back to after user consent. Consumes the signed state, completes the token exchange, then redirects to the dashboard (or returns JSON when the state was headless). TikTok states on the platforms pipeline are forwarded to /v1/platforms/tiktok/callback.","parameters":[{"name":"platform","required":true,"in":"path","description":"Platform slug (e.g. instagram, tiktok, linkedin).","schema":{"type":"string"}},{"name":"state","required":false,"in":"query","description":"Signed state minted at connect time.","schema":{"type":"string"}},{"name":"code","required":false,"in":"query","description":"Provider authorization code.","schema":{"type":"string"}},{"name":"error","required":false,"in":"query","description":"Provider-side error (set when the user cancels).","schema":{}}],"responses":{"200":{"description":"302 redirect to the dashboard, or JSON payload when the connect flow was headless."}},"tags":["oauth"]}},"/v1/connections/webhooks/{platform}":{"post":{"operationId":"ConnectionsWebhooksController_handle","parameters":[{"name":"platform","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"tags":["connections-webhooks"]}},"/v1/posts":{"post":{"operationId":"PostsController_create","summary":"Create a post (draft, scheduled, or publish-now)","description":"Creates a post for one profile across the selected platforms. Runs the shared capability validator; unconfirmed warnings reject with HTTP 400. Pass scheduledAt to schedule, saveAsDraft to bypass publish-time validation. Idempotent via the Idempotency-Key header.","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreatePostDto"}}}},"responses":{"201":{"description":"The created post record."}},"tags":["posts"],"security":[{"apiKey":[]},{"firebaseAuth":[]}]},"get":{"operationId":"PostsController_findAll","summary":"List posts (paginated)","description":"Returns posts for the account, optionally scoped to one profile. Cursor-paginated; omit limit+cursor to fetch the whole set (calendar view). Soft-deleted posts are excluded (see GET /trash).","parameters":[{"name":"profileId","required":false,"in":"query","description":"Scope to one brand/profile.","schema":{"type":"string"}},{"name":"limit","required":false,"in":"query","description":"Page size (1–100). Omit with no cursor to fetch the whole set.","schema":{"type":"string"}},{"name":"cursor","required":false,"in":"query","description":"Post id to page after (from a previous response).","schema":{"type":"string"}},{"name":"sort","required":false,"in":"query","description":"Sort key (e.g. createdAt/scheduledAt, optionally scheduled-only).","schema":{"type":"string"}}],"responses":{"200":{"description":"{ posts: PostRecord[], nextCursor, hasMore } — a page of posts."}},"tags":["posts"],"security":[{"apiKey":[]},{"firebaseAuth":[]}]}},"/v1/posts/fanout":{"post":{"operationId":"PostsController_createFanout","summary":"Create a fan-out post (master + per-profile children)","description":"Creates one master post and a child variant per target profile, each with optional caption/tag/schedule overrides. Idempotent via the Idempotency-Key header.","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateFanoutPostDto"}}}},"responses":{"201":{"description":"The created master post plus its child posts."}},"tags":["posts"],"security":[{"apiKey":[]},{"firebaseAuth":[]}]}},"/v1/posts/validate":{"post":{"operationId":"PostsController_validate","summary":"Validate a draft against the capability rules","description":"Runs the shared @letspost/post-capabilities validator against a draft and returns the structured result. Read-only — persists nothing. Always returns 200 regardless of whether the post would pass.","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreatePostDto"}}}},"responses":{"200":{"description":"{ canPublish, errors, warnings, confirmedWarnings, infos, perPlatformPlan } — structured capability result."}},"tags":["posts"],"security":[{"apiKey":[]},{"firebaseAuth":[]}]}},"/v1/posts/stats":{"get":{"operationId":"PostsController_getStats","summary":"Post counts by status","description":"Aggregated post counts (bucketed by status, with a per-network breakdown) for the account, optionally scoped to one profile. Excludes soft-deleted and uncommitted rows.","parameters":[{"name":"profileId","required":false,"in":"query","description":"Scope to one brand/profile. Omit for the whole account.","schema":{"type":"string"}}],"responses":{"200":{"description":"Post-count stats: totals per status bucket plus a per-platform breakdown."}},"tags":["posts"],"security":[{"apiKey":[]},{"firebaseAuth":[]}]}},"/v1/posts/trash":{"get":{"operationId":"PostsController_listTrash","summary":"List soft-deleted posts (trash)","description":"Lists posts in the trash (soft-deleted within the 30-day window), optionally scoped to one profile.","parameters":[{"name":"profileId","required":false,"in":"query","description":"Scope to one brand/profile.","schema":{"type":"string"}}],"responses":{"200":{"description":"PostRecord[] — soft-deleted posts."}},"tags":["posts"],"security":[{"apiKey":[]},{"firebaseAuth":[]}]},"delete":{"operationId":"PostsController_emptyTrash","summary":"Empty the trash","description":"Permanently deletes every soft-deleted post, optionally scoped to one profile. Irreversible.","parameters":[{"name":"profileId","required":false,"in":"query","description":"Only wipe trash for this brand/profile.","schema":{"type":"string"}}],"responses":{"200":{"description":"{ deleted } — number of posts permanently removed."}},"tags":["posts"],"security":[{"apiKey":[]},{"firebaseAuth":[]}]}},"/v1/posts/{id}":{"get":{"operationId":"PostsController_findOne","summary":"Get one post","description":"Returns the full post record for one id, if it is within the caller's profile scope.","parameters":[{"name":"id","required":true,"in":"path","description":"Internal LetsPost post id.","schema":{"type":"string"}}],"responses":{"200":{"description":"PostRecord — the full post document."}},"tags":["posts"],"security":[{"apiKey":[]},{"firebaseAuth":[]}]},"patch":{"operationId":"PostsController_update","summary":"Update a post","description":"Partially updates a post (content, media, platforms, schedule, format). Only supplied fields change.","parameters":[{"name":"id","required":true,"in":"path","description":"Internal LetsPost post id.","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdatePostDto"}}}},"responses":{"200":{"description":"The updated post record."}},"tags":["posts"],"security":[{"apiKey":[]},{"firebaseAuth":[]}]},"delete":{"operationId":"PostsController_remove","summary":"Soft-delete a post","description":"Moves a post to the trash (recoverable within 30 days via POST /:id/restore). Does not delete anything from the social platforms.","parameters":[{"name":"id","required":true,"in":"path","description":"Internal LetsPost post id.","schema":{"type":"string"}}],"responses":{"200":{"description":"Delete acknowledgement."}},"tags":["posts"],"security":[{"apiKey":[]},{"firebaseAuth":[]}]}},"/v1/posts/{id}/restore":{"post":{"operationId":"PostsController_restore","summary":"Restore a soft-deleted post","description":"Restores a post from the trash (within the 30-day window).","parameters":[{"name":"id","required":true,"in":"path","description":"Internal LetsPost post id.","schema":{"type":"string"}}],"responses":{"200":{"description":"The restored post record."}},"tags":["posts"],"security":[{"apiKey":[]},{"firebaseAuth":[]}]}},"/v1/posts/{id}/status":{"get":{"operationId":"PostsController_getStatus","summary":"Per-platform publish status","description":"Returns the per-platform publish results for a post (status, platformPostId, platformUrl, error/rawError, attempts, publishedAt).","parameters":[{"name":"id","required":true,"in":"path","description":"Internal LetsPost post id.","schema":{"type":"string"}}],"responses":{"200":{"description":"Array of per-platform result rows { platform, status, platformPostId, platformUrl, error, rawError, attempts, publishedAt }."}},"tags":["posts"],"security":[{"apiKey":[]},{"firebaseAuth":[]}]}},"/v1/posts/{id}/audit":{"get":{"operationId":"PostsController_getAuditLog","summary":"Post audit log","description":"Returns the post's audit-log entries (newest first), each with an ISO `at` timestamp.","parameters":[{"name":"id","required":true,"in":"path","description":"Internal LetsPost post id.","schema":{"type":"string"}}],"responses":{"200":{"description":"Audit-log entries (newest first), each with an ISO `at` timestamp."}},"tags":["posts"],"security":[{"apiKey":[]},{"firebaseAuth":[]}]}},"/v1/posts/{id}/retry":{"post":{"operationId":"PostsController_retry","summary":"Retry failed platforms","description":"Re-enqueues the post's failed platforms for another publish attempt.","parameters":[{"name":"id","required":true,"in":"path","description":"Internal LetsPost post id.","schema":{"type":"string"}}],"responses":{"200":{"description":"Retry acknowledgement / updated publish state."}},"tags":["posts"],"security":[{"apiKey":[]},{"firebaseAuth":[]}]}},"/v1/posts/{id}/report-stuck":{"post":{"operationId":"PostsController_reportStuck","summary":"Report a stuck post","description":"Flags a post stuck in queued/publishing (>5 min) — writes an adminAlerts warning row for support. Empty body; the server resolves post state.","parameters":[{"name":"id","required":true,"in":"path","description":"Internal LetsPost post id.","schema":{"type":"string"}}],"responses":{"200":{"description":"{ ok: true }."}},"tags":["posts"],"security":[{"apiKey":[]},{"firebaseAuth":[]}]}},"/v1/posts/{id}/publish-now":{"post":{"operationId":"PostsController_publishNow","summary":"Publish a draft now","description":"Flips a draft post into the publish queue immediately. The post must be in `draft` — reschedule a queued/scheduled post via PATCH instead.","parameters":[{"name":"id","required":true,"in":"path","description":"Internal LetsPost post id (must be a draft).","schema":{"type":"string"}}],"responses":{"200":{"description":"Updated post / publish acknowledgement."}},"tags":["posts"],"security":[{"apiKey":[]},{"firebaseAuth":[]}]}},"/v1/posts/{id}/platforms/{platform}":{"delete":{"operationId":"PostsController_deletePlatform","summary":"Delete a platform from a post (always 403)","description":"Explicit 403 guard: LetsPost does not delete posts from social platforms. Kept so older clients get a clear refusal instead of a 404.","parameters":[{"name":"id","required":true,"in":"path","description":"Internal LetsPost post id.","schema":{"type":"string"}},{"name":"platform","required":true,"in":"path","description":"Platform to remove (e.g. instagram).","schema":{"type":"string"}}],"responses":{"403":{"description":"Always refused — delete the post on the platform itself."}},"tags":["posts"],"security":[{"apiKey":[]},{"firebaseAuth":[]}]}},"/v1/posts/{id}/expand":{"post":{"operationId":"PostsController_expand","summary":"Expand a post to more platforms","description":"Publishes an existing post to platforms it hasn't reached yet.","parameters":[{"name":"id","required":true,"in":"path","description":"Internal LetsPost post id.","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ExpandPostDto"}}}},"responses":{"200":{"description":"Expand acknowledgement / updated publish state."}},"tags":["posts"],"security":[{"apiKey":[]},{"firebaseAuth":[]}]}},"/v1/posts/{id}/duplicate":{"post":{"operationId":"PostsController_duplicate","summary":"Duplicate a post","description":"Clones an existing post as a new draft. Pass scheduledAt to clone+schedule in one call (recycle flow).","parameters":[{"name":"id","required":true,"in":"path","description":"Internal LetsPost post id to clone.","schema":{"type":"string"}}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DuplicatePostDto"}}}},"responses":{"201":{"description":"The cloned post record."}},"tags":["posts"],"security":[{"apiKey":[]},{"firebaseAuth":[]}]}},"/v1/posts/{id}/recycles":{"get":{"operationId":"PostsController_listRecycles","summary":"List recycle clones of a post","description":"Lists every clone produced from this post via the recycle flow (the \"Recycle history\" panel).","parameters":[{"name":"id","required":true,"in":"path","description":"Internal LetsPost post id (the recycle source).","schema":{"type":"string"}}],"responses":{"200":{"description":"PostRecord[] — clones produced from this post."}},"tags":["posts"],"security":[{"apiKey":[]},{"firebaseAuth":[]}]}},"/v1/posts/{id}/recycle-config":{"patch":{"operationId":"PostsController_setRecycleConfig","summary":"Set auto-recycle config","description":"Enables/disables auto-recycle on a post. When enabled, the recycle cron clones+schedules it every intervalDays until disabled.","parameters":[{"name":"id","required":true,"in":"path","description":"Internal LetsPost post id.","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RecycleConfigDto"}}}},"responses":{"200":{"description":"The updated recycle config."}},"tags":["posts"],"security":[{"apiKey":[]},{"firebaseAuth":[]}]}},"/v1/posts/bulk":{"post":{"operationId":"PostsController_bulkCreate","summary":"Bulk-create draft posts","description":"Creates up to 50 draft posts in one call. Body: { posts: CreatePostDto[] }.","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["posts"],"properties":{"posts":{"type":"array","maxItems":50,"items":{"$ref":"#/components/schemas/CreatePostDto"}}}}}}},"responses":{"201":{"description":"{ created, posts: PostRecord[] } — the created drafts."}},"tags":["posts"],"security":[{"apiKey":[]},{"firebaseAuth":[]}]}},"/v1/posts/optimize-captions":{"post":{"operationId":"PostsController_optimizeCaptions","summary":"Optimize captions per platform","description":"Generates per-platform optimized captions from a universal title + description. Returns platform-specific character counts, hashtag placement, and warnings. Counts against the monthly AI-compose quota (429/400 when the cap is hit).","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/OptimizeCaptionsDto"}}}},"responses":{"200":{"description":"Per-platform optimized captions with character counts, hashtag placement, and warnings."}},"tags":["posts"],"security":[{"apiKey":[]},{"firebaseAuth":[]}]}},"/v1/posts/{id}/share-link":{"post":{"operationId":"PostsController_createShareLink","summary":"Create a public share link (Pro+)","description":"Mints a public share link for a post so it can be previewed without authentication.","parameters":[{"name":"id","required":true,"in":"path","description":"Internal LetsPost post id.","schema":{"type":"string"}}],"responses":{"201":{"description":"The minted share link (id + public URL)."}},"tags":["posts"],"security":[{"apiKey":[]},{"firebaseAuth":[]}]}},"/v1/posts/{id}/share-links":{"get":{"operationId":"PostsController_listShareLinks","summary":"List a post's share links","description":"Lists every public share link minted for a post.","parameters":[{"name":"id","required":true,"in":"path","description":"Internal LetsPost post id.","schema":{"type":"string"}}],"responses":{"200":{"description":"Share links for the post (id, public URL, created/revoked state)."}},"tags":["posts"],"security":[{"apiKey":[]},{"firebaseAuth":[]}]}},"/v1/posts/{id}/share-link/{linkId}":{"delete":{"operationId":"PostsController_revokeShareLink","summary":"Revoke a share link","description":"Revokes a single public share link so its URL stops resolving.","parameters":[{"name":"id","required":true,"in":"path","description":"Internal LetsPost post id.","schema":{"type":"string"}},{"name":"linkId","required":true,"in":"path","description":"Share link id to revoke.","schema":{"type":"string"}}],"responses":{"200":{"description":"Revoke acknowledgement."}},"tags":["posts"],"security":[{"apiKey":[]},{"firebaseAuth":[]}]}},"/v1/posts/auto-schedule":{"post":{"operationId":"PostsController_autoSchedule","summary":"Auto-schedule draft posts","description":"Distributes the given draft posts evenly across future days, using postsPerDay and optional startDate + HH:MM timeSlots.","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AutoScheduleDto"}}}},"responses":{"200":{"description":"The scheduled posts / assigned slots."}},"tags":["posts"],"security":[{"apiKey":[]},{"firebaseAuth":[]}]}},"/v1/share/p/{token}":{"get":{"operationId":"ShareController_resolve","parameters":[{"name":"token","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":""}}}},"/v1/recurring-posts":{"post":{"operationId":"RecurringController_create","summary":"Create a recurring rule from an existing post","description":"Reposts the template post on a repeating cadence (5-field cron in the user timezone). Returns the created rule (rec_* id).","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateRecurringDto"}}}},"responses":{"201":{"description":"The created recurring rule."}},"tags":["recurring-posts"],"security":[{"apiKey":[]},{"firebaseAuth":[]}]},"get":{"operationId":"RecurringController_list","summary":"List recurring rules","parameters":[],"responses":{"200":{"description":"Recurring rules, newest first."}},"tags":["recurring-posts"],"security":[{"apiKey":[]},{"firebaseAuth":[]}]}},"/v1/recurring-posts/{id}":{"delete":{"operationId":"RecurringController_cancel","summary":"Cancel (deactivate) a recurring rule","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":"Whether a rule was cancelled."}},"tags":["recurring-posts"],"security":[{"apiKey":[]},{"firebaseAuth":[]}]}},"/v1/queue/submit/{postId}":{"post":{"operationId":"QueueController_submitPost","summary":"Submit a post to the publish queue","description":"Enqueues Cloud Tasks to publish the post to each of its platforms (fanning out carousels). Only draft/scheduled/failed posts are submittable. 404 if the post doesn't exist, 403 if not owned or in a non-submittable status.","parameters":[{"name":"postId","required":true,"in":"path","description":"Internal LetsPost post id.","schema":{"type":"string"}}],"responses":{"200":{"description":"{ success, postId, platforms } — the platforms the post was queued for."}},"tags":["queue"],"security":[{"apiKey":[]},{"firebaseAuth":[]}]}},"/v1/queue/cancel/{postId}":{"delete":{"operationId":"QueueController_cancelPost","summary":"Cancel a post's scheduled publish tasks","description":"Deletes any pending Cloud Tasks queued to publish the post.","parameters":[{"name":"postId","required":true,"in":"path","description":"Internal LetsPost post id.","schema":{"type":"string"}}],"responses":{"200":{"description":"{ success, postId }."}},"tags":["queue"],"security":[{"apiKey":[]},{"firebaseAuth":[]}]}},"/v1/queue/status/{postId}":{"get":{"operationId":"QueueController_getStatus","summary":"Publish status and per-platform results for a post","description":"Returns the post's status plus the per-platform publish results. 404 if not found, 403 if not owned.","parameters":[{"name":"postId","required":true,"in":"path","description":"Internal LetsPost post id.","schema":{"type":"string"}}],"responses":{"200":{"description":"{ postId, status, platforms } — platforms is the per-platform publish result map."}},"tags":["queue"],"security":[{"apiKey":[]},{"firebaseAuth":[]}]}},"/v1/admin/webhooks/retry-failed":{"post":{"operationId":"AdminController_runWebhookRetry","parameters":[],"responses":{"201":{"description":""}},"tags":["admin"],"security":[{"apiKey":[]},{"firebaseAuth":[]}]}},"/v1/admin/billing/enforce-past-due":{"post":{"operationId":"AdminController_runEnforcePastDue","parameters":[],"responses":{"201":{"description":""}},"tags":["admin"],"security":[{"apiKey":[]},{"firebaseAuth":[]}]}},"/v1/admin/connections/refresh":{"post":{"operationId":"AdminController_runConnectionRefresh","parameters":[],"responses":{"201":{"description":""}},"tags":["admin"],"security":[{"apiKey":[]},{"firebaseAuth":[]}]}},"/v1/admin/overview":{"get":{"operationId":"AdminController_getOverview","parameters":[],"responses":{"200":{"description":""}},"tags":["admin"],"security":[{"apiKey":[]},{"firebaseAuth":[]}]}},"/v1/admin/support/{ticketId}/triage":{"post":{"operationId":"AdminController_triageTicket","parameters":[{"name":"ticketId","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"201":{"description":""}},"tags":["admin"],"security":[{"apiKey":[]},{"firebaseAuth":[]}]}},"/v1/admin/alerts":{"get":{"operationId":"AdminController_listAlerts","parameters":[{"name":"filter","required":true,"in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"tags":["admin"],"security":[{"apiKey":[]},{"firebaseAuth":[]}]},"post":{"operationId":"AdminController_createAlert","parameters":[],"responses":{"201":{"description":""}},"tags":["admin"],"security":[{"apiKey":[]},{"firebaseAuth":[]}]}},"/v1/admin/alerts/{id}/ack":{"post":{"operationId":"AdminController_ackAlert","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"201":{"description":""}},"tags":["admin"],"security":[{"apiKey":[]},{"firebaseAuth":[]}]}},"/v1/admin/alert-prefs":{"get":{"operationId":"AdminController_getAlertPrefs","parameters":[],"responses":{"200":{"description":""}},"tags":["admin"],"security":[{"apiKey":[]},{"firebaseAuth":[]}]},"post":{"operationId":"AdminController_setAlertPrefs","parameters":[],"responses":{"201":{"description":""}},"tags":["admin"],"security":[{"apiKey":[]},{"firebaseAuth":[]}]}},"/v1/admin/funnel":{"get":{"operationId":"AdminController_getFunnel","parameters":[{"name":"days","required":true,"in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"tags":["admin"],"security":[{"apiKey":[]},{"firebaseAuth":[]}]}},"/v1/admin/cancellation-feedback":{"get":{"operationId":"AdminController_getCancellationFeedback","parameters":[{"name":"days","required":true,"in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"tags":["admin"],"security":[{"apiKey":[]},{"firebaseAuth":[]}]}},"/v1/admin/platforms/health":{"get":{"operationId":"AdminController_getPlatformsHealth","parameters":[],"responses":{"200":{"description":""}},"tags":["admin"],"security":[{"apiKey":[]},{"firebaseAuth":[]}]}},"/v1/admin/platforms/usage":{"get":{"operationId":"AdminController_getPlatformsUsage","parameters":[{"name":"date","required":true,"in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"tags":["admin"],"security":[{"apiKey":[]},{"firebaseAuth":[]}]}},"/v1/admin/platforms/mode":{"get":{"operationId":"AdminController_listPlatformsMode","parameters":[],"responses":{"200":{"description":""}},"tags":["admin"],"security":[{"apiKey":[]},{"firebaseAuth":[]}]}},"/v1/admin/platforms/mode/pinterest":{"post":{"operationId":"AdminController_setPinterestMode","parameters":[],"responses":{"201":{"description":""}},"tags":["admin"],"security":[{"apiKey":[]},{"firebaseAuth":[]}]}},"/v1/admin/cron/status":{"get":{"operationId":"AdminController_getCronStatus","parameters":[],"responses":{"200":{"description":""}},"tags":["admin"],"security":[{"apiKey":[]},{"firebaseAuth":[]}]}},"/v1/admin/cron/configs":{"get":{"operationId":"AdminController_getCronConfigs","parameters":[],"responses":{"200":{"description":""}},"tags":["admin"],"security":[{"apiKey":[]},{"firebaseAuth":[]}]}},"/v1/admin/email-templates":{"get":{"operationId":"AdminController_listEmailTemplates","parameters":[],"responses":{"200":{"description":""}},"tags":["admin"],"security":[{"apiKey":[]},{"firebaseAuth":[]}]}},"/v1/admin/email-templates/{name}":{"get":{"operationId":"AdminController_getEmailTemplate","parameters":[{"name":"name","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"tags":["admin"],"security":[{"apiKey":[]},{"firebaseAuth":[]}]},"post":{"operationId":"AdminController_setEmailTemplate","parameters":[{"name":"name","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"201":{"description":""}},"tags":["admin"],"security":[{"apiKey":[]},{"firebaseAuth":[]}]}},"/v1/admin/email-templates/{name}/reset":{"post":{"operationId":"AdminController_resetEmailTemplate","parameters":[{"name":"name","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"201":{"description":""}},"tags":["admin"],"security":[{"apiKey":[]},{"firebaseAuth":[]}]}},"/v1/admin/cron/configs/{name}":{"post":{"operationId":"AdminController_setCronConfig","parameters":[{"name":"name","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"201":{"description":""}},"tags":["admin"],"security":[{"apiKey":[]},{"firebaseAuth":[]}]}},"/v1/admin/cohorts/retention":{"get":{"operationId":"AdminController_getCohortRetention","parameters":[{"name":"weeks","required":true,"in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"tags":["admin"],"security":[{"apiKey":[]},{"firebaseAuth":[]}]}},"/v1/admin/ltv-by-plan":{"get":{"operationId":"AdminController_getLtvByPlan","parameters":[],"responses":{"200":{"description":""}},"tags":["admin"],"security":[{"apiKey":[]},{"firebaseAuth":[]}]}},"/v1/admin/audit-log":{"get":{"operationId":"AdminController_listAuditLog","parameters":[{"name":"limit","required":true,"in":"query","schema":{"type":"string"}},{"name":"cursor","required":true,"in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"tags":["admin"],"security":[{"apiKey":[]},{"firebaseAuth":[]}]}},"/v1/admin/users/{uid}/role":{"post":{"operationId":"AdminController_assignRole","parameters":[{"name":"uid","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"201":{"description":""}},"tags":["admin"],"security":[{"apiKey":[]},{"firebaseAuth":[]}]}},"/v1/admin/users/{uid}":{"get":{"operationId":"AdminController_getUserDetails","parameters":[{"name":"uid","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"tags":["admin"],"security":[{"apiKey":[]},{"firebaseAuth":[]}]}},"/v1/admin/stats":{"get":{"operationId":"AdminController_getStats","parameters":[],"responses":{"200":{"description":""}},"tags":["admin"],"security":[{"apiKey":[]},{"firebaseAuth":[]}]}},"/v1/admin/queue":{"get":{"operationId":"AdminController_getQueue","parameters":[],"responses":{"200":{"description":""}},"tags":["admin"],"security":[{"apiKey":[]},{"firebaseAuth":[]}]}},"/v1/admin/sync-metrics":{"post":{"operationId":"AdminController_syncMetrics","parameters":[],"responses":{"201":{"description":""}},"tags":["admin"],"security":[{"apiKey":[]},{"firebaseAuth":[]}]}},"/v1/admin/email/test-welcome":{"post":{"operationId":"AdminController_testWelcomeEmail","parameters":[],"responses":{"201":{"description":""}},"tags":["admin"],"security":[{"apiKey":[]},{"firebaseAuth":[]}]}},"/v1/admin/email/test-goodbye":{"post":{"operationId":"AdminController_testGoodbyeEmail","parameters":[],"responses":{"201":{"description":""}},"tags":["admin"],"security":[{"apiKey":[]},{"firebaseAuth":[]}]}},"/v1/admin/users":{"get":{"operationId":"AdminController_listActiveUsers","parameters":[{"name":"q","required":true,"in":"query","schema":{"type":"string"}},{"name":"tier","required":true,"in":"query","schema":{"type":"string"}},{"name":"role","required":true,"in":"query","schema":{"type":"string"}},{"name":"page","required":true,"in":"query","schema":{"type":"string"}},{"name":"pageSize","required":true,"in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"tags":["admin"],"security":[{"apiKey":[]},{"firebaseAuth":[]}]}},"/v1/admin/sync-metrics-cron":{"post":{"operationId":"AdminController_syncMetricsCron","parameters":[{"name":"x-cron-secret","required":true,"in":"header","schema":{"type":"string"}}],"responses":{"201":{"description":""}},"tags":["admin"],"security":[{"apiKey":[]},{"firebaseAuth":[]}]}},"/v1/admin/secrets":{"get":{"operationId":"SecretsController_list","parameters":[],"responses":{"200":{"description":""}}}},"/v1/admin/secrets/restart":{"post":{"operationId":"SecretsController_restart","parameters":[],"responses":{"201":{"description":""}}}},"/v1/admin/secrets/create":{"post":{"operationId":"SecretsController_create","parameters":[],"responses":{"201":{"description":""}}}},"/v1/admin/secrets/registry/{name}":{"delete":{"operationId":"SecretsController_removeRegistryEntry","parameters":[{"name":"name","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":""}}}},"/v1/admin/secrets/{name}":{"post":{"operationId":"SecretsController_update","parameters":[{"name":"name","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"201":{"description":""}}}},"/v1/admin/secrets/{name}/revoke":{"post":{"operationId":"SecretsController_revoke","parameters":[{"name":"name","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"201":{"description":""}}}},"/v1/admin/secrets/{name}/test":{"post":{"operationId":"SecretsController_test","parameters":[{"name":"name","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"201":{"description":""}}}},"/v1/admin/mfa/enroll/start":{"post":{"operationId":"MfaController_enrollStart","parameters":[],"responses":{"201":{"description":""}}}},"/v1/admin/mfa/enroll/verify":{"post":{"operationId":"MfaController_enrollVerify","parameters":[],"responses":{"201":{"description":""}}}},"/v1/admin/mfa/recovery-codes/generate":{"post":{"operationId":"MfaController_generateRecoveryCodes","parameters":[],"responses":{"201":{"description":""}}}},"/v1/admin/tests":{"get":{"operationId":"TestRunnerController_list","parameters":[],"responses":{"200":{"description":""}},"tags":["admin-tests"],"security":[{"firebaseAuth":[]}]}},"/v1/admin/tests/run":{"post":{"operationId":"TestRunnerController_run","parameters":[],"responses":{"201":{"description":""}},"tags":["admin-tests"],"security":[{"firebaseAuth":[]}]}},"/v1/admin/tests/runs":{"get":{"operationId":"TestRunnerController_listRuns","parameters":[{"name":"limit","required":true,"in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"tags":["admin-tests"],"security":[{"firebaseAuth":[]}]}},"/v1/admin/tests/runs/{runId}":{"get":{"operationId":"TestRunnerController_getRun","parameters":[{"name":"runId","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"tags":["admin-tests"],"security":[{"firebaseAuth":[]}]}},"/v1/analytics/refresh":{"post":{"operationId":"AnalyticsController_refreshNow","summary":"Trigger an on-demand metrics sync","description":"Pulls fresh metrics from every connected platform for the authenticated account. Rate-limited to once per minute (429 with retryAfterMs otherwise). Returns the per-platform fetch summary.","parameters":[],"responses":{"200":{"description":"{ ok, refreshedAtMs, ...perPlatformSummary } — number of posts/accounts synced per platform."}},"tags":["analytics"],"security":[{"apiKey":[]},{"firebaseAuth":[]}]}},"/v1/analytics/overview":{"get":{"operationId":"AnalyticsController_getOverview","summary":"Account overview (multi-platform aggregate + time-series)","description":"Period summary for a profile: totals, per-platform breakdown (byPlatform), top posts, and a daily time-series (dailyTrend) for charting. Pass platform= to narrow to a single network. startDate is clamped to the plan retention window.","parameters":[{"name":"profileId","required":false,"in":"query","description":"Scope to one brand/profile. Omit for the account default.","schema":{"type":"string"}},{"name":"startDate","required":false,"in":"query","description":"ISO date. Clamped to plan retention. Default: retention window start.","schema":{"type":"string"}},{"name":"endDate","required":false,"in":"query","description":"ISO date. Default: now.","schema":{"type":"string"}},{"name":"granularity","required":false,"in":"query","description":"Time-series bucket. Default: day.","schema":{"enum":["hour","day","week","month"],"type":"string"}},{"name":"platform","required":false,"in":"query","description":"Single-platform filter (e.g. instagram, tiktok). Omit for all connected platforms.","schema":{"type":"string"}}],"responses":{"200":{"description":"AnalyticsOverview: { period, totalPosts, totalViews, totalLikes, totalComments, totalShares, totalSaves, totalImpressions, totalReach, avgEngagementRate (0-100), byPlatform (per-platform posts/views/likes/comments/shares/saves/impressions/reach), topPosts[], dailyTrend[] (per-bucket views/likes/comments/shares/saves/impressions/reach) }."}},"tags":["analytics"],"security":[{"apiKey":[]},{"firebaseAuth":[]}]}},"/v1/analytics/posts/{postId}/history":{"get":{"operationId":"AnalyticsController_getPostHistory","summary":"Per-post metric history (time-series)","description":"Time-series of per-platform metrics for one internal post id, sorted by fetch time.","parameters":[{"name":"postId","required":true,"in":"path","description":"Internal LetsPost post id.","schema":{"type":"string"}}],"responses":{"200":{"description":"PostMetrics[] snapshots over time (views, likes, comments, shares, saves, impressions, reach, engagementRate, fetchedAt)."}},"tags":["analytics"],"security":[{"apiKey":[]},{"firebaseAuth":[]}]}},"/v1/analytics/accounts/{accountId}/history":{"get":{"operationId":"AnalyticsController_getAccountHistory","summary":"Per-account audience history (followers over time)","description":"Time-series of follower / following / total-post counts for one connected account. Falls back to a single snapshot when no history exists yet.","parameters":[{"name":"accountId","required":true,"in":"path","description":"Connected account id.","schema":{"type":"string"}},{"name":"startDate","required":false,"in":"query","description":"ISO date. Clamped to plan retention.","schema":{"type":"string"}},{"name":"endDate","required":false,"in":"query","description":"ISO date. Default: now.","schema":{"type":"string"}}],"responses":{"200":{"description":"AccountMetrics[] over time (followers, following, totalPosts, views, likes, comments, shares, saves, impressions, fetchedAt)."}},"tags":["analytics"],"security":[{"apiKey":[]},{"firebaseAuth":[]}]}},"/v1/analytics/compare":{"get":{"operationId":"AnalyticsController_compare","summary":"Compare two periods (period-over-period diff)","description":"Returns overviews for period A and B plus relative deltas. All four period bounds are required; both periods are clamped to plan retention.","parameters":[{"name":"profileId","required":false,"in":"query","description":"Scope to one brand/profile.","schema":{"type":"string"}},{"name":"periodAStart","required":true,"in":"query","description":"ISO date — period A start.","schema":{"type":"string"}},{"name":"periodAEnd","required":true,"in":"query","description":"ISO date — period A end.","schema":{"type":"string"}},{"name":"periodBStart","required":true,"in":"query","description":"ISO date — period B start.","schema":{"type":"string"}},{"name":"periodBEnd","required":true,"in":"query","description":"ISO date — period B end.","schema":{"type":"string"}},{"name":"granularity","required":false,"in":"query","schema":{"enum":["hour","day","week","month"],"type":"string"}},{"name":"platform","required":false,"in":"query","description":"Single-platform filter.","schema":{"type":"string"}}],"responses":{"200":{"description":"{ a: AnalyticsOverview, b: AnalyticsOverview, delta: { totalPosts, totalViews, totalLikes, totalComments, totalShares, avgEngagementRate } }."}},"tags":["analytics"],"security":[{"apiKey":[]},{"firebaseAuth":[]}]}},"/v1/analytics/export":{"get":{"operationId":"AnalyticsController_export","summary":"Export analytics as CSV","description":"Streams a CSV attachment. type=overview|accounts|best-times. Requires the analytics:export scope.","parameters":[{"name":"type","required":false,"in":"query","description":"Default: overview.","schema":{"enum":["overview","accounts","best-times"],"type":"string"}},{"name":"format","required":false,"in":"query","description":"Only csv today.","schema":{"enum":["csv"],"type":"string"}},{"name":"profileId","required":false,"in":"query","schema":{"type":"string"}},{"name":"startDate","required":false,"in":"query","schema":{"type":"string"}},{"name":"endDate","required":false,"in":"query","schema":{"type":"string"}},{"name":"granularity","required":false,"in":"query","schema":{"enum":["hour","day","week","month"],"type":"string"}},{"name":"platform","required":false,"in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":"text/csv attachment."}},"tags":["analytics"],"security":[{"apiKey":[]},{"firebaseAuth":[]}]}},"/v1/analytics/posts/{postId}":{"get":{"operationId":"AnalyticsController_getPostMetrics","summary":"Per-post metrics (latest, per platform)","description":"Latest per-platform metric rows for a single internal post id.","parameters":[{"name":"postId","required":true,"in":"path","description":"Internal LetsPost post id.","schema":{"type":"string"}}],"responses":{"200":{"description":"PostMetrics[] — one row per platform the post was published to."}},"tags":["analytics"],"security":[{"apiKey":[]},{"firebaseAuth":[]}]}},"/v1/analytics/posts-summary":{"get":{"operationId":"AnalyticsController_getPostMetricsSummary","summary":"Batch per-post engagement summary","description":"Aggregated engagement for up to 100 post ids at once. Posts without synced metrics are omitted from the map.","parameters":[{"name":"postIds","required":false,"in":"query","description":"Comma-separated internal post ids (max 100).","schema":{"type":"string"}}],"responses":{"200":{"description":"Record<postId, { views, likes, comments, shares, engagementRate }>."}},"tags":["analytics"],"security":[{"apiKey":[]},{"firebaseAuth":[]}]}},"/v1/analytics/outliers":{"get":{"operationId":"AnalyticsController_getOutliers","summary":"Top-performer / outlier posts","description":"Profile-wide top-performer detection — posts whose reach is a robust statistical outlier above the account baseline.","parameters":[{"name":"profileId","required":true,"in":"query","description":"Brand/profile to analyze.","schema":{"type":"string"}}],"responses":{"200":{"description":"{ metric, median, sampleSize, outliers: [{ postId, views, engagement, score, multiple }] }."}},"tags":["analytics"],"security":[{"apiKey":[]},{"firebaseAuth":[]}]}},"/v1/analytics/accounts":{"get":{"operationId":"AnalyticsController_getAccountMetrics","summary":"Audience metrics for all connected accounts","description":"Follower/following counts and lifetime totals for every connected account on the authenticated account.","parameters":[],"responses":{"200":{"description":"AccountMetrics[] — { accountId, platform, followers, following, totalPosts, views, likes, comments, shares, saves, impressions, fetchedAt }."}},"tags":["analytics"],"security":[{"apiKey":[]},{"firebaseAuth":[]}]}},"/v1/analytics/account-insights":{"get":{"operationId":"AnalyticsController_getAccountInsights","summary":"Account insights (multi-platform aggregate totals)","description":"Latest account-level insights summed across a profile's active connections (totals) with a per-account breakdown (byAccount). Pass platform= to narrow to a single network. Surfaces organic reach/engagement without needing a LetsPost-published post.","parameters":[{"name":"profileId","required":false,"in":"query","description":"Scope to one brand/profile.","schema":{"type":"string"}},{"name":"platform","required":false,"in":"query","description":"Single-platform filter. Omit to aggregate across all connected platforms.","schema":{"type":"string"}}],"responses":{"200":{"description":"{ totals: { followers, views, likes, comments, shares, saves, impressions }, byAccount: [{ accountId, platform, followers, views, likes, comments, shares, saves, impressions, windowDays, fetchedAt }] }."}},"tags":["analytics"],"security":[{"apiKey":[]},{"firebaseAuth":[]}]}},"/v1/analytics/best-times":{"get":{"operationId":"AnalyticsController_getBestTimes","summary":"Best times to post (data-driven)","description":"Optimal posting times derived from the account's own historical engagement. Returns ranked best/worst slots plus a 7×24 heatmap (0-100). Pass platform= to narrow; omit for all.","parameters":[{"name":"platform","required":false,"in":"query","description":"Single-platform filter (e.g. tiktok). Omit for all connected platforms.","schema":{"type":"string"}}],"responses":{"200":{"description":"{ platform, bestTimes: [{ day, hour, label, score }], worstTimes: [...], heatmap: number[7][24] }."}},"tags":["analytics"],"security":[{"apiKey":[]},{"firebaseAuth":[]}]}},"/v1/agent/telegram/webhook":{"post":{"operationId":"TelegramController_webhook","parameters":[],"responses":{"200":{"description":""}}}},"/v1/agent/telegram/access-code":{"post":{"operationId":"TelegramConnectController_issueAccessCode","summary":"Mint a Telegram access code","description":"Issues a fresh single-use access code plus a t.me deep link the user opens to bind their Telegram chat to the centralized bot. Pro-and-above.","parameters":[],"responses":{"200":{"description":"{ code, expiresAtMs, deepLinkUrl, botUsername }."}},"tags":["telegram"],"security":[{"apiKey":[]},{"firebaseAuth":[]}]}},"/v1/agent/telegram/chats":{"get":{"operationId":"TelegramConnectController_listChats","summary":"List linked Telegram chats","description":"The Telegram chats bound to the authenticated account, plus the bot username.","parameters":[],"responses":{"200":{"description":"{ chats: TelegramChatRecord[], botUsername }."}},"tags":["telegram"],"security":[{"apiKey":[]},{"firebaseAuth":[]}]}},"/v1/agent/telegram/chats/{chatId}":{"delete":{"operationId":"TelegramConnectController_unlinkChat","summary":"Unlink a Telegram chat","description":"Disconnects one linked Telegram chat and clears its channel-link sentinel. 404 if the chat is not linked to this account.","parameters":[{"name":"chatId","required":true,"in":"path","description":"Telegram chat id to unlink.","schema":{"type":"string"}}],"responses":{"200":{"description":"{ ok: true }."}},"tags":["telegram"],"security":[{"apiKey":[]},{"firebaseAuth":[]}]}},"/v1/agent/telegram/connect":{"get":{"operationId":"TelegramConnectController_status","summary":"Telegram connection summary","description":"Whether the account has at least one linked Telegram chat, the bot username, and the linked-chat list.","parameters":[],"responses":{"200":{"description":"{ connected, botUsername, chats: TelegramChatRecord[] }."}},"tags":["telegram"],"security":[{"apiKey":[]},{"firebaseAuth":[]}]}},"/v1/agent/telegram/chats/{chatId}/refresh-profile":{"post":{"operationId":"TelegramConnectController_refreshProfile","summary":"Refresh a Telegram chat profile","description":"Re-fetches the chat's Telegram title/photo via getChat and persists it. 404 if the chat is not owned or the bot token is missing.","parameters":[{"name":"chatId","required":true,"in":"path","description":"Telegram chat id to refresh.","schema":{"type":"string"}}],"responses":{"200":{"description":"The updated TelegramChatRecord."}},"tags":["telegram"],"security":[{"apiKey":[]},{"firebaseAuth":[]}]}},"/v1/agent/slack/events":{"post":{"operationId":"SlackController_events","parameters":[],"responses":{"200":{"description":""}}}},"/v1/agent/slack/status":{"get":{"operationId":"SlackOAuthController_status","summary":"Slack connection status","description":"Whether the account has an installed Slack workspace, plus team/bot metadata and the installer profile.","parameters":[],"responses":{"200":{"description":"{ connected, teamId?, teamName?, botUserId?, installedAtMs?, installerProfile? }."}},"tags":["slack"],"security":[{"apiKey":[]},{"firebaseAuth":[]}]}},"/v1/agent/slack/refresh-profile":{"post":{"operationId":"SlackOAuthController_refreshProfile","summary":"Refresh the Slack installer profile","description":"Re-fetches the installer's Slack display name and avatar via users.info and persists them. 404 when Slack is not connected.","parameters":[],"responses":{"200":{"description":"SlackInstallerProfile — the refreshed installer display name and avatar."}},"tags":["slack"],"security":[{"apiKey":[]},{"firebaseAuth":[]}]}},"/v1/agent/slack":{"delete":{"operationId":"SlackOAuthController_disconnect","summary":"Disconnect the Slack workspace","description":"Deletes the installer's Slack workspace install and clears any channel-link sentinels. Idempotent — succeeds even if nothing was connected.","parameters":[],"responses":{"200":{"description":"{ ok: true }."}},"tags":["slack"],"security":[{"apiKey":[]},{"firebaseAuth":[]}]}},"/v1/agent/slack/install":{"get":{"operationId":"SlackOAuthController_install","summary":"Start the Slack OAuth install","description":"Returns the Slack OAuth v2 authorize URL (with a signed state param) the dashboard redirects the browser to. Starter-and-above. 400 if Slack integration is not configured.","parameters":[],"responses":{"200":{"description":"{ url } — the Slack authorize URL to redirect to."}},"tags":["slack"],"security":[{"apiKey":[]},{"firebaseAuth":[]}]}},"/v1/agent/slack/interactions":{"post":{"operationId":"SlackInteractionsController_interactions","parameters":[],"responses":{"200":{"description":""}}}},"/v1/approval/{token}":{"get":{"operationId":"ApprovalController_getApproval","parameters":[{"name":"token","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":""}}}},"/v1/approval/{token}/decision":{"post":{"operationId":"ApprovalController_submit","parameters":[{"name":"token","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":""}}}},"/v1/agent/connect-start/{token}":{"get":{"operationId":"AgentConnectStartController_start","parameters":[{"name":"token","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":""}}}},"/v1/agent/internal/inbound-slack":{"post":{"operationId":"AgentInternalController_inboundSlack","parameters":[{"name":"x-cloudtasks-secret","required":true,"in":"header","schema":{"type":"string"}}],"responses":{"200":{"description":""}}}},"/v1/agent/internal/commit-workspace":{"post":{"operationId":"AgentInternalController_commitWorkspace","parameters":[{"name":"x-cloudtasks-secret","required":true,"in":"header","schema":{"type":"string"}}],"responses":{"200":{"description":""}}}},"/v1/replication/eligible-profiles":{"get":{"operationId":"ReplicationController_eligibleProfiles","parameters":[{"name":"postId","required":true,"in":"query","schema":{"type":"string"}},{"name":"q","required":true,"in":"query","schema":{"type":"string"}},{"name":"platforms","required":true,"in":"query","schema":{"type":"string"}},{"name":"roles","required":true,"in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":""}}}},"/v1/replication/preview":{"post":{"operationId":"ReplicationController_preview","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateReplicationDto"}}}},"responses":{"201":{"description":""}}}},"/v1/replication":{"post":{"operationId":"ReplicationController_create","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateReplicationDto"}}}},"responses":{"201":{"description":""}}},"get":{"operationId":"ReplicationController_list","parameters":[],"responses":{"200":{"description":""}}}},"/v1/replication/{jobId}":{"get":{"operationId":"ReplicationController_get","parameters":[{"name":"jobId","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":""}}}},"/v1/replication/{jobId}/targets":{"get":{"operationId":"ReplicationController_targets","parameters":[{"name":"jobId","required":true,"in":"path","schema":{"type":"string"}},{"name":"status","required":true,"in":"query","schema":{"type":"string"}},{"name":"cursor","required":true,"in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":""}}}},"/v1/replication/{jobId}/pause":{"post":{"operationId":"ReplicationController_pause","parameters":[{"name":"jobId","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"201":{"description":""}}}},"/v1/replication/{jobId}/resume":{"post":{"operationId":"ReplicationController_resume","parameters":[{"name":"jobId","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"201":{"description":""}}}},"/v1/replication/{jobId}/cancel":{"post":{"operationId":"ReplicationController_cancel","parameters":[{"name":"jobId","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"201":{"description":""}}}},"/v1/replication/{jobId}/retry-failed":{"post":{"operationId":"ReplicationController_retryFailed","parameters":[{"name":"jobId","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"201":{"description":""}}}},"/v1/api-keys/scopes":{"get":{"operationId":"ApiKeysController_listScopes","parameters":[],"responses":{"200":{"description":""}},"tags":["api-keys"],"security":[{"firebaseAuth":[]}]}},"/v1/api-keys":{"post":{"operationId":"ApiKeysController_create","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateApiKeyDto"}}}},"responses":{"201":{"description":""}},"tags":["api-keys"],"security":[{"firebaseAuth":[]}]},"get":{"operationId":"ApiKeysController_list","parameters":[],"responses":{"200":{"description":""}},"tags":["api-keys"],"security":[{"firebaseAuth":[]}]}},"/v1/api-keys/{id}":{"patch":{"operationId":"ApiKeysController_rename","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RenameApiKeyDto"}}}},"responses":{"200":{"description":""}},"tags":["api-keys"],"security":[{"firebaseAuth":[]}]},"delete":{"operationId":"ApiKeysController_revoke","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"tags":["api-keys"],"security":[{"firebaseAuth":[]}]}},"/v1/api-keys/{id}/scopes":{"patch":{"operationId":"ApiKeysController_updateScopes","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateScopesDto"}}}},"responses":{"200":{"description":""}},"tags":["api-keys"],"security":[{"firebaseAuth":[]}]}},"/v1/webhooks":{"post":{"operationId":"WebhooksController_create","summary":"Create a webhook endpoint","description":"Registers an HTTPS subscriber URL for outbound event delivery. Endpoint count is capped by plan tier. Returns the created endpoint including its signing secret.","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateWebhookDto"}}}},"responses":{"200":{"description":"WebhookEndpoint — { id, url, events, active, secret, headers, createdAt, ... }."}},"tags":["webhooks"],"security":[{"apiKey":[]},{"firebaseAuth":[]}]},"get":{"operationId":"WebhooksController_findAll","summary":"List all webhook endpoints","description":"Returns every (non-deleted) webhook endpoint for the authenticated account.","parameters":[],"responses":{"200":{"description":"WebhookEndpoint[]."}},"tags":["webhooks"],"security":[{"apiKey":[]},{"firebaseAuth":[]}]}},"/v1/webhooks/{id}":{"get":{"operationId":"WebhooksController_findOne","summary":"Get a webhook endpoint by ID","description":"Returns one webhook endpoint owned by the caller.","parameters":[{"name":"id","required":true,"in":"path","description":"Webhook endpoint id.","schema":{"type":"string"}}],"responses":{"200":{"description":"WebhookEndpoint."}},"tags":["webhooks"],"security":[{"apiKey":[]},{"firebaseAuth":[]}]},"patch":{"operationId":"WebhooksController_update","summary":"Update a webhook endpoint","description":"Partially updates an endpoint (events, secret, headers, active). The URL is immutable.","parameters":[{"name":"id","required":true,"in":"path","description":"Webhook endpoint id.","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateWebhookDto"}}}},"responses":{"200":{"description":"The updated WebhookEndpoint."}},"tags":["webhooks"],"security":[{"apiKey":[]},{"firebaseAuth":[]}]},"delete":{"operationId":"WebhooksController_remove","summary":"Delete (soft) a webhook endpoint","description":"Soft-deletes the endpoint; it stops receiving deliveries.","parameters":[{"name":"id","required":true,"in":"path","description":"Webhook endpoint id.","schema":{"type":"string"}}],"responses":{"200":{"description":"{ success: true }."}},"tags":["webhooks"],"security":[{"apiKey":[]},{"firebaseAuth":[]}]}},"/v1/webhooks/{id}/regenerate-secret":{"post":{"operationId":"WebhooksController_regenerateSecret","summary":"Regenerate the signing secret for a webhook endpoint","description":"Rotates the HMAC signing secret. Returns the new plaintext secret once — store it immediately.","parameters":[{"name":"id","required":true,"in":"path","description":"Webhook endpoint id.","schema":{"type":"string"}}],"responses":{"200":{"description":"{ secret } — the new signing secret (shown once)."}},"tags":["webhooks"],"security":[{"apiKey":[]},{"firebaseAuth":[]}]}},"/v1/webhooks/{id}/test":{"post":{"operationId":"WebhooksController_testDelivery","summary":"Fire a synthetic test event at the endpoint","description":"Delivers a signed webhook.test event to validate the subscriber URL. Persists a delivery record alongside real events.","parameters":[{"name":"id","required":true,"in":"path","description":"Webhook endpoint id.","schema":{"type":"string"}}],"responses":{"200":{"description":"{ deliveryId, status: delivered|failed|dead|pending }."}},"tags":["webhooks"],"security":[{"apiKey":[]},{"firebaseAuth":[]}]}},"/v1/webhooks/{id}/deliveries":{"get":{"operationId":"WebhooksController_listDeliveries","summary":"List recent deliveries for a webhook endpoint","description":"Cursor-paginated delivery records for one endpoint, newest first.","parameters":[{"name":"id","required":true,"in":"path","description":"Webhook endpoint id.","schema":{"type":"string"}},{"name":"limit","required":false,"in":"query","description":"Page size 1-100 (default 25).","schema":{"type":"string"}},{"name":"cursor","required":false,"in":"query","description":"Pagination cursor from a prior page.","schema":{"type":"string"}}],"responses":{"200":{"description":"{ deliveries: WebhookDeliveryRecord[], nextCursor }."}},"tags":["webhooks"],"security":[{"apiKey":[]},{"firebaseAuth":[]}]}},"/v1/webhooks/{id}/deliveries/{deliveryId}":{"get":{"operationId":"WebhooksController_getDelivery","summary":"Inspect a single webhook delivery (incl. attempts)","description":"Returns one delivery record with its per-attempt history.","parameters":[{"name":"id","required":true,"in":"path","description":"Webhook endpoint id.","schema":{"type":"string"}},{"name":"deliveryId","required":true,"in":"path","description":"Delivery record id.","schema":{"type":"string"}}],"responses":{"200":{"description":"WebhookDeliveryRecord with attempts[]."}},"tags":["webhooks"],"security":[{"apiKey":[]},{"firebaseAuth":[]}]}},"/v1/webhooks/{id}/deliveries/{deliveryId}/replay":{"post":{"operationId":"WebhooksController_replayDelivery","summary":"Force one extra delivery attempt against the same endpoint","description":"Re-sends a past delivery's payload to the endpoint using the current signing context.","parameters":[{"name":"id","required":true,"in":"path","description":"Webhook endpoint id.","schema":{"type":"string"}},{"name":"deliveryId","required":true,"in":"path","description":"Delivery record id to replay.","schema":{"type":"string"}}],"responses":{"200":{"description":"{ status: delivered|failed|dead|pending }."}},"tags":["webhooks"],"security":[{"apiKey":[]},{"firebaseAuth":[]}]}},"/v1/billing/plans":{"get":{"operationId":"BillingController_getPlans","summary":"List available plans + pricing","description":"Public — no auth required. Returns every purchasable plan tier with pricing, limits, and feature flags for the pricing page.","parameters":[],"responses":{"200":{"description":"Plan[] — { tier, name, prices, limits, features }."}},"tags":["billing"],"security":[{"apiKey":[]},{"firebaseAuth":[]}]}},"/v1/billing/status":{"get":{"operationId":"BillingController_getStatus","summary":"Current plan + usage + subscription state","description":"Full billing snapshot for the authenticated user: plan card, trial banner, feature gates, analytics refresh cadence, queue priority. Shape mirrors @letspost/plans BillingStatusResponse.","parameters":[],"responses":{"200":{"description":"BillingStatusResponse — { plan, tier, usage, subscription, trial, features, ... }."}},"tags":["billing"],"security":[{"apiKey":[]},{"firebaseAuth":[]}]}},"/v1/billing/subscription":{"patch":{"operationId":"BillingController_manageSubscription","summary":"Manage subscription (cancel / reactivate / downgrade / coupon / pause)","description":"Single mutation endpoint keyed on body.action. downgrade requires targetPriceId + targetTier; apply_coupon requires couponId; pause accepts months (default 1).","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["action"],"properties":{"action":{"type":"string","enum":["cancel","reactivate","downgrade","apply_coupon","pause"]},"targetPriceId":{"type":"string"},"targetTier":{"type":"string"},"couponId":{"type":"string"},"months":{"type":"number"}}}}}},"responses":{"200":{"description":"Action-dependent: cancel/downgrade/pause return the subscription state; reactivate → { reactivated: true }; apply_coupon → { applied: true }."}},"tags":["billing"],"security":[{"apiKey":[]},{"firebaseAuth":[]}]}},"/v1/billing/feedback":{"post":{"operationId":"BillingController_storeFeedback","summary":"Store cancellation / downgrade feedback","description":"Persists the exit-survey answers shown during a cancel or downgrade flow. reason, offerAccepted, finalAction and planBefore are required. Returns 204.","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["reason","offerAccepted","finalAction","planBefore"],"properties":{"reason":{"type":"string"},"reasonDetail":{"type":"string"},"offerShown":{"type":["string","null"]},"offerAccepted":{"type":"boolean"},"finalAction":{"type":"string"},"planBefore":{"type":"string"},"planAfter":{"type":"string"}}}}}},"responses":{"204":{"description":"Feedback stored. No body."}},"tags":["billing"],"security":[{"apiKey":[]},{"firebaseAuth":[]}]}},"/v1/billing/checkout":{"post":{"operationId":"BillingController_createCheckout","summary":"Create a Stripe Checkout session","description":"Starts a hosted Stripe Checkout for the given tier. interval defaults to month. Returns the Checkout URL to redirect the browser to.","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["tier"],"properties":{"tier":{"type":"string"},"interval":{"type":"string","enum":["month","year"]}}}}}},"responses":{"200":{"description":"{ url } — Stripe Checkout session URL."}},"tags":["billing"],"security":[{"apiKey":[]},{"firebaseAuth":[]}]}},"/v1/billing/portal":{"post":{"operationId":"BillingController_createPortal","summary":"Create a Stripe billing portal session","description":"Mints a Stripe customer-portal link where the user can manage payment methods and invoices. Returns the portal URL.","parameters":[],"responses":{"200":{"description":"{ url } — Stripe billing portal URL."}},"tags":["billing"],"security":[{"apiKey":[]},{"firebaseAuth":[]}]}},"/v1/billing/webhook":{"post":{"operationId":"BillingController_handleWebhook","summary":"Stripe webhook handler","description":"Public — authenticated by the Stripe-Signature header (HMAC), not a bearer token. Consumes platform subscription events and applies them idempotently.","parameters":[],"responses":{"200":{"description":"{ received: true } once the event is processed."}},"tags":["billing"],"security":[{"apiKey":[]},{"firebaseAuth":[]}]}},"/v1/billing/connect/webhook":{"post":{"operationId":"BillingController_handleConnectWebhook","summary":"Stripe Connect webhook handler","description":"Public — verified with STRIPE_CONNECT_WEBHOOK_SECRET (falls back to the platform secret). Routes Connect events through the same idempotent handler.","parameters":[],"responses":{"200":{"description":"{ received: true } once the event is processed."}},"tags":["billing"],"security":[{"apiKey":[]},{"firebaseAuth":[]}]}},"/v1/mcp":{"post":{"operationId":"McpServerController_handle","parameters":[],"responses":{"200":{"description":""}}}},"/v1/platforms/whatsapp/link-code":{"post":{"operationId":"WhatsAppController_generateLinkCode","summary":"Mint an 8-digit WhatsApp link code","description":"Generates a fresh single-use 8-digit code the user texts to the bot number to bind their phone. Pro-and-above. Returns 409 if the account is already linked (unlink first).","parameters":[],"responses":{"200":{"description":"{ code, expiresAtMs } — the 8-digit code and its expiry."}},"tags":["whatsapp"],"security":[{"apiKey":[]},{"firebaseAuth":[]}]}},"/v1/platforms/whatsapp/refresh-profile":{"post":{"operationId":"WhatsAppController_refreshProfile","summary":"Refresh the linked WhatsApp profile","description":"Re-fetches the linked number's WhatsApp display name and picture from the gateway and persists them.","parameters":[],"responses":{"200":{"description":"{ success, profileName, profilePictureUrl }."}},"tags":["whatsapp"],"security":[{"apiKey":[]},{"firebaseAuth":[]}]}},"/v1/platforms/whatsapp/status":{"get":{"operationId":"WhatsAppController_getStatus","summary":"WhatsApp connection status","description":"Whether the account has a linked WhatsApp number, plus the bot number and linked-profile metadata.","parameters":[],"responses":{"200":{"description":"{ linked, phone, botPhoneNumber, linkedAtMs, connectorName, profileName, profilePictureUrl }."}},"tags":["whatsapp"],"security":[{"apiKey":[]},{"firebaseAuth":[]}]}},"/v1/platforms/whatsapp/link":{"delete":{"operationId":"WhatsAppController_unlink","summary":"Unlink the WhatsApp number","description":"Disconnects the account's linked WhatsApp number. Returns the phone that was unlinked (if any).","parameters":[],"responses":{"200":{"description":"{ success, phone } — the unlinked phone, or null if none was linked."}},"tags":["whatsapp"],"security":[{"apiKey":[]},{"firebaseAuth":[]}]}},"/v1/platforms/whatsapp/settings":{"get":{"operationId":"WhatsAppController_getSettings","summary":"Get agent settings for a channel","description":"Assistant settings (enabled flag, capabilities, reply style, tone, notification prefs, conversation timeout, voice, confirm-before-publish) for the given chat channel. Defaults to whatsapp.","parameters":[{"name":"channel","required":false,"in":"query","description":"Chat channel to read. Default: whatsapp.","schema":{"enum":["whatsapp","telegram","slack"],"type":"string"}}],"responses":{"200":{"description":"WhatsAppAgentSettings for the channel."}},"tags":["whatsapp"],"security":[{"apiKey":[]},{"firebaseAuth":[]}]},"put":{"operationId":"WhatsAppController_putSettings","summary":"Update agent settings for a channel","description":"Partial update of the assistant settings for the given channel. Only the supplied fields are changed; invalid values fall back to the prior/default rather than erroring. Returns the full updated settings.","parameters":[{"name":"channel","required":false,"in":"query","description":"Chat channel to update. Default: whatsapp.","schema":{"enum":["whatsapp","telegram","slack"],"type":"string"}}],"responses":{"200":{"description":"The updated WhatsAppAgentSettings."}},"tags":["whatsapp"],"security":[{"apiKey":[]},{"firebaseAuth":[]}]}},"/v1/platforms/whatsapp/voice-preview":{"get":{"operationId":"WhatsAppController_voicePreview","summary":"Preview a voice sample","description":"Synthesizes a short base64 MP3 sample of the given voice so the picker can play it before committing. Empty/omitted voice previews the locale default. Returns { audio: null } when TTS is unavailable.","parameters":[{"name":"voice","required":false,"in":"query","description":"Voice id to preview. Omit for the locale default.","schema":{"type":"string"}}],"responses":{"200":{"description":"{ audio: base64 mp3 | null, mime }."}},"tags":["whatsapp"],"security":[{"apiKey":[]},{"firebaseAuth":[]}]}},"/v1/platforms/whatsapp/events":{"get":{"operationId":"WhatsAppController_getEvents","summary":"Paginated WhatsApp event history","description":"Time-ordered assistant event history for the linked number, paginated by a millisecond cursor.","parameters":[{"name":"limit","required":false,"in":"query","description":"Page size.","schema":{"type":"string"}},{"name":"cursor","required":false,"in":"query","description":"Millisecond cursor from the previous page.","schema":{"type":"string"}}],"responses":{"200":{"description":"A page of events plus the next cursor."}},"tags":["whatsapp"],"security":[{"apiKey":[]},{"firebaseAuth":[]}]}},"/v1/platforms/whatsapp/test-message":{"post":{"operationId":"WhatsAppController_sendTestMessage","summary":"Send a test WhatsApp message","description":"Sends an arbitrary text message from the linked number to a target E.164 phone. 400 on NOT_LINKED / INVALID_TARGET / EMPTY_BODY.","parameters":[],"responses":{"200":{"description":"{ success, to } — the E.164 target the message was sent to."}},"tags":["whatsapp"],"security":[{"apiKey":[]},{"firebaseAuth":[]}]}},"/v1/platforms/whatsapp/test-notification":{"post":{"operationId":"WhatsAppController_sendTestNotification","summary":"Send a sample post-published notification","description":"Sends a sample \"post published\" DM on the given channel so the user can preview the notification card. 400 with code NOT_LINKED when the channel is not connected.","parameters":[],"responses":{"200":{"description":"{ success, to, channel }."}},"tags":["whatsapp"],"security":[{"apiKey":[]},{"firebaseAuth":[]}]}},"/v1/team":{"get":{"operationId":"TeamController_listMembers","summary":"List workspace roster (owner + members)","description":"Pro+ only. Returns the owner row followed by every team member. A member sees their org owner's roster (resolved via orgOwnerId).","parameters":[],"responses":{"200":{"description":"TeamMember[] — { uid, email, displayName, role, profileIds, status } rows; index 0 is the owner."}},"tags":["team"],"security":[{"apiKey":[]},{"firebaseAuth":[]}]}},"/v1/team/invite":{"post":{"operationId":"TeamController_inviteMember","summary":"Invite a member by email","description":"Pro+ only, owner/admin only. Sends an invite email and creates a pending invite scoped to the given profiles (or all). Body: InviteMemberDto.","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/InviteMemberDto"}}}},"responses":{"201":{"description":"The created pending invite record (token, email, role, profileIds, expiresAt)."}},"tags":["team"],"security":[{"apiKey":[]},{"firebaseAuth":[]}]}},"/v1/team/invite/{token}":{"get":{"operationId":"TeamController_lookupInvite","summary":"Look up an invite by token","description":"Public — no auth required; the token is the authorization. Resolves invite details so the accept landing can render the inviter and role.","parameters":[{"name":"token","required":true,"in":"path","description":"Opaque invite token from the invite email link.","schema":{"type":"string"}}],"responses":{"200":{"description":"Invite summary — { email, role, inviterName, workspaceName, status, expiresAt }."}},"tags":["team"],"security":[{"apiKey":[]},{"firebaseAuth":[]}]}},"/v1/team/invite/{token}/accept":{"post":{"operationId":"TeamController_acceptInvite","summary":"Accept an invite","description":"Requires auth as the invitee. Consumes the token and joins the authenticated user to the workspace with the invited role.","parameters":[{"name":"token","required":true,"in":"path","description":"Opaque invite token from the invite email link.","schema":{"type":"string"}}],"responses":{"201":{"description":"The membership record created for the accepting user."}},"tags":["team"],"security":[{"apiKey":[]},{"firebaseAuth":[]}]}},"/v1/team/{id}":{"patch":{"operationId":"TeamController_updateRole","summary":"Change a member's role","description":"Pro+ only, owner/admin only. Updates the target member's role. Body: UpdateRoleDto.","parameters":[{"name":"id","required":true,"in":"path","description":"Member id (uid) to update.","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateRoleDto"}}}},"responses":{"200":{"description":"The updated membership record."}},"tags":["team"],"security":[{"apiKey":[]},{"firebaseAuth":[]}]},"delete":{"operationId":"TeamController_removeMember","summary":"Remove a member","description":"Pro+ only, owner/admin only. Revokes the target member's workspace access. Returns 204.","parameters":[{"name":"id","required":true,"in":"path","description":"Member id (uid) to remove.","schema":{"type":"string"}}],"responses":{"204":{"description":"Member removed. No body."}},"tags":["team"],"security":[{"apiKey":[]},{"firebaseAuth":[]}]}},"/v1/team/policies/mfa":{"get":{"operationId":"TeamController_getMfaPolicy","summary":"Read workspace MFA enforcement policy","description":"Any plan. Owners read their own policy; members get the owner's value. `visible` reflects whether the toggle should render.","parameters":[],"responses":{"200":{"description":"{ enforced: boolean, visible: boolean }."}},"tags":["team"],"security":[{"apiKey":[]},{"firebaseAuth":[]}]},"patch":{"operationId":"TeamController_setMfaPolicy","summary":"Set workspace MFA enforcement policy","description":"Pro+ only, owner-only toggle. enforced=true requires all members to have MFA enabled.","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["enforced"],"properties":{"enforced":{"type":"boolean"}}}}}},"responses":{"200":{"description":"{ enforced: boolean, visible: boolean } — the updated policy."}},"tags":["team"],"security":[{"apiKey":[]},{"firebaseAuth":[]}]}},"/v1/support":{"post":{"operationId":"SupportController_create","summary":"Open a support ticket","description":"Creates a support ticket for the authenticated user. Set isPublic to surface it as a public suggestion. Optionally records the x-client-version header.","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateTicketDto"}}}},"responses":{"200":{"description":"The created ticket (serialized): { id, subject, category, severity, status, description, isPublic, createdAt, ... }."}},"tags":["support"],"security":[{"apiKey":[]},{"firebaseAuth":[]}]},"get":{"operationId":"SupportController_list","summary":"List support tickets","description":"Returns the caller's tickets (all tickets for admin/support-tier users), newest first, cursor-paginated.","parameters":[{"name":"limit","required":false,"in":"query","description":"Page size (default 25).","schema":{"type":"string"}},{"name":"cursor","required":false,"in":"query","description":"Opaque pagination cursor from a prior page.","schema":{"type":"string"}}],"responses":{"200":{"description":"{ tickets: Ticket[], pagination: { hasMore, nextCursor, count } }."}},"tags":["support"],"security":[{"apiKey":[]},{"firebaseAuth":[]}]}},"/v1/support/suggestions":{"get":{"operationId":"SupportController_listSuggestions","summary":"List public suggestions (authenticated)","description":"Public feature-suggestion tickets ranked by votes. Same data as the unauthenticated GET /v1/support/suggestions route.","parameters":[],"responses":{"200":{"description":"Suggestion[] — public tickets with vote counts."}},"tags":["support"],"security":[{"apiKey":[]},{"firebaseAuth":[]}]}},"/v1/support/{id}":{"get":{"operationId":"SupportController_getOne","summary":"Get one ticket (with messages)","description":"Returns a single ticket and its message thread. Non-admins can only read their own tickets.","parameters":[{"name":"id","required":true,"in":"path","description":"Ticket id.","schema":{"type":"string"}}],"responses":{"200":{"description":"Ticket with messages[]."}},"tags":["support"],"security":[{"apiKey":[]},{"firebaseAuth":[]}]},"delete":{"operationId":"SupportController_deleteTicket","summary":"Delete a ticket","description":"Deletes a ticket owned by the caller.","parameters":[{"name":"id","required":true,"in":"path","description":"Ticket id.","schema":{"type":"string"}}],"responses":{"200":{"description":"No content on success."}},"tags":["support"],"security":[{"apiKey":[]},{"firebaseAuth":[]}]}},"/v1/support/{id}/messages":{"post":{"operationId":"SupportController_addMessage","summary":"Post a message to a ticket","description":"Appends a message to the ticket thread. Admin/support replies are recorded to the audit log.","parameters":[{"name":"id","required":true,"in":"path","description":"Ticket id.","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AddMessageDto"}}}},"responses":{"200":{"description":"The updated ticket (or created message record)."}},"tags":["support"],"security":[{"apiKey":[]},{"firebaseAuth":[]}]}},"/v1/support/{id}/status":{"patch":{"operationId":"SupportController_updateStatus","summary":"Change ticket status","description":"Updates the ticket status. Admin/support changes are recorded to the audit log.","parameters":[{"name":"id","required":true,"in":"path","description":"Ticket id.","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateStatusDto"}}}},"responses":{"200":{"description":"The updated ticket."}},"tags":["support"],"security":[{"apiKey":[]},{"firebaseAuth":[]}]}},"/v1/support/{id}/vote":{"post":{"operationId":"SupportController_vote","summary":"Upvote a public suggestion","description":"Adds the caller's vote to a public suggestion ticket.","parameters":[{"name":"id","required":true,"in":"path","description":"Suggestion ticket id.","schema":{"type":"string"}}],"responses":{"200":{"description":"Updated vote state / count."}},"tags":["support"],"security":[{"apiKey":[]},{"firebaseAuth":[]}]},"delete":{"operationId":"SupportController_unvote","summary":"Remove an upvote from a suggestion","description":"Removes the caller's vote from a public suggestion ticket.","parameters":[{"name":"id","required":true,"in":"path","description":"Suggestion ticket id.","schema":{"type":"string"}}],"responses":{"200":{"description":"Updated vote state / count."}},"tags":["support"],"security":[{"apiKey":[]},{"firebaseAuth":[]}]}},"/v1/status/components":{"get":{"operationId":"StatusController_getComponents","summary":"Get all status components + overall status","description":"Public status-page data: every tracked component with its current status, plus the rolled-up overall status. No auth required.","parameters":[],"responses":{"200":{"description":"{ components: StatusComponent[], overallStatus: ComponentStatus }."}},"tags":["status"]}},"/v1/status/incidents/active":{"get":{"operationId":"StatusController_getActiveIncidents","summary":"List active incidents","description":"Currently open (unresolved) incidents. Public, no auth.","parameters":[],"responses":{"200":{"description":"StatusIncident[] — active incidents."}},"tags":["status"]}},"/v1/status/incidents/recent":{"get":{"operationId":"StatusController_getRecentIncidents","summary":"List recent incidents","description":"Recently resolved/past incidents for the status-page history. Public, no auth.","parameters":[],"responses":{"200":{"description":"StatusIncident[] — recent incidents."}},"tags":["status"]}},"/v1/status/uptime/{componentId}":{"get":{"operationId":"StatusController_getUptime","summary":"Daily uptime series for a component","description":"Per-day uptime percentage history for one component. Public, no auth.","parameters":[{"name":"componentId","required":true,"in":"path","description":"Component id (slug).","schema":{"type":"string"}}],"responses":{"200":{"description":"{ date, uptimePercent }[]."}},"tags":["status"]}},"/v1/status/subscribe":{"post":{"operationId":"StatusController_subscribe","summary":"Subscribe to status updates","description":"Registers an email for status notifications (optionally scoped to specific components). Sends a confirmation email. Public, no auth.","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SubscribeDto"}}}},"responses":{"200":{"description":"No content on success (confirmation email sent)."}},"tags":["status"]}},"/v1/status/subscribe/confirm/{token}":{"get":{"operationId":"StatusController_confirmSubscription","summary":"Confirm a status subscription","description":"Confirms an email subscription from the emailed token, then 302-redirects to the status page. Public, no auth.","parameters":[{"name":"token","required":true,"in":"path","description":"Confirmation token from the subscription email.","schema":{"type":"string"}}],"responses":{"200":{"description":"302 redirect to the status page."}},"tags":["status"]}},"/v1/status/unsubscribe/{token}":{"get":{"operationId":"StatusController_unsubscribe","summary":"Unsubscribe from status updates","description":"Removes an email subscription from the emailed token, then 302-redirects to the status page. Public, no auth.","parameters":[{"name":"token","required":true,"in":"path","description":"Unsubscribe token from the notification email.","schema":{"type":"string"}}],"responses":{"200":{"description":"302 redirect to the status page."}},"tags":["status"]}},"/v1/status/admin/backfill-uptime":{"post":{"operationId":"StatusController_backfillUptime","summary":"Backfill uptime history (admin)","description":"Recomputes the last 90 days of per-component uptime. Requires auth.","parameters":[],"responses":{"200":{"description":"No content on success."}},"tags":["status"],"security":[{"apiKey":[]},{"firebaseAuth":[]}]}},"/v1/status/admin/components":{"post":{"operationId":"StatusController_upsertComponent","summary":"Create or update a status component (admin)","description":"Upserts a component by id. Requires auth.","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpsertComponentDto"}}}},"responses":{"200":{"description":"No content on success."}},"tags":["status"],"security":[{"apiKey":[]},{"firebaseAuth":[]}]}},"/v1/status/admin/incidents":{"post":{"operationId":"StatusController_createIncident","summary":"Create an incident (admin)","description":"Opens a new incident with a first timeline message. Requires auth.","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateIncidentDto"}}}},"responses":{"200":{"description":"The new incident id (string)."}},"tags":["status"],"security":[{"apiKey":[]},{"firebaseAuth":[]}]}},"/v1/status/admin/incidents/{id}/updates":{"post":{"operationId":"StatusController_addIncidentUpdate","summary":"Post an incident update (admin)","description":"Appends a status update to an incident timeline. Requires auth.","parameters":[{"name":"id","required":true,"in":"path","description":"Incident id.","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AddIncidentUpdateDto"}}}},"responses":{"200":{"description":"No content on success."}},"tags":["status"],"security":[{"apiKey":[]},{"firebaseAuth":[]}]}},"/v1/status/admin/components/{id}/status":{"patch":{"operationId":"StatusController_setComponentStatus","summary":"Set a component's status (admin)","description":"Sets the current status of one component. Body: { status }. Requires auth.","parameters":[{"name":"id","required":true,"in":"path","description":"Component id.","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","enum":["operational","degraded_performance","partial_outage","major_outage","under_maintenance"]}}}}}},"responses":{"200":{"description":"No content on success."}},"tags":["status"],"security":[{"apiKey":[]},{"firebaseAuth":[]}]}},"/v1/healthz/api":{"get":{"operationId":"HealthzController_checkApi","parameters":[],"responses":{"200":{"description":""}}}},"/v1/healthz/firestore":{"get":{"operationId":"HealthzController_checkFirestore","parameters":[],"responses":{"200":{"description":""}}}},"/v1/health":{"get":{"operationId":"HealthController_health","parameters":[],"responses":{"200":{"description":""}}}},"/v1/platform-invites":{"post":{"operationId":"PlatformInvitesController_create","summary":"Create a platform-connect invite link","description":"Generates a shareable, tokenized invite that lets a third party connect a social account to the caller's profile via OAuth. Rate-limited to 10/min. Body: CreateInviteDto.","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateInviteDto"}}}},"responses":{"201":{"description":"The created invite — { id, token, url, profileId, platform, expiresAt, maxUses, usedCount }."}},"tags":["platform-invites"],"security":[{"apiKey":[]},{"firebaseAuth":[]}]},"get":{"operationId":"PlatformInvitesController_list","summary":"List the caller's platform-connect invites","description":"Returns every invite created by the authenticated user with its live usage/expiry state.","parameters":[],"responses":{"200":{"description":"Invite[] — { id, token, url, profileId, platform, expiresAt, maxUses, usedCount, status }."}},"tags":["platform-invites"],"security":[{"apiKey":[]},{"firebaseAuth":[]}]}},"/v1/platform-invites/{id}":{"delete":{"operationId":"PlatformInvitesController_revoke","summary":"Revoke a platform-connect invite","description":"Immediately invalidates the invite so its link can no longer be used. Returns 204.","parameters":[{"name":"id","required":true,"in":"path","description":"Invite id to revoke.","schema":{"type":"string"}}],"responses":{"204":{"description":"Invite revoked. No body."}},"tags":["platform-invites"],"security":[{"apiKey":[]},{"firebaseAuth":[]}]}},"/v1/platform-invites/{token}/validate":{"get":{"operationId":"PlatformInvitesController_validate","summary":"Validate an invite token","description":"Public — the token itself is the authorization. Checks whether the invite is still usable and returns the profile/platform it targets so the landing page can render.","parameters":[{"name":"token","required":true,"in":"path","description":"Invite token from the shared link.","schema":{"type":"string"}}],"responses":{"200":{"description":"{ valid, profileId, platform, profileName?, reason? } — reason set when invalid (expired/revoked/used)."}},"tags":["platform-invites"],"security":[{"apiKey":[]},{"firebaseAuth":[]}]}},"/v1/platform-invites/{token}/connect":{"get":{"operationId":"PlatformInvitesController_connect","summary":"Redirect an invitee into the OAuth connect flow","description":"Public — the token is the authorization. 302-redirects to the platform OAuth URL for a valid invite; on any failure (expired/revoked/used) bounces to the branded invite landing.","parameters":[{"name":"token","required":true,"in":"path","description":"Invite token from the shared link.","schema":{"type":"string"}}],"responses":{"200":{"description":"302 redirect (to the platform OAuth URL, or the invite landing on failure). No JSON body."}},"tags":["platform-invites"],"security":[{"apiKey":[]},{"firebaseAuth":[]}]}},"/v1/webhooks/resend/inbound":{"post":{"operationId":"InboundEmailController_handle","summary":"Resend inbound email webhook","parameters":[],"responses":{"200":{"description":""}},"tags":["webhooks"]}},"/oauth/redirect":{"get":{"operationId":"OAuthRedirectController_redirect","parameters":[{"name":"url","required":true,"in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":""}}}},"/v1/oauth/register":{"post":{"operationId":"OAuthApiController_register","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RegisterClientDto"}}}},"responses":{"201":{"description":""}}}},"/v1/oauth/consent":{"post":{"operationId":"OAuthApiController_consent","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConsentDto"}}}},"responses":{"200":{"description":""}}}},"/v1/oauth/token":{"post":{"operationId":"OAuthApiController_token","parameters":[],"responses":{"200":{"description":""}}}},"/v1/oauth/clients/{clientId}":{"get":{"operationId":"OAuthApiController_getClient","parameters":[{"name":"clientId","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":""}}}},"/tiktokr1RJ2OcsbqsbEFmjpmL2UHpXqREzOf3e.txt":{"get":{"operationId":"TikTokVerifyController_serveDomainVerificationLegacy","parameters":[],"responses":{"200":{"description":""}}}},"/tiktokpkv8GeIlNQN9nBx7WOuhDcCOlt58cSgw.txt":{"get":{"operationId":"TikTokVerifyController_serveDomainVerification","parameters":[],"responses":{"200":{"description":""}}}},"/v1/platforms/tiktok/callback/tiktokgpsW5GQiRnv5K3gjaN87CQUPsQfffcdG.txt":{"get":{"operationId":"TikTokVerifyController_serveCallbackVerification","parameters":[],"responses":{"200":{"description":""}}}},"/v1/scheduled-jobs":{"get":{"operationId":"ScheduledJobsController_list","parameters":[{"name":"profileId","required":true,"in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"tags":["scheduled-jobs"],"security":[{"firebaseAuth":[]}]},"post":{"operationId":"ScheduledJobsController_create","parameters":[],"responses":{"201":{"description":""}},"tags":["scheduled-jobs"],"security":[{"firebaseAuth":[]}]}},"/v1/scheduled-jobs/{id}":{"patch":{"operationId":"ScheduledJobsController_update","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"tags":["scheduled-jobs"],"security":[{"firebaseAuth":[]}]},"delete":{"operationId":"ScheduledJobsController_remove","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"204":{"description":""}},"tags":["scheduled-jobs"],"security":[{"firebaseAuth":[]}]}},"/v1/scheduled-jobs/{id}/run":{"post":{"operationId":"ScheduledJobsController_runNow","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"tags":["scheduled-jobs"],"security":[{"firebaseAuth":[]}]}},"/v1/scheduled-jobs/_fire":{"post":{"operationId":"ScheduledJobsFireController_fire","parameters":[{"name":"x-cloudtasks-secret","required":true,"in":"header","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"tags":["scheduled-jobs"]}},"/i/{slug}":{"get":{"operationId":"ShortLinksController_resolve","parameters":[{"name":"slug","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":""}}}},"/v1/shortlinks/{slug}/meta":{"get":{"operationId":"ShortLinksMetaController_meta","parameters":[{"name":"slug","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":""}}}},"/v1/action-logs":{"get":{"operationId":"ActionLogController_list","summary":"List action-log entries","description":"Verbatim external-API calls, tool invocations, OAuth round-trips and system events for the authenticated account, newest first. Cursor-paginated on createdAt ms; supports kind / target / correlationId / free-text / time-window filtering (applied in-memory).","parameters":[{"name":"limit","required":false,"in":"query","description":"Page size 1-200 (default 50).","schema":{"type":"string"}},{"name":"cursorMs","required":false,"in":"query","description":"createdAt ms cursor from a prior page (nextCursorMs).","schema":{"type":"string"}},{"name":"kind","required":false,"in":"query","description":"Filter by entry kind. Unknown values ignored.","schema":{"enum":["external_api_call","internal_tool","wa_inbound","wa_outbound","oauth_callback","post_publish","system_event"],"type":"string"}},{"name":"target","required":false,"in":"query","description":"Substring match on the entry target.","schema":{"type":"string"}},{"name":"correlationId","required":false,"in":"query","description":"Exact correlation id match.","schema":{"type":"string"}},{"name":"q","required":false,"in":"query","description":"Free-text over operation + correlationId + target.","schema":{"type":"string"}},{"name":"sinceMs","required":false,"in":"query","description":"Lower bound on createdAt (epoch ms).","schema":{"type":"string"}},{"name":"untilMs","required":false,"in":"query","description":"Upper bound on createdAt (epoch ms).","schema":{"type":"string"}}],"responses":{"200":{"description":"{ entries: ActionLogEntry[], nextCursorMs: number | null }."}},"tags":["action-logs"],"security":[{"apiKey":[]},{"firebaseAuth":[]}]}},"/v1/partner/by-code/{code}":{"get":{"operationId":"PartnerController_byCode","summary":"Resolve a referral code to a partner name","description":"Public — no auth required. Returns only the partner display name for the \"Affiliated with X\" banner. 404 for unknown or inactive codes.","parameters":[{"name":"code","required":true,"in":"path","description":"Partner referral code from the referral link.","schema":{"type":"string"}}],"responses":{"200":{"description":"{ name } — partner display name."}},"tags":["partner"],"security":[{"apiKey":[]},{"firebaseAuth":[]}]}},"/v1/partner/me":{"get":{"operationId":"PartnerController_getMe","summary":"Partner dashboard bundle","description":"Full partner dashboard for the current user (auto-enrolls Pro+). 403 unless the user has an ACTIVE partner account. Stripe ids / raw amounts are never returned — only derived commission figures, and referral emails are masked.","parameters":[],"responses":{"200":{"description":"{ partner, connect, notifications, minimumPayout, stats, referrals[], payouts[] }."}},"tags":["partner"],"security":[{"apiKey":[]},{"firebaseAuth":[]}]}},"/v1/partner/notifications":{"patch":{"operationId":"PartnerController_updateNotifications","summary":"Update partner notification preferences","description":"Patches the current partner's email notification prefs. Only known boolean keys are accepted. 403 unless an active partner.","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":{"type":"boolean"},"description":"Partial map of PartnerNotificationPrefs boolean flags."}}}},"responses":{"200":{"description":"PartnerNotificationPrefs — the full merged preference map."}},"tags":["partner"],"security":[{"apiKey":[]},{"firebaseAuth":[]}]}},"/v1/partner/payout-cadence":{"patch":{"operationId":"PartnerController_updatePayoutCadence","summary":"Set partner payout cadence","description":"Sets the current partner's payout schedule. 403 unless an active partner.","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"cadence":{"type":"string","enum":["weekly","monthly"]}}}}}},"responses":{"200":{"description":"{ payoutCadence } — the persisted cadence."}},"tags":["partner"],"security":[{"apiKey":[]},{"firebaseAuth":[]}]}},"/v1/partner/apply":{"post":{"operationId":"PartnerController_apply","summary":"Apply to the Partner Program","description":"Any signed-in user can apply. Stores the application and emails partner@letspost.it for manual review. All fields optional.","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"instagram":{"type":"string"},"tiktok":{"type":"string"},"youtube":{"type":"string"},"website":{"type":"string"},"description":{"type":"string"}}}}}},"responses":{"200":{"description":"{ submitted: true }."}},"tags":["partner"],"security":[{"apiKey":[]},{"firebaseAuth":[]}]}},"/v1/partner/connect/onboard":{"post":{"operationId":"PartnerController_connectOnboard","summary":"Start / resume Stripe Connect onboarding","description":"Creates or resumes the partner's Stripe Connect Express account and returns a Stripe-hosted onboarding link. 403 unless an active partner.","parameters":[],"responses":{"200":{"description":"{ url } — Stripe onboarding link."}},"tags":["partner"],"security":[{"apiKey":[]},{"firebaseAuth":[]}]}},"/v1/partner/connect/refresh":{"post":{"operationId":"PartnerController_connectRefresh","summary":"Refresh Stripe Connect verification state","description":"Pulls the live Connect account state from Stripe and mirrors it onto the partner. Called when the partner returns from Stripe onboarding.","parameters":[],"responses":{"200":{"description":"{ hasAccount, detailsSubmitted, chargesEnabled, payoutsEnabled, payoutReady }."}},"tags":["partner"],"security":[{"apiKey":[]},{"firebaseAuth":[]}]}},"/v1/partner/connect/login-link":{"post":{"operationId":"PartnerController_connectLoginLink","summary":"Mint a Stripe Express dashboard login link","description":"Returns a link to the partner's Stripe Express dashboard. Only succeeds for a payout-verified account.","parameters":[],"responses":{"200":{"description":"{ url } — Stripe Express dashboard login link."}},"tags":["partner"],"security":[{"apiKey":[]},{"firebaseAuth":[]}]}},"/v1/admin/partners":{"get":{"operationId":"PartnerAdminController_list","parameters":[],"responses":{"200":{"description":""}},"tags":["admin-partners"],"security":[{"firebaseAuth":[]}]},"post":{"operationId":"PartnerAdminController_enable","parameters":[],"responses":{"201":{"description":""}},"tags":["admin-partners"],"security":[{"firebaseAuth":[]}]}},"/v1/admin/partners/{userId}":{"patch":{"operationId":"PartnerAdminController_update","parameters":[{"name":"userId","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"tags":["admin-partners"],"security":[{"firebaseAuth":[]}]}},"/v1/admin/partners/{userId}/referrals":{"get":{"operationId":"PartnerAdminController_referrals","parameters":[{"name":"userId","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"tags":["admin-partners"],"security":[{"firebaseAuth":[]}]}},"/v1/admin/partners/{userId}/ledger":{"get":{"operationId":"PartnerAdminController_ledger","parameters":[{"name":"userId","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"tags":["admin-partners"],"security":[{"firebaseAuth":[]}]}},"/v1/admin/partners/{userId}/payouts":{"get":{"operationId":"PartnerAdminController_payouts","parameters":[{"name":"userId","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"tags":["admin-partners"],"security":[{"firebaseAuth":[]}]},"post":{"operationId":"PartnerAdminController_payout","parameters":[{"name":"userId","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"201":{"description":""}},"tags":["admin-partners"],"security":[{"firebaseAuth":[]}]}},"/v1/admin/partners/{userId}/stats":{"get":{"operationId":"PartnerAdminController_stats","parameters":[{"name":"userId","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"tags":["admin-partners"],"security":[{"firebaseAuth":[]}]}},"/v1/admin/partners/{userId}/connect":{"get":{"operationId":"PartnerAdminController_connectStatus","parameters":[{"name":"userId","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"tags":["admin-partners"],"security":[{"firebaseAuth":[]}]}},"/v1/admin/partners/{userId}/payout-mode":{"patch":{"operationId":"PartnerAdminController_setPayoutMode","parameters":[{"name":"userId","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"tags":["admin-partners"],"security":[{"firebaseAuth":[]}]}},"/v1/admin/partners/{userId}/connect/payout":{"post":{"operationId":"PartnerAdminController_connectPayout","parameters":[{"name":"userId","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"201":{"description":""}},"tags":["admin-partners"],"security":[{"firebaseAuth":[]}]}},"/v1/admin/partners/{userId}/approve":{"post":{"operationId":"PartnerAdminController_approve","parameters":[{"name":"userId","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"201":{"description":""}},"tags":["admin-partners"],"security":[{"firebaseAuth":[]}]}},"/v1/messaging/instagram/webhook":{"get":{"operationId":"MessagingController_verifyWebhook","parameters":[{"name":"hub.mode","required":true,"in":"query","schema":{"type":"string"}},{"name":"hub.verify_token","required":true,"in":"query","schema":{"type":"string"}},{"name":"hub.challenge","required":true,"in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"tags":["messaging"]},"post":{"operationId":"MessagingController_receiveWebhook","parameters":[{"name":"x-hub-signature-256","required":true,"in":"header","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"tags":["messaging"]}},"/v1/messaging/conversations":{"get":{"operationId":"MessagingController_listConversations","summary":"List Instagram DM conversations","parameters":[{"name":"profileId","required":true,"in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"tags":["messaging"],"security":[{"bearer":[]}]}},"/v1/messaging/conversations/{igsid}/messages":{"get":{"operationId":"MessagingController_listMessages","summary":"List messages in a conversation","parameters":[{"name":"igsid","required":true,"in":"path","schema":{"type":"string"}},{"name":"profileId","required":true,"in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"tags":["messaging"],"security":[{"bearer":[]}]}},"/v1/messaging/conversations/{igsid}/reply":{"post":{"operationId":"MessagingController_reply","summary":"Send a manual DM reply (24h window enforced)","parameters":[{"name":"igsid","required":true,"in":"path","schema":{"type":"string"}},{"name":"profileId","required":true,"in":"query","schema":{"type":"string"}}],"responses":{"201":{"description":""}},"tags":["messaging"],"security":[{"bearer":[]}]}},"/v1/messaging/automations":{"get":{"operationId":"MessagingController_listAutomations","summary":"List DM automations","parameters":[{"name":"profileId","required":true,"in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"tags":["messaging"],"security":[{"bearer":[]}]},"post":{"operationId":"MessagingController_createAutomation","summary":"Create a DM automation","parameters":[{"name":"profileId","required":true,"in":"query","schema":{"type":"string"}}],"responses":{"201":{"description":""}},"tags":["messaging"],"security":[{"bearer":[]}]}},"/v1/messaging/automations/{id}":{"patch":{"operationId":"MessagingController_updateAutomation","summary":"Update a DM automation","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}},{"name":"profileId","required":true,"in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"tags":["messaging"],"security":[{"bearer":[]}]},"delete":{"operationId":"MessagingController_deleteAutomation","summary":"Delete a DM automation","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}},{"name":"profileId","required":true,"in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"tags":["messaging"],"security":[{"bearer":[]}]}},"/v1/messaging/instagram/subscribe":{"post":{"operationId":"MessagingController_subscribe","summary":"Subscribe the profile IG account to DM/comment webhooks","parameters":[{"name":"profileId","required":true,"in":"query","schema":{"type":"string"}}],"responses":{"201":{"description":""}},"tags":["messaging"],"security":[{"bearer":[]}]}}},"info":{"title":"letspost.it API","description":"Unified social media management API — posts, profiles, analytics, webhooks, OAuth orchestration. Authenticate with a Firebase ID token (`firebaseAuth`) or a `lp_live_*` API key (`apiKey`).","version":"1.0","contact":{}},"tags":[],"servers":[],"components":{"securitySchemes":{"firebaseAuth":{"scheme":"bearer","bearerFormat":"JWT","type":"http"},"apiKey":{"scheme":"bearer","bearerFormat":"lp_live_*","type":"http"}},"schemas":{"UpdateProfileDto":{"type":"object","properties":{"name":{"type":"string","description":"Profile / brand display name.","minLength":1,"maxLength":50},"avatar":{"type":"string","description":"Avatar URL (http/https only), max 500 chars.","maxLength":500},"brandColor":{"type":"string","description":"#RRGGBB brand colour (6-digit hex), e.g. #c96442.","example":"#c96442"}}},"CreateProfileDto":{"type":"object","properties":{"name":{"type":"string","description":"Profile / brand display name.","minLength":1,"maxLength":50},"avatar":{"type":"string","description":"Avatar URL (http/https only), max 500 chars.","maxLength":500},"brandColor":{"type":"string","description":"#RRGGBB brand colour (6-digit hex), e.g. #c96442.","example":"#c96442"}},"required":["name"]},"InheritanceRulesDto":{"type":"object","properties":{"autoRepost":{"type":"boolean","description":"Auto-repost the parent's posts to the child.","default":false},"languageOverride":{"type":["object","null"],"description":"Force this language on inherited posts (null = inherit as-is)."},"tagPrefix":{"type":["object","null"],"description":"Tag prefix applied to inherited posts (null = none)."}}},"CreateProfileLinkDto":{"type":"object","properties":{"parentId":{"type":"string","description":"Parent profile id."},"childId":{"type":"string","description":"Child profile id."},"inheritanceRules":{"description":"Optional inheritance overrides for the link.","allOf":[{"$ref":"#/components/schemas/InheritanceRulesDto"}]}},"required":["parentId","childId"]},"CreatePostDto":{"type":"object","properties":{"profileId":{"type":"string","description":"Brand/profile the post belongs to."},"content":{"type":"string","description":"Caption / post body for every platform. 1–5000 chars.","maxLength":5000},"youtubeTitle":{"type":"string","description":"YouTube-specific title. Falls back to the first line of content. Max 100 chars.","maxLength":100},"mediaUrl":{"type":"string","description":"Single media URL. Omit for text-only posts."},"mediaUrls":{"description":"Carousel media URLs in slide order. Max 10 slides.","type":"array","items":{"type":"string"}},"platforms":{"type":"array","description":"Target platforms (at least one).","items":{"type":"string","enum":["tiktok","instagram","youtube","facebook","twitter","linkedin","threads","pinterest","bluesky","google-business"]}},"scheduledAt":{"type":"string","description":"ISO timestamp to schedule the post. Omit to keep as an immediate/draft post."},"postFormat":{"type":"string","description":"feed (default) or story (IG/FB Stories).","enum":["feed","story"]},"firstComment":{"type":"string","description":"Auto-posted first comment. Max 2200 chars.","maxLength":2200},"platformDescriptions":{"type":"object","description":"Per-platform caption overrides, keyed by platform.","additionalProperties":{"type":"string"}},"threadsSettings":{"type":"object","description":"Threads reply-control + alt-text settings."},"tiktokSettings":{"type":"object","description":"TikTok privacy + interaction toggles + commercial-content disclosure flags."},"confirmedWarnings":{"description":"Capability-warning codes the caller acknowledged at compose-time. Unconfirmed warnings block the create with HTTP 400.","type":"array","items":{"type":"string"}},"saveAsDraft":{"type":"boolean","description":"When true, bypasses publish-time validation so a half-written post can be saved as a draft."}},"required":["profileId","content","platforms"]},"ChildOverrideDto":{"type":"object","properties":{"profileId":{"type":"string","description":"Child brand/profile to publish the variant to."},"platforms":{"type":"array","description":"Platforms for this child variant.","items":{"type":"string","enum":["tiktok","instagram","youtube","facebook","twitter","linkedin","threads","pinterest","bluesky","google-business"]}},"captionOverride":{"type":"string","description":"Caption override for this child. Max 2200 chars.","maxLength":2200},"tagPrefix":{"type":"string","description":"Hashtag/tag prefix prepended to the caption. Max 100 chars.","maxLength":100},"scheduleOffsetMinutes":{"type":"number","description":"Minutes to offset this child from the master scheduledAt.","minimum":0}},"required":["profileId","platforms"]},"CreateFanoutPostDto":{"type":"object","properties":{"masterProfileId":{"type":"string","description":"Master brand/profile the fan-out originates from."},"content":{"type":"string","description":"Unified caption / body. Max 5000 chars.","maxLength":5000},"youtubeTitle":{"type":"string","description":"YouTube-specific title. Max 100 chars.","maxLength":100},"mediaUrl":{"type":"string","description":"Single media URL. Omit for text-only fan-out."},"masterPlatforms":{"type":"array","description":"Platforms for the master post.","items":{"type":"string","enum":["tiktok","instagram","youtube","facebook","twitter","linkedin","threads","pinterest","bluesky","google-business"]}},"scheduledAt":{"type":"string","description":"ISO timestamp to schedule the master (children offset from it)."},"children":{"description":"Child variants (at least one).","type":"array","items":{"$ref":"#/components/schemas/ChildOverrideDto"}}},"required":["masterProfileId","content","masterPlatforms","children"]},"UpdatePostDto":{"type":"object","properties":{"profileId":{"type":"string","description":"Move the post to another brand/profile."},"content":{"type":"string","description":"Caption / post body. 1–5000 chars.","maxLength":5000},"youtubeTitle":{"type":"string","description":"YouTube-specific title. Max 100 chars.","maxLength":100},"mediaUrl":{"type":"string","description":"Single media URL."},"mediaUrls":{"description":"Full carousel media set in slide order. [] clears media.","type":"array","items":{"type":"string"}},"platforms":{"type":"array","description":"Target platforms (at least one when supplied).","items":{"type":"string","enum":["tiktok","instagram","youtube","facebook","twitter","linkedin","threads","pinterest","bluesky","google-business"]}},"scheduledAt":{"type":["object","null"],"description":"ISO timestamp to (re)schedule. null unschedules."},"postFormat":{"type":"string","description":"feed or story (IG/FB Stories).","enum":["feed","story"]}}},"ExpandPostDto":{"type":"object","properties":{"addPlatforms":{"type":"array","description":"Platforms to additionally publish to (1–10).","items":{"type":"string","enum":["tiktok","instagram","youtube","facebook","twitter","linkedin","threads","pinterest","bluesky","google-business"]}}},"required":["addPlatforms"]},"DuplicatePostDto":{"type":"object","properties":{"scheduledAt":{"type":"string","description":"ISO timestamp to clone+schedule in one call. Omit to clone as an unscheduled draft."}}},"RecycleConfigDto":{"type":"object","properties":{"enabled":{"type":"boolean","description":"Turn auto-recycle on or off for the post."},"intervalDays":{"type":"number","description":"Days between recycles. 1–365.","minimum":1,"maximum":365}},"required":["enabled"]},"OptimizeCaptionsDto":{"type":"object","properties":{"title":{"type":"string","description":"Universal title / hook. Max 200 chars.","maxLength":200},"description":{"type":"string","description":"Universal description / body. Max 5000 chars.","maxLength":5000},"platforms":{"type":"array","description":"Platforms to generate optimized captions for.","items":{"type":"string","enum":["tiktok","instagram","youtube","facebook","twitter","linkedin","threads","pinterest","bluesky","google-business"]}},"hashtags":{"description":"Hashtags to place. Max 30, each ≤100 chars.","type":"array","items":{"type":"string"}}},"required":["title","platforms"]},"AutoScheduleDto":{"type":"object","properties":{"postIds":{"description":"Draft post ids to distribute. 1–200.","type":"array","items":{"type":"string"}},"postsPerDay":{"type":"number","description":"How many posts to place per day. 1–24.","minimum":1,"maximum":24},"startDate":{"type":"string","description":"ISO date to start distributing from. Default: next available day."},"timeSlots":{"description":"Time-of-day slots in HH:MM to place posts at.","example":["09:00","17:30"],"type":"array","items":{"type":"string"}}},"required":["postIds"]},"CreateRecurringDto":{"type":"object","properties":{}},"CreateReplicationDto":{"type":"object","properties":{}},"CreateApiKeyDto":{"type":"object","properties":{}},"RenameApiKeyDto":{"type":"object","properties":{}},"UpdateScopesDto":{"type":"object","properties":{}},"CreateWebhookDto":{"type":"object","properties":{"url":{"type":"string","format":"uri","description":"Subscriber URL. Must be HTTPS."},"secret":{"type":"string","maxLength":128,"description":"Optional signing secret. Auto-generated if omitted."},"headers":{"type":"object","additionalProperties":{"type":"string"},"description":"Custom headers sent with each delivery."},"events":{"type":"array","minItems":1,"description":"Event types to subscribe to.","items":{"type":"string","enum":["post.scheduled","post.published","post.failed","post.partial","post.cancelled","post.deleted","post.restored","account.connected","account.disconnected","account.revoked_by_provider","account.token_expired","account.token_refreshed","account.permissions_changed","analytics.daily_summary_ready","team.member_invited","team.member_accepted","team.member_removed","team.role_changed","ticket.created","ticket.replied","ticket.resolved","ticket.reopened","subscription.upgraded","subscription.downgraded","subscription.cancelled","payment.succeeded","payment.failed","media.uploaded"]}}},"required":["url","events"]},"UpdateWebhookDto":{"type":"object","properties":{"secret":{"type":"string","maxLength":128,"description":"Optional signing secret. Auto-generated if omitted."},"headers":{"type":"object","additionalProperties":{"type":"string"},"description":"Custom headers sent with each delivery."},"events":{"type":"array","minItems":1,"description":"Event types to subscribe to.","items":{"type":"string","enum":["post.scheduled","post.published","post.failed","post.partial","post.cancelled","post.deleted","post.restored","account.connected","account.disconnected","account.revoked_by_provider","account.token_expired","account.token_refreshed","account.permissions_changed","analytics.daily_summary_ready","team.member_invited","team.member_accepted","team.member_removed","team.role_changed","ticket.created","ticket.replied","ticket.resolved","ticket.reopened","subscription.upgraded","subscription.downgraded","subscription.cancelled","payment.succeeded","payment.failed","media.uploaded"]}},"active":{"type":"boolean","description":"Enable/disable the endpoint without deleting it."}}},"InviteMemberDto":{"type":"object","properties":{"email":{"type":"string","description":"Invitee email address.","format":"email"},"role":{"type":"string","description":"Role to grant the invitee.","enum":["admin","editor","viewer","reviewer"]},"profileIds":{"description":"Membership scope: 'all' (default) or an array of profile ids to restrict access to.","oneOf":[{"type":"string","enum":["all"]},{"type":"array","items":{"type":"string"}}]}},"required":["email","role"]},"UpdateRoleDto":{"type":"object","properties":{"role":{"type":"string","description":"New role for the member.","enum":["admin","editor","viewer","reviewer"]}},"required":["role"]},"CreateTicketDto":{"type":"object","properties":{"subject":{"type":"string","maxLength":200,"description":"Ticket subject line."},"category":{"type":"string","enum":["bug","question","feature_request","billing","integration","suggestion"],"description":"Ticket category."},"severity":{"type":"string","enum":["low","medium","high","critical"],"description":"Severity level."},"description":{"type":"string","maxLength":5000,"description":"Full ticket description / body."},"isPublic":{"type":"boolean","description":"When true, surfaces the ticket as a public suggestion."}},"required":["subject","category","severity","description"]},"AddMessageDto":{"type":"object","properties":{"body":{"type":"string","maxLength":5000,"description":"Message body appended to the ticket thread."}},"required":["body"]},"UpdateStatusDto":{"type":"object","properties":{"status":{"type":"string","enum":["open","triage","in_progress","waiting_on_user","resolved","closed"],"description":"New ticket status."}},"required":["status"]},"SubscribeDto":{"type":"object","properties":{"email":{"type":"string","format":"email","description":"Subscriber email address."},"componentIds":{"description":"Component ids to subscribe to. Empty = all components.","type":"array","items":{"type":"string"}}},"required":["email"]},"UpsertComponentDto":{"type":"object","properties":{"id":{"type":"string","description":"Stable component id (slug)."},"name":{"type":"string","description":"Human-readable component name."},"description":{"type":"string","description":"Optional component description."},"currentStatus":{"type":"string","enum":["operational","degraded_performance","partial_outage","major_outage","under_maintenance"],"description":"Current component status."},"order":{"type":"number","description":"Display sort order."}},"required":["id","name","currentStatus"]},"CreateIncidentDto":{"type":"object","properties":{"title":{"type":"string","description":"Incident title."},"affectedComponentIds":{"description":"Component ids affected by this incident.","type":"array","items":{"type":"string"}},"impact":{"type":"string","enum":["minor","major","critical"],"description":"Incident impact level."},"firstMessage":{"type":"string","description":"First status message for the incident timeline."}},"required":["title","affectedComponentIds","impact","firstMessage"]},"AddIncidentUpdateDto":{"type":"object","properties":{"status":{"type":"string","enum":["investigating","identified","monitoring","resolved","postmortem"],"description":"New incident status."},"message":{"type":"string","description":"Update message posted to the incident timeline."},"author":{"type":"string","description":"Optional author name."}},"required":["status","message"]},"CreateInviteDto":{"type":"object","properties":{"profileId":{"type":"string","description":"Profile the invited account will connect to."},"platform":{"type":"string","description":"Social platform to connect.","enum":["tiktok","instagram","youtube","facebook","twitter","linkedin","threads","pinterest","bluesky"]},"expiryHours":{"type":"number","description":"Hours until expiry (1–720). Default 72.","minimum":1,"maximum":720,"default":72},"maxUses":{"type":"number","description":"Number of SUCCESSFUL connections allowed (1–50). Default 1. Only completed OAuth connects count.","minimum":1,"maximum":50,"default":1}},"required":["profileId","platform"]},"RegisterClientDto":{"type":"object","properties":{}},"ConsentDto":{"type":"object","properties":{}}}}}