Copy YAML for LLM
Copy JSON for LLM
openapi: 3.0.3 info: title: Supergrow External API description: | The Supergrow External API allows you to programmatically manage workspaces, posts, content, and analytics for your LinkedIn content management workflow. ## Authentication Every request must carry one of: - **API key** in the `X-Api-Key` header. Org owners and admins create their key in the Supergrow app under Settings → MCP (one key per user). - **OAuth bearer token** in the `Authorization: Bearer <token>` header, issued by Supergrow's OAuth 2.1 server after the user consents. This is how MCP clients such as Claude connect. Tokens carry the same access as the user's API key. If both are sent, the API key is used. ## Optional Headers - `X-Skill-Version` — optional string sent by MCP/skill clients so the backend can track skill adoption. Free-form version string; safe to omit. ## Rate Limits - **3,600 requests per hour** per user - **5,000 requests per day** per user - **Content extraction:** 10 extractions per day per workspace ## MCP Server Supergrow offers an MCP (Model Context Protocol) server that lets you manage your LinkedIn content directly from AI tools like Claude. Contact support@supergrow.ai to learn more. version: "1.1.0" contact: url: https://help.supergrow.ai servers: - url: https://external-api.supergrow.ai/api/v1/external description: Production security: - ApiKeyAuth: [] - BearerAuth: [] components: securitySchemes: ApiKeyAuth: type: apiKey in: header name: X-Api-Key BearerAuth: type: http scheme: bearer bearerFormat: JWT description: Access token issued by Supergrow's OAuth 2.1 server (used by MCP clients). schemas: Error: type: object properties: errors: type: array items: type: string error_code: type: string nullable: true description: Machine-readable error code (e.g. PAID_PLAN_REQUIRED) Workspace: type: object properties: id: type: string format: uuid org_id: type: string format: uuid nullable: true description: The organization this workspace belongs to. Null for personal (non-org) workspaces. name: type: string is_archived: type: boolean created_at: type: string format: date-time updated_at: type: string format: date-time role: type: string enum: [ADMIN, TEAM MEMBER, CLIENT, ORG_OWNER, ORG_ADMIN] timezone: type: string nullable: true description: Workspace timezone (e.g. "Asia/Kolkata"). Used for scheduling posts. language: type: string nullable: true description: Workspace content language as an ISO 639-1 code (e.g. "en", "hi"). AutoPlugComment: type: object properties: id: type: string format: uuid post_id: type: string format: uuid text: type: string status: type: string enum: [draft, scheduled, published, failed] post_after_time_unit: type: string enum: [minutes, hours, days] post_after_time_unit_count: type: integer published_at: type: string format: date-time nullable: true created_at: type: string format: date-time MediaItem: type: object properties: id: type: string format: uuid workspace_id: type: string format: uuid file_name: type: string content_type: type: string description: MIME type (e.g. image/jpeg, video/mp4, application/pdf) media_type: type: string enum: [image, video, audio, document] file_size: type: integer nullable: true description: File size in bytes (set after confirmation) status: type: string enum: [pending, confirmed] url: type: string nullable: true description: Presigned read URL (24h expiry). Only present for confirmed items. created_at: type: string format: date-time updated_at: type: string format: date-time CompanyPage: type: object properties: id: type: string format: uuid name: type: string url: type: string logo_url: type: string page_id: type: string description: LinkedIn company page ID MediaAttachment: type: object description: Hydrated read-only representation of a media item attached to a post. Returned in the `media_attachments` array. The `url` is a 24h presigned URL — do NOT cache or persist it; re-fetch the post to refresh. properties: id: type: string format: uuid type: type: string enum: [image, video, document, audio] file_name: type: string content_type: type: string byte_size: type: integer nullable: true url: type: string description: 24h presigned GET URL — short-lived, do not persist. preview_url: type: string nullable: true description: Thumbnail/preview URL for videos. Null for non-video media. url_expires_at: type: string format: date-time description: Wall-clock expiry of `url`. Re-fetch the post to obtain a fresh URL. position: type: integer Post: type: object properties: id: type: string format: uuid text: type: string media_attachments: type: array description: Canonical read-only representation of attached media. Use this in your integration instead of the deprecated legacy URL fields. items: $ref: '#/components/schemas/MediaAttachment' image_urls: type: array deprecated: true description: Legacy field. Populated for back-compat — derived from `media_attachments` when the post was written via `media_ids`. New integrations should read `media_attachments`. items: type: string status: type: string enum: [draft, scheduled, published, failed, under_review, approved, needs_changes] published_at: type: string format: date-time nullable: true published_url: type: string nullable: true video_url: type: string nullable: true deprecated: true description: Legacy field. See `media_attachments`. video_title: type: string nullable: true carousel_title: type: string nullable: true file_url: type: string nullable: true deprecated: true description: Legacy field. See `media_attachments`. publish_from_type: type: string enum: [personal, company] description: Whether the post is published from a personal profile or company page linked_in_company_page_id: type: string format: uuid nullable: true description: The company page ID if publish_from_type is "company" likes_count: type: integer nullable: true comments_count: type: integer nullable: true reshares_count: type: integer nullable: true impressions_count: type: integer nullable: true app_url: type: string nullable: true description: URL to edit the post in the Supergrow app created_at: type: string format: date-time updated_at: type: string format: date-time ContentDna: type: object nullable: true properties: id: type: string format: uuid status: type: string enum: [pending, analyzing, completed, failed, seeded] description: Availability of the stored DNA. Existing completed or seeded DNA remains available during regeneration. regeneration_status: type: string nullable: true enum: [queued, running, completed, failed] regeneration_kind: type: string nullable: true enum: [regenerate, import] description: What started the latest run, a regeneration or an import of pasted writing details. regeneration_active: type: boolean description: True while an unexpired regeneration request is queued or running. regeneration_error: type: string nullable: true description: The latest regeneration attempt error, if any. linkedin_profile_url: type: string nullable: true name: type: string current_role: type: string company: type: string industry: type: string experience_years: type: integer seniority_level: type: string expertise_areas: type: array items: type: string main_topics: type: array items: type: string target_customers: type: array items: type: object content_positioning: type: object writing_style: type: object content_patterns: type: object created_at: type: string format: date-time updated_at: type: string format: date-time LinkedInAccount: type: object properties: id: type: string name: type: string avatar_url: type: string nullable: true linked_in_profile_url: type: string nullable: true created_at: type: string format: date-time updated_at: type: string format: date-time WeeklyReport: type: object properties: id: type: string format: uuid week_start_date: type: string format: date week_end_date: type: string format: date week_label: type: string status: type: string target: type: object description: The LinkedIn profile or company page this report covers. properties: type: type: string enum: [personal, company_page] linked_in_account_id: type: string nullable: true linked_in_company_page_id: type: string format: uuid nullable: true summary: type: object properties: posts_published: type: integer total_engagements: type: integer total_impressions: type: integer rolling_average_engagement: type: number vs_baseline_percentage: type: number vs_baseline_text: type: string best_performer: type: object nullable: true properties: post_id: type: string multiplier: type: number insight: type: object worst_performer: type: object nullable: true properties: post_id: type: string multiplier: type: number insight: type: object posts_breakdown: type: array items: type: object audience_growth: type: object nullable: true created_at: type: string format: date-time DailyAnalytic: type: object properties: date: type: string format: date impressions: type: integer engagements: type: integer reactions: type: integer comments: type: integer reshares: type: integer members_reached: type: integer ExtractionResult: type: object properties: text: type: string url: type: string KnowledgeBaseItem: type: object properties: id: type: string format: uuid title: type: string description: type: string nullable: true source_type: type: string enum: [text, website, pdf, document, youtube, voice] source_url: type: string nullable: true status: type: string enum: [draft, processing, ready, failed] scope: type: string enum: [workspace, org] chunks_count: type: integer created_at: type: string format: date-time updated_at: type: string format: date-time KnowledgeBaseSearchResultItem: type: object properties: score: type: number format: float description: Relevance score (0–1, higher is better) entry_type: type: string enum: [chunk, insight, chunk_with_insight] chunk: type: object nullable: true description: The source text chunk (present for chunk and chunk_with_insight entries) properties: id: type: string content: type: string content_preview: type: string chunk_index: type: integer token_count: type: integer insight: type: object nullable: true description: Extracted insight (present for insight and chunk_with_insight entries) properties: type: type: string enum: [statistic, quote, milestone, product_feature, customer_proof_point, contrarian_claim] text: type: string confidence: type: string enum: [high, medium, low] knowledge_base_item: type: object properties: id: type: string format: uuid title: type: string description: type: string nullable: true source_type: type: string source_url: type: string nullable: true metadata: type: object description: Additional metadata for the search result item. Pagination: type: object properties: total: type: integer page: type: integer per_page: type: integer pages: type: integer parameters: WorkspaceId: name: workspace_id in: query required: true schema: type: string format: uuid description: The ID of the workspace paths: /workspaces: get: summary: List all workspaces description: Returns all workspaces accessible to the authenticated user, including their role in each. tags: [Workspaces] responses: "200": description: List of workspaces content: application/json: schema: type: object properties: workspaces: type: array items: $ref: "#/components/schemas/Workspace" /posts: get: summary: List posts description: Lists posts in a workspace filtered by status. tags: [Posts] parameters: - $ref: "#/components/parameters/WorkspaceId" - name: status in: query required: true schema: type: string enum: [draft, scheduled, published, failed, all] - name: tag_ids in: query required: false schema: type: string description: Comma-separated tag IDs responses: "200": description: List of posts content: application/json: schema: type: object properties: posts: type: array items: $ref: "#/components/schemas/Post" post: summary: Create a post description: | Creates a new draft post in a workspace. Optionally publish from a company page by providing `linked_in_company_page_id`. Use `GET /company-pages` to list available company pages. tags: [Posts] parameters: - $ref: "#/components/parameters/WorkspaceId" requestBody: required: true content: application/json: schema: type: object required: [text] properties: text: type: string maxLength: 3000 media_ids: type: array description: Canonical write-side media contract. UUIDs of MediaItems (uploaded via POST /media). When present, the server derives legacy URL fields (image_urls, file_url, video_url) from the media items by type — clients should use this instead of the deprecated legacy fields. Order is preserved as display order. items: type: string format: uuid image_urls: type: array deprecated: true description: Legacy write field. Still accepted for back-compat; signed URLs are rejected with 422. Prefer `media_ids`. items: type: string file_url: type: string deprecated: true description: Legacy write field. Prefer `media_ids`. carousel_title: type: string video_url: type: string deprecated: true description: Legacy write field. Prefer `media_ids`. video_title: type: string video_thumbnail_url: type: string deprecated: true description: Legacy write field. Prefer `media_ids`. linked_in_company_page_id: type: string format: uuid description: Optional. If provided, the post will be published from this company page instead of the personal profile. Use GET /company-pages to list available pages. responses: "201": description: Post created content: application/json: schema: type: object properties: status: type: string post: $ref: "#/components/schemas/Post" /posts/{id}: get: summary: Get a post description: Get details for a specific post. tags: [Posts] parameters: - $ref: "#/components/parameters/WorkspaceId" - name: id in: path required: true schema: type: string format: uuid responses: "200": description: Post details content: application/json: schema: type: object properties: post: $ref: "#/components/schemas/Post" put: summary: Update a post description: Updates an existing draft post. tags: [Posts] parameters: - $ref: "#/components/parameters/WorkspaceId" - name: id in: path required: true schema: type: string format: uuid requestBody: required: true content: application/json: schema: type: object required: [text] properties: text: type: string maxLength: 3000 media_ids: type: array description: Canonical write-side media contract. UUIDs of MediaItems (uploaded via POST /media). When present, the server derives legacy URL fields from the media items by type. Order is preserved. items: type: string format: uuid image_urls: type: array deprecated: true description: Legacy write field. Prefer `media_ids`. items: type: string file_url: type: string deprecated: true description: Legacy write field. Prefer `media_ids`. carousel_title: type: string video_url: type: string deprecated: true description: Legacy write field. Prefer `media_ids`. video_title: type: string video_thumbnail_url: type: string deprecated: true description: Legacy write field. Prefer `media_ids`. responses: "200": description: Post updated delete: summary: Delete a post description: Deletes a draft or failed post. tags: [Posts] parameters: - $ref: "#/components/parameters/WorkspaceId" - name: id in: path required: true schema: type: string format: uuid responses: "200": description: Post deleted /posts/{id}/schedule: post: summary: Schedule a post description: >- Schedules a post for publication at a specific time. Pass `hour` in the workspace's local timezone (NOT UTC). For example, for 2 PM IST pass hour=14. tags: [Posts] parameters: - $ref: "#/components/parameters/WorkspaceId" - name: id in: path required: true schema: type: string format: uuid requestBody: required: true content: application/json: schema: type: object properties: day_of_week: type: integer description: Day of week (0=Sunday, 1=Monday, ..., 6=Saturday) hour: type: integer minimum: 0 maximum: 23 description: Hour in the workspace's local timezone (0-23). Do NOT convert to UTC. minute: type: integer minimum: 0 maximum: 59 day_of_the_month: type: integer minimum: 1 maximum: 31 month: type: integer minimum: 1 maximum: 12 year: type: integer responses: "200": description: Post scheduled /posts/{id}/unschedule: post: summary: Unschedule a post description: Removes scheduling and reverts post to draft. tags: [Posts] parameters: - $ref: "#/components/parameters/WorkspaceId" - name: id in: path required: true schema: type: string format: uuid responses: "200": description: Post unscheduled /posts/{id}/publish: post: summary: Publish a post description: Immediately initiates the publishing process (asynchronous). tags: [Posts] parameters: - $ref: "#/components/parameters/WorkspaceId" - name: id in: path required: true schema: type: string format: uuid responses: "200": description: Publishing initiated /posts/queue: post: summary: Queue a post description: | Creates a post and auto-schedules it in the next available time slot. Optionally publish from a company page by providing `linked_in_company_page_id`. tags: [Posts] parameters: - $ref: "#/components/parameters/WorkspaceId" requestBody: required: true content: application/json: schema: type: object required: [text] properties: text: type: string maxLength: 3000 media_ids: type: array description: Canonical write-side media contract. UUIDs of MediaItems (uploaded via POST /media). When present, derives legacy URL fields from MediaItem types. Order is preserved. items: type: string format: uuid image_urls: type: array deprecated: true description: Legacy write field. Prefer `media_ids`. items: type: string file_url: type: string deprecated: true description: Legacy write field. Prefer `media_ids`. carousel_title: type: string video_url: type: string deprecated: true description: Legacy write field. Prefer `media_ids`. video_title: type: string linked_in_company_page_id: type: string format: uuid description: Optional. Publish from a company page instead of personal profile. responses: "201": description: Post created and scheduled /posts/{id}/public-link: post: summary: Create public preview link description: Creates a public link to preview a post. Returns the existing link if one already exists. tags: [Posts] parameters: - $ref: "#/components/parameters/WorkspaceId" - name: id in: path required: true schema: type: string format: uuid responses: "200": description: Public link created content: application/json: schema: type: object properties: public_url: type: string format: uri "404": description: Post not found delete: summary: Remove public preview link description: Removes the public preview link from a post. tags: [Posts] parameters: - $ref: "#/components/parameters/WorkspaceId" - name: id in: path required: true schema: type: string format: uuid responses: "200": description: Public link removed /content-dna: get: summary: Get Content DNA description: Returns the Content DNA profile for the workspace — professional profile, writing style, content patterns, and target audience. tags: [Content DNA] parameters: - $ref: "#/components/parameters/WorkspaceId" responses: "200": description: Content DNA profile content: application/json: schema: type: object properties: content_dna: $ref: "#/components/schemas/ContentDna" /content-dna/regenerate: post: summary: Regenerate Content DNA description: | Triggers Content DNA regeneration from the member's published history. No request body needed. LinkedIn is primary when an account or stored DNA profile URL exists. LinkedIn generation has no minimum post count and can use profile data alone. If LinkedIn post fetching fails or no URL exists, the Supergrow fallback requires at least five distinct, original, non-empty published posts. Ambiguous account selection or insufficient history without a URL returns 422. LinkedIn fetching runs in the job. Failed generation preserves the current DNA. Existing DNA remains available until generation succeeds. Read regeneration_status and regeneration_active to track progress. Regeneration replaces personal edits, retains populated current organization template values, and preserves existing restrictions. Duplicate active requests return 409. A new request can reclaim an expired one-hour lease. tags: [Content DNA] parameters: - $ref: "#/components/parameters/WorkspaceId" responses: "200": description: Regeneration started content: application/json: schema: type: object properties: message: type: string content_dna: $ref: "#/components/schemas/ContentDna" "409": description: Content DNA regeneration is already in progress content: application/json: schema: $ref: "#/components/schemas/Error" "503": description: Failed to enqueue regeneration; the request claim has been released content: application/json: schema: $ref: "#/components/schemas/Error" "422": description: Insufficient published history without a profile URL, invalid URL, or ambiguous member account content: application/json: schema: $ref: "#/components/schemas/Error" /linked-in-accounts: get: summary: Get LinkedIn accounts description: Returns LinkedIn accounts connected to the workspace. Sensitive tokens are never exposed. tags: [LinkedIn Accounts] parameters: - $ref: "#/components/parameters/WorkspaceId" responses: "200": description: List of LinkedIn accounts content: application/json: schema: type: object properties: linked_in_accounts: type: array items: $ref: "#/components/schemas/LinkedInAccount" /calendar/weekly: get: summary: Get weekly calendar description: Returns the weekly calendar view with time slots and scheduled posts. tags: [Calendar] parameters: - $ref: "#/components/parameters/WorkspaceId" - name: offset in: query schema: type: integer default: 0 description: "Week offset (0=current, 1=next, -1=last)" responses: "200": description: Weekly calendar content: application/json: schema: type: object properties: calendar: type: object description: Weekly calendar data with time slots and associated posts timezone: type: string example: "America/New_York" description: The workspace's configured timezone /calendar/monthly: get: summary: Get monthly calendar description: Returns the monthly calendar view with time slots and scheduled posts. tags: [Calendar] parameters: - $ref: "#/components/parameters/WorkspaceId" - name: offset in: query schema: type: integer default: 0 description: "Month offset (0=current, 1=next, -1=last)" responses: "200": description: Monthly calendar content: application/json: schema: type: object properties: calendar: type: object description: Monthly calendar data with time slots and associated posts timezone: type: string example: "America/New_York" description: The workspace's configured timezone /kanban: get: summary: Get kanban board description: Returns the kanban board with posts grouped by status. tags: [Kanban] parameters: - $ref: "#/components/parameters/WorkspaceId" - name: page in: query schema: type: integer default: 1 - name: per_page in: query schema: type: integer default: 10 maximum: 100 responses: "200": description: Kanban board /kanban/column: get: summary: Get kanban column description: Returns posts for a single status column. tags: [Kanban] parameters: - $ref: "#/components/parameters/WorkspaceId" - name: status in: query required: true schema: type: string enum: [draft, under_review, approved, needs_changes, scheduled, published] - name: page in: query schema: type: integer default: 1 - name: per_page in: query schema: type: integer default: 10 maximum: 100 responses: "200": description: Kanban column /analytics/weekly-reports: get: summary: Get weekly reports description: | Returns weekly content performance reports for the workspace. By default only personal-profile reports are returned (one per week). Use target_type to fetch company-page reports or all reports. tags: [Analytics] parameters: - $ref: "#/components/parameters/WorkspaceId" - name: limit in: query schema: type: integer default: 12 maximum: 52 - name: target_type in: query schema: type: string enum: [personal, company_page, all] default: personal description: Filter reports by target. Each report includes a `target` object identifying its LinkedIn account or company page. responses: "200": description: List of weekly reports content: application/json: schema: type: object properties: weekly_reports: type: array items: $ref: "#/components/schemas/WeeklyReport" /analytics/followers: get: summary: Get live followers data description: | Returns live follower count and trend data from LinkedIn API. Rate limited to 500 live analytics calls per day per workspace. tags: [Analytics] parameters: - $ref: "#/components/parameters/WorkspaceId" - name: period in: query schema: type: string enum: [last_7_days, last_30_days, current_month, previous_month] default: last_7_days description: Predefined period (ignored if start_date/end_date provided) - name: start_date in: query schema: type: string format: date description: Custom start date (YYYY-MM-DD). Use with end_date. - name: end_date in: query schema: type: string format: date description: Custom end date (YYYY-MM-DD). Use with start_date. responses: "200": description: Followers data content: application/json: schema: type: object properties: current_count: type: integer trend: type: object "422": description: No LinkedIn account connected /analytics/metrics: get: summary: Get live engagement metrics description: | Returns live engagement metric trends from LinkedIn API. Supported metrics: IMPRESSION, MEMBERS_REACHED, RESHARE, REACTION, COMMENT. Rate limited to 500 live analytics calls per day per workspace. tags: [Analytics] parameters: - $ref: "#/components/parameters/WorkspaceId" - name: metric in: query schema: type: string enum: [IMPRESSION, MEMBERS_REACHED, RESHARE, REACTION, COMMENT] default: IMPRESSION - name: period in: query schema: type: string enum: [last_7_days, last_30_days, current_month, previous_month] default: last_7_days description: Predefined period (ignored if start_date/end_date provided) - name: start_date in: query schema: type: string format: date description: Custom start date (YYYY-MM-DD). Use with end_date. - name: end_date in: query schema: type: string format: date description: Custom end date (YYYY-MM-DD). Use with start_date. responses: "200": description: Metric trend data content: application/json: schema: type: object properties: data: type: array items: type: object metric_type: type: string "422": description: No LinkedIn account connected or invalid metric /extract: post: summary: Extract text (auto-detect) description: Automatically detects URL type (webpage, YouTube, PDF, audio) and extracts text. Limited to 10 extractions per day per workspace. tags: [Content Extraction] parameters: - $ref: "#/components/parameters/WorkspaceId" requestBody: required: true content: application/json: schema: type: object required: [url] properties: url: type: string format: uri responses: "200": description: Extracted text content: application/json: schema: $ref: "#/components/schemas/ExtractionResult" "429": description: Daily extraction limit reached content: application/json: schema: $ref: "#/components/schemas/Error" /extract/youtube: post: summary: Extract YouTube transcript description: Extracts the transcript from a YouTube video. Limited to 10 extractions per day per workspace. tags: [Content Extraction] parameters: - $ref: "#/components/parameters/WorkspaceId" requestBody: required: true content: application/json: schema: type: object required: [url] properties: url: type: string format: uri responses: "200": description: YouTube transcript content: application/json: schema: $ref: "#/components/schemas/ExtractionResult" /extract/pdf: post: summary: Extract PDF text description: Extracts text content from a PDF file. Limited to 10 extractions per day per workspace. tags: [Content Extraction] parameters: - $ref: "#/components/parameters/WorkspaceId" requestBody: required: true content: application/json: schema: type: object required: [url] properties: url: type: string format: uri responses: "200": description: PDF text content: application/json: schema: $ref: "#/components/schemas/ExtractionResult" /extract/audio: post: summary: Transcribe audio description: | Transcribes audio or video files to text. Limited to 10 extractions per day per workspace. Supported formats: mp3, mp4, wav, m4a, aac, ogg, flac, wma, avi, mov, wmv, flv, webm, mkv. tags: [Content Extraction] parameters: - $ref: "#/components/parameters/WorkspaceId" requestBody: required: true content: application/json: schema: type: object required: [url] properties: url: type: string format: uri responses: "200": description: Transcribed text content: application/json: schema: $ref: "#/components/schemas/ExtractionResult" /questions/from-topic: post: summary: Generate questions from a topic description: | Generates 8-12 interview-style questions for a given topic. Questions are personalized using the workspace's Content DNA (if available) and designed to extract insights for LinkedIn content creation. Each question includes follow-up prompts. tags: [Questions] parameters: - $ref: "#/components/parameters/WorkspaceId" requestBody: required: true content: application/json: schema: type: object required: [topic] properties: topic: type: string description: The topic to generate questions about responses: "200": description: Generated questions content: application/json: schema: type: object properties: questions: type: array items: type: object properties: question: type: string follow_ups: type: array items: type: string "422": description: Missing topic or generation failed /questions/from-text: post: summary: Generate questions from text content description: | Analyzes text content (article, blog post, transcript, etc.) and generates interview-style questions to help extract insights for LinkedIn posts. Questions are self-contained with context from the text. tags: [Questions] parameters: - $ref: "#/components/parameters/WorkspaceId" requestBody: required: true content: application/json: schema: type: object required: [text] properties: text: type: string description: The text content to analyze and generate questions from responses: "200": description: Generated questions content: application/json: schema: type: object properties: questions: type: array items: type: object properties: question: type: string follow_ups: type: array items: type: string intent: type: string enum: [comprehension, application, critical_thinking, synthesis, data_analysis] "422": description: Missing text or generation failed /knowledge-base/ingest: post: summary: Ingest text content description: Ingests plain text into the workspace knowledge base. The content is chunked, embedded, and indexed asynchronously. Returns immediately with status "processing". tags: [Knowledge Base] parameters: - $ref: "#/components/parameters/WorkspaceId" requestBody: required: true content: application/json: schema: type: object required: [content] properties: content: type: string description: The text content to ingest title: type: string description: Optional title for the knowledge base item responses: "201": description: Ingestion started content: application/json: schema: type: object properties: knowledge_base_item: $ref: "#/components/schemas/KnowledgeBaseItem" "401": description: Unauthorized or workspace access denied content: application/json: schema: $ref: "#/components/schemas/Error" "422": description: content parameter missing or ingestion failed content: application/json: schema: $ref: "#/components/schemas/Error" /knowledge-base: get: summary: List knowledge base items description: Returns a paginated list of knowledge base items in the workspace. tags: [Knowledge Base] parameters: - $ref: "#/components/parameters/WorkspaceId" - name: source_type in: query schema: type: string enum: [text, website, pdf, document, youtube, voice] description: Filter by source type - name: status in: query schema: type: string enum: [draft, processing, ready, failed] description: Filter by status - name: page in: query schema: type: integer default: 1 - name: per_page in: query schema: type: integer default: 20 maximum: 100 responses: "200": description: List of knowledge base items content: application/json: schema: type: object properties: knowledge_base_items: type: array items: $ref: "#/components/schemas/KnowledgeBaseItem" total_count: type: integer page: type: integer per_page: type: integer /knowledge-base/{id}: get: summary: Get a knowledge base item description: Returns a single knowledge base item by ID. tags: [Knowledge Base] parameters: - $ref: "#/components/parameters/WorkspaceId" - name: id in: path required: true schema: type: string format: uuid responses: "200": description: Knowledge base item content: application/json: schema: type: object properties: knowledge_base_item: $ref: "#/components/schemas/KnowledgeBaseItem" "404": description: Item not found or belongs to a different workspace content: application/json: schema: $ref: "#/components/schemas/Error" delete: summary: Delete a knowledge base item description: Permanently deletes a knowledge base item and its associated chunks. tags: [Knowledge Base] parameters: - $ref: "#/components/parameters/WorkspaceId" - name: id in: path required: true schema: type: string format: uuid responses: "200": description: Item deleted content: application/json: schema: type: object properties: status: type: string example: ok "404": description: Item not found content: application/json: schema: $ref: "#/components/schemas/Error" /knowledge-base/search: post: summary: Search the knowledge base description: | Performs semantic vector search across the workspace knowledge base. Returns both raw text chunks and extracted insights ranked by relevance score. Results include: - **chunks** — raw text segments from ingested documents - **insights** — structured data points (statistics, quotes, milestones, etc.) extracted from chunks by AI tags: [Knowledge Base] parameters: - $ref: "#/components/parameters/WorkspaceId" requestBody: required: true content: application/json: schema: type: object required: [query] properties: query: type: string description: The search query source_type: type: string enum: [text, website, pdf, document, youtube, voice] description: Filter results to a specific source type include_insights: type: boolean default: true description: Set to false to return raw chunks only similarity_threshold: type: number description: Similarity threshold between 0 and 1 for filtering results limit: type: integer default: 10 minimum: 1 maximum: 50 responses: "200": description: Search results content: application/json: schema: type: object properties: query: type: string total_results: type: integer results: type: array items: $ref: "#/components/schemas/KnowledgeBaseSearchResultItem" "401": description: Unauthorized or workspace access denied content: application/json: schema: $ref: "#/components/schemas/Error" "422": description: query parameter missing content: application/json: schema: $ref: "#/components/schemas/Error" /post-score: post: summary: Score a LinkedIn post description: | Analyzes a LinkedIn post on two axes and returns actionable, line-level feedback. The response is a **backward-compatible superset**: - `score` — the per-criteria breakdown (unchanged). Existing integrations can keep reading this array exactly as before. - `analysis` — added, two-axis analysis: - **Performance** (`analysis.performance.score`, 0–10): will the post engage? — hook, clarity, readability, CTA, originality. - **Authenticity** (`analysis.authenticity.score`, 0–10 + `slop_risk`): does the post read as a genuine human, or as AI slop? `analysis.slop_issues` lists each detected slop pattern, worst-first, with the **exact offending line quoted** (`quoted_line`), a human `fix` explanation, and a **paste-ready `rewrite`** (the literal replacement text; empty string = delete the line). The list is empty when the post reads as genuine human writing. - `analysis.verdict` — `ready_to_post` (Performance ≥ 7 and slop risk low) or `needs_work`. - `analysis.voice_note` — how the post fits the author's voice (when Content DNA is configured). Slop detection is voice-aware: when a workspace has Content DNA, a writer's genuinely blunt or personal style is not flagged as slop. tags: [Post Score] parameters: - $ref: "#/components/parameters/WorkspaceId" requestBody: required: true content: application/json: schema: type: object required: [text] properties: text: type: string description: The LinkedIn post text to score responses: "200": description: Post score — per-criteria breakdown plus two-axis analysis content: application/json: schema: type: object properties: score: type: array description: > Per-criteria performance breakdown: 5 criteria (Hook Quality, Clarity and Coherency, Easy to read, Call-To-Action, Originality), each scored 0-2 with weight 2. Key on `criteria_title` (stable) — `criteria_name` is a descriptive question that varies per call. The overall /10 (`analysis.performance.score`) is the sum of the 5 criterion scores. When the post contains a link, an additional "Link present" item is appended with score -1 and no `weight` (a reach penalty), so the array can have 6 items. items: type: object properties: criteria_name: type: string description: Descriptive question for the criterion. Free-form and NOT stable across calls — do not key on it. criteria_title: type: string description: Stable criterion key — use this to identify the criterion. "Link present" appears only when the post contains a link. enum: [Hook Quality, Clarity and Coherency, Easy to read, Call-To-Action, Originality, Link present] score: type: integer minimum: -1 maximum: 2 description: 0-2 for the 5 performance criteria; -1 for the "Link present" penalty item. Differs from analysis.performance.score (0-10). weight: type: integer nullable: true description: Max points for the criterion (2 for the 5 performance criteria); absent on the "Link present" item. suggestions_to_improve: type: string what_was_done_well: type: string analysis: type: object description: Two-axis analysis (Performance + Authenticity) with line-level slop detection. properties: verdict: type: string enum: [ready_to_post, needs_work] performance: type: object properties: score: type: integer description: 0–10 engagement potential. authenticity: type: object properties: score: type: integer description: 0–10, where 10 reads as unmistakably human. slop_risk: type: string enum: [low, medium, high] slop_issues: type: array description: Detected AI-slop patterns, worst-first. Empty when the post reads as genuine. items: type: object properties: dimension: type: string enum: [slop] description: Which axis the issue belongs to (currently always "slop"). severity: type: string enum: [low, medium, high] quoted_line: type: string description: The exact offending line from the post. why: type: string description: The slop pattern name and why it hurts. fix: type: string description: A human-readable explanation of the fix or action. rewrite: type: string description: >- The literal text to substitute for quoted_line, paste-ready with no quotes or commentary. An empty string means the line should be deleted. voice_note: type: array description: Bullet points on how the post fits the author's voice. Empty array when Content DNA is not configured. items: type: string "422": description: Invalid input or scoring failed content: application/json: schema: $ref: "#/components/schemas/Error" /posts/{id}/comments: post: summary: Create auto-plug comments description: | Add auto-plug comments to a post. These comments are automatically published after the post goes live, with a configurable delay. Cannot add comments to already-published posts. tags: [Posts] parameters: - $ref: "#/components/parameters/WorkspaceId" - name: id in: path required: true schema: type: string format: uuid requestBody: required: true content: application/json: schema: type: object required: [comments] properties: comments: type: array items: type: object required: [text, post_after_time_unit, post_after_time_unit_count] properties: text: type: string description: The comment text post_after_time_unit: type: string enum: [minutes, hours, days] description: Time unit for the delay after post publication post_after_time_unit_count: type: integer description: Number of time units to wait before posting the comment example: 5 responses: "201": description: Comments created content: application/json: schema: type: object properties: comments: type: array items: $ref: "#/components/schemas/AutoPlugComment" errors: type: array items: type: object "400": description: Cannot add comments to a published post "404": description: Post not found get: summary: List auto-plug comments description: Returns all auto-plug comments for a post. tags: [Posts] parameters: - $ref: "#/components/parameters/WorkspaceId" - name: id in: path required: true schema: type: string format: uuid responses: "200": description: List of comments content: application/json: schema: type: object properties: comments: type: array items: $ref: "#/components/schemas/AutoPlugComment" "404": description: Post not found /posts/{id}/comments/{comment_id}: delete: summary: Delete an auto-plug comment description: Deletes a specific auto-plug comment. Cannot delete comments on published posts. tags: [Posts] parameters: - $ref: "#/components/parameters/WorkspaceId" - name: id in: path required: true schema: type: string format: uuid - name: comment_id in: path required: true schema: type: string format: uuid responses: "200": description: Comment deleted "404": description: Comment not found "422": description: Cannot delete comments on a published post /company-pages: get: summary: List company pages description: | Returns all LinkedIn company pages connected to the workspace. Use the `id` from the response as `linked_in_company_page_id` when creating or queuing posts to publish from a company page. tags: [Company Pages] parameters: - $ref: "#/components/parameters/WorkspaceId" responses: "200": description: List of company pages content: application/json: schema: type: object properties: company_pages: type: array items: $ref: "#/components/schemas/CompanyPage" /media: post: summary: Create a media item (returns a presigned upload URL) description: | Reserves a MediaItem in `pending` status and returns a presigned S3-compatible POST URL the caller uses to upload the file directly to object storage. After the upload completes, call `POST /media/{id}/confirm` to flip status to `confirmed` and record the file size. Size limits enforced at upload time via `content-length-range`: images 10MB, documents 25MB, audio 50MB, video 200MB. tags: [Media] requestBody: required: true content: application/json: schema: type: object required: [workspace_id, content_type, file_name] properties: workspace_id: type: string format: uuid content_type: type: string description: MIME type (e.g. "image/jpeg", "application/pdf"). Determines media_type and size limit. Images must be `image/jpeg`, `image/png`, or `image/gif` (the formats LinkedIn accepts). file_name: type: string description: Original file name. Sanitized; the stored object_key uses a UUID + this base name. responses: "201": description: Pending MediaItem and presigned upload URL content: application/json: schema: type: object properties: media_item: $ref: "#/components/schemas/MediaItem" upload: type: object properties: url: type: string description: Object-storage endpoint to POST the file to (multipart form) fields: type: object description: Form fields that must be included in the multipart POST (e.g. policy, signature, key) key: type: string description: Final object_key the file will land at expires_in: type: integer description: Seconds until the presigned URL expires (default 3600) max_size: type: integer description: Maximum file size in bytes "401": description: Missing/invalid credential, or the authenticated user does not have access to this workspace "403": description: | Forbidden. Either: - Account is blocked (`errors: ["Your account has been blocked"]`) - Caller's plan is not on the paid tier (`error_code: "PAID_PLAN_REQUIRED"`) "404": description: Workspace not found "422": description: | Missing required fields, storage error, or an image `content_type` other than `image/jpeg`, `image/png`, or `image/gif` (`errors: ["Unsupported image format: image/webp. LinkedIn only supports JPG, PNG, and GIF."]`). get: summary: List media items description: Returns confirmed media items for a workspace. Supports filtering by media type and pagination. tags: [Media] parameters: - $ref: "#/components/parameters/WorkspaceId" - name: media_type in: query required: false schema: type: string enum: [image, video, audio, document] description: Filter by media type - name: page in: query required: false schema: type: integer default: 1 - name: per_page in: query required: false schema: type: integer default: 20 maximum: 100 responses: "200": description: List of confirmed media items content: application/json: schema: type: object properties: media_items: type: array items: $ref: "#/components/schemas/MediaItem" pagination: type: object properties: page: type: integer per_page: type: integer total: type: integer /media/{id}: get: summary: Get a media item description: Returns a single confirmed media item with a presigned read URL. tags: [Media] parameters: - $ref: "#/components/parameters/WorkspaceId" - name: id in: path required: true schema: type: string format: uuid responses: "200": description: Media item details content: application/json: schema: type: object properties: media_item: $ref: "#/components/schemas/MediaItem" "404": description: Media item not found /media/{id}/confirm: post: summary: Confirm an uploaded media item description: | Flips a `pending` MediaItem to `confirmed` after the file has been uploaded via the presigned URL from `POST /media`. The backend HEADs the object to verify it landed and records the file size. For images, it also checks the uploaded bytes. The declared `content_type` is not trusted: a file declared as `image/png` whose bytes are AVIF or WebP is rejected with 422, and the MediaItem and its object are deleted. Idempotent: confirming an already-confirmed item returns 200. tags: [Media] parameters: - name: id in: path required: true schema: type: string format: uuid requestBody: required: true content: application/json: schema: type: object required: [workspace_id] properties: workspace_id: type: string format: uuid responses: "200": description: Confirmed media item content: application/json: schema: type: object properties: media_item: $ref: "#/components/schemas/MediaItem" "401": description: Missing/invalid credential, or the authenticated user does not have access to this workspace "403": description: | Forbidden. Either: - Account is blocked (`errors: ["Your account has been blocked"]`) - Caller's plan is not on the paid tier (`error_code: "PAID_PLAN_REQUIRED"`) "404": description: Media item not found in this workspace "422": description: | Either: - File not found in storage (upload didn't complete). The item stays `pending`; retry the upload and confirm again. - Unsupported image format detected from the uploaded bytes (`errors: ["Unsupported image format: image/avif. LinkedIn only supports JPG, PNG, and GIF."]`). The MediaItem and its object are deleted, so this `id` is no longer valid. Convert the image to JPG, PNG, or GIF and start again with `POST /media`.
{"openapi":"3.0.3","info":{"title":"Supergrow External API","description":"The Supergrow External API allows you to programmatically manage workspaces, posts, content, and analytics for your LinkedIn content management workflow.\n\n## Authentication\nEvery request must carry one of:\n\n- **API key** in the `X-Api-Key` header. Org owners and admins create their key in the Supergrow app under Settings → MCP (one key per user).\n- **OAuth bearer token** in the `Authorization: Bearer \u003ctoken\u003e` header, issued by Supergrow's OAuth 2.1 server after the user consents. This is how MCP clients such as Claude connect. Tokens carry the same access as the user's API key.\n\nIf both are sent, the API key is used.\n\n## Optional Headers\n- `X-Skill-Version` — optional string sent by MCP/skill clients so the backend can track skill adoption. Free-form version string; safe to omit.\n\n## Rate Limits\n- **3,600 requests per hour** per user\n- **5,000 requests per day** per user\n- **Content extraction:** 10 extractions per day per workspace\n\n## MCP Server\nSupergrow offers an MCP (Model Context Protocol) server that lets you manage your LinkedIn content directly from AI tools like Claude. Contact support@supergrow.ai to learn more.\n","version":"1.1.0","contact":{"url":"https://help.supergrow.ai"}},"servers":[{"url":"https://external-api.supergrow.ai/api/v1/external","description":"Production"}],"security":[{"ApiKeyAuth":[]},{"BearerAuth":[]}],"components":{"securitySchemes":{"ApiKeyAuth":{"type":"apiKey","in":"header","name":"X-Api-Key"},"BearerAuth":{"type":"http","scheme":"bearer","bearerFormat":"JWT","description":"Access token issued by Supergrow's OAuth 2.1 server (used by MCP clients)."}},"schemas":{"Error":{"type":"object","properties":{"errors":{"type":"array","items":{"type":"string"}},"error_code":{"type":"string","nullable":true,"description":"Machine-readable error code (e.g. PAID_PLAN_REQUIRED)"}}},"Workspace":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"org_id":{"type":"string","format":"uuid","nullable":true,"description":"The organization this workspace belongs to. Null for personal (non-org) workspaces."},"name":{"type":"string"},"is_archived":{"type":"boolean"},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"},"role":{"type":"string","enum":["ADMIN","TEAM MEMBER","CLIENT","ORG_OWNER","ORG_ADMIN"]},"timezone":{"type":"string","nullable":true,"description":"Workspace timezone (e.g. \"Asia/Kolkata\"). Used for scheduling posts."},"language":{"type":"string","nullable":true,"description":"Workspace content language as an ISO 639-1 code (e.g. \"en\", \"hi\")."}}},"AutoPlugComment":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"post_id":{"type":"string","format":"uuid"},"text":{"type":"string"},"status":{"type":"string","enum":["draft","scheduled","published","failed"]},"post_after_time_unit":{"type":"string","enum":["minutes","hours","days"]},"post_after_time_unit_count":{"type":"integer"},"published_at":{"type":"string","format":"date-time","nullable":true},"created_at":{"type":"string","format":"date-time"}}},"MediaItem":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"workspace_id":{"type":"string","format":"uuid"},"file_name":{"type":"string"},"content_type":{"type":"string","description":"MIME type (e.g. image/jpeg, video/mp4, application/pdf)"},"media_type":{"type":"string","enum":["image","video","audio","document"]},"file_size":{"type":"integer","nullable":true,"description":"File size in bytes (set after confirmation)"},"status":{"type":"string","enum":["pending","confirmed"]},"url":{"type":"string","nullable":true,"description":"Presigned read URL (24h expiry). Only present for confirmed items."},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"}}},"CompanyPage":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"},"url":{"type":"string"},"logo_url":{"type":"string"},"page_id":{"type":"string","description":"LinkedIn company page ID"}}},"MediaAttachment":{"type":"object","description":"Hydrated read-only representation of a media item attached to a post. Returned in the `media_attachments` array. The `url` is a 24h presigned URL — do NOT cache or persist it; re-fetch the post to refresh.","properties":{"id":{"type":"string","format":"uuid"},"type":{"type":"string","enum":["image","video","document","audio"]},"file_name":{"type":"string"},"content_type":{"type":"string"},"byte_size":{"type":"integer","nullable":true},"url":{"type":"string","description":"24h presigned GET URL — short-lived, do not persist."},"preview_url":{"type":"string","nullable":true,"description":"Thumbnail/preview URL for videos. Null for non-video media."},"url_expires_at":{"type":"string","format":"date-time","description":"Wall-clock expiry of `url`. Re-fetch the post to obtain a fresh URL."},"position":{"type":"integer"}}},"Post":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"text":{"type":"string"},"media_attachments":{"type":"array","description":"Canonical read-only representation of attached media. Use this in your integration instead of the deprecated legacy URL fields.","items":{"$ref":"#/components/schemas/MediaAttachment"}},"image_urls":{"type":"array","deprecated":true,"description":"Legacy field. Populated for back-compat — derived from `media_attachments` when the post was written via `media_ids`. New integrations should read `media_attachments`.","items":{"type":"string"}},"status":{"type":"string","enum":["draft","scheduled","published","failed","under_review","approved","needs_changes"]},"published_at":{"type":"string","format":"date-time","nullable":true},"published_url":{"type":"string","nullable":true},"video_url":{"type":"string","nullable":true,"deprecated":true,"description":"Legacy field. See `media_attachments`."},"video_title":{"type":"string","nullable":true},"carousel_title":{"type":"string","nullable":true},"file_url":{"type":"string","nullable":true,"deprecated":true,"description":"Legacy field. See `media_attachments`."},"publish_from_type":{"type":"string","enum":["personal","company"],"description":"Whether the post is published from a personal profile or company page"},"linked_in_company_page_id":{"type":"string","format":"uuid","nullable":true,"description":"The company page ID if publish_from_type is \"company\""},"likes_count":{"type":"integer","nullable":true},"comments_count":{"type":"integer","nullable":true},"reshares_count":{"type":"integer","nullable":true},"impressions_count":{"type":"integer","nullable":true},"app_url":{"type":"string","nullable":true,"description":"URL to edit the post in the Supergrow app"},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"}}},"ContentDna":{"type":"object","nullable":true,"properties":{"id":{"type":"string","format":"uuid"},"status":{"type":"string","enum":["pending","analyzing","completed","failed","seeded"],"description":"Availability of the stored DNA. Existing completed or seeded DNA remains available during regeneration."},"regeneration_status":{"type":"string","nullable":true,"enum":["queued","running","completed","failed"]},"regeneration_kind":{"type":"string","nullable":true,"enum":["regenerate","import"],"description":"What started the latest run, a regeneration or an import of pasted writing details."},"regeneration_active":{"type":"boolean","description":"True while an unexpired regeneration request is queued or running."},"regeneration_error":{"type":"string","nullable":true,"description":"The latest regeneration attempt error, if any."},"linkedin_profile_url":{"type":"string","nullable":true},"name":{"type":"string"},"current_role":{"type":"string"},"company":{"type":"string"},"industry":{"type":"string"},"experience_years":{"type":"integer"},"seniority_level":{"type":"string"},"expertise_areas":{"type":"array","items":{"type":"string"}},"main_topics":{"type":"array","items":{"type":"string"}},"target_customers":{"type":"array","items":{"type":"object"}},"content_positioning":{"type":"object"},"writing_style":{"type":"object"},"content_patterns":{"type":"object"},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"}}},"LinkedInAccount":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"avatar_url":{"type":"string","nullable":true},"linked_in_profile_url":{"type":"string","nullable":true},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"}}},"WeeklyReport":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"week_start_date":{"type":"string","format":"date"},"week_end_date":{"type":"string","format":"date"},"week_label":{"type":"string"},"status":{"type":"string"},"target":{"type":"object","description":"The LinkedIn profile or company page this report covers.","properties":{"type":{"type":"string","enum":["personal","company_page"]},"linked_in_account_id":{"type":"string","nullable":true},"linked_in_company_page_id":{"type":"string","format":"uuid","nullable":true}}},"summary":{"type":"object","properties":{"posts_published":{"type":"integer"},"total_engagements":{"type":"integer"},"total_impressions":{"type":"integer"},"rolling_average_engagement":{"type":"number"},"vs_baseline_percentage":{"type":"number"},"vs_baseline_text":{"type":"string"}}},"best_performer":{"type":"object","nullable":true,"properties":{"post_id":{"type":"string"},"multiplier":{"type":"number"},"insight":{"type":"object"}}},"worst_performer":{"type":"object","nullable":true,"properties":{"post_id":{"type":"string"},"multiplier":{"type":"number"},"insight":{"type":"object"}}},"posts_breakdown":{"type":"array","items":{"type":"object"}},"audience_growth":{"type":"object","nullable":true},"created_at":{"type":"string","format":"date-time"}}},"DailyAnalytic":{"type":"object","properties":{"date":{"type":"string","format":"date"},"impressions":{"type":"integer"},"engagements":{"type":"integer"},"reactions":{"type":"integer"},"comments":{"type":"integer"},"reshares":{"type":"integer"},"members_reached":{"type":"integer"}}},"ExtractionResult":{"type":"object","properties":{"text":{"type":"string"},"url":{"type":"string"}}},"KnowledgeBaseItem":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"title":{"type":"string"},"description":{"type":"string","nullable":true},"source_type":{"type":"string","enum":["text","website","pdf","document","youtube","voice"]},"source_url":{"type":"string","nullable":true},"status":{"type":"string","enum":["draft","processing","ready","failed"]},"scope":{"type":"string","enum":["workspace","org"]},"chunks_count":{"type":"integer"},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"}}},"KnowledgeBaseSearchResultItem":{"type":"object","properties":{"score":{"type":"number","format":"float","description":"Relevance score (0–1, higher is better)"},"entry_type":{"type":"string","enum":["chunk","insight","chunk_with_insight"]},"chunk":{"type":"object","nullable":true,"description":"The source text chunk (present for chunk and chunk_with_insight entries)","properties":{"id":{"type":"string"},"content":{"type":"string"},"content_preview":{"type":"string"},"chunk_index":{"type":"integer"},"token_count":{"type":"integer"}}},"insight":{"type":"object","nullable":true,"description":"Extracted insight (present for insight and chunk_with_insight entries)","properties":{"type":{"type":"string","enum":["statistic","quote","milestone","product_feature","customer_proof_point","contrarian_claim"]},"text":{"type":"string"},"confidence":{"type":"string","enum":["high","medium","low"]}}},"knowledge_base_item":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"title":{"type":"string"},"description":{"type":"string","nullable":true},"source_type":{"type":"string"},"source_url":{"type":"string","nullable":true}}},"metadata":{"type":"object","description":"Additional metadata for the search result item."}}},"Pagination":{"type":"object","properties":{"total":{"type":"integer"},"page":{"type":"integer"},"per_page":{"type":"integer"},"pages":{"type":"integer"}}}},"parameters":{"WorkspaceId":{"name":"workspace_id","in":"query","required":true,"schema":{"type":"string","format":"uuid"},"description":"The ID of the workspace"}}},"paths":{"/workspaces":{"get":{"summary":"List all workspaces","description":"Returns all workspaces accessible to the authenticated user, including their role in each.","tags":["Workspaces"],"responses":{"200":{"description":"List of workspaces","content":{"application/json":{"schema":{"type":"object","properties":{"workspaces":{"type":"array","items":{"$ref":"#/components/schemas/Workspace"}}}}}}}}}},"/posts":{"get":{"summary":"List posts","description":"Lists posts in a workspace filtered by status.","tags":["Posts"],"parameters":[{"$ref":"#/components/parameters/WorkspaceId"},{"name":"status","in":"query","required":true,"schema":{"type":"string","enum":["draft","scheduled","published","failed","all"]}},{"name":"tag_ids","in":"query","required":false,"schema":{"type":"string"},"description":"Comma-separated tag IDs"}],"responses":{"200":{"description":"List of posts","content":{"application/json":{"schema":{"type":"object","properties":{"posts":{"type":"array","items":{"$ref":"#/components/schemas/Post"}}}}}}}}},"post":{"summary":"Create a post","description":"Creates a new draft post in a workspace. Optionally publish from a company page by providing `linked_in_company_page_id`.\nUse `GET /company-pages` to list available company pages.\n","tags":["Posts"],"parameters":[{"$ref":"#/components/parameters/WorkspaceId"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["text"],"properties":{"text":{"type":"string","maxLength":3000},"media_ids":{"type":"array","description":"Canonical write-side media contract. UUIDs of MediaItems (uploaded via POST /media). When present, the server derives legacy URL fields (image_urls, file_url, video_url) from the media items by type — clients should use this instead of the deprecated legacy fields. Order is preserved as display order.","items":{"type":"string","format":"uuid"}},"image_urls":{"type":"array","deprecated":true,"description":"Legacy write field. Still accepted for back-compat; signed URLs are rejected with 422. Prefer `media_ids`.","items":{"type":"string"}},"file_url":{"type":"string","deprecated":true,"description":"Legacy write field. Prefer `media_ids`."},"carousel_title":{"type":"string"},"video_url":{"type":"string","deprecated":true,"description":"Legacy write field. Prefer `media_ids`."},"video_title":{"type":"string"},"video_thumbnail_url":{"type":"string","deprecated":true,"description":"Legacy write field. Prefer `media_ids`."},"linked_in_company_page_id":{"type":"string","format":"uuid","description":"Optional. If provided, the post will be published from this company page instead of the personal profile. Use GET /company-pages to list available pages."}}}}}},"responses":{"201":{"description":"Post created","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string"},"post":{"$ref":"#/components/schemas/Post"}}}}}}}}},"/posts/{id}":{"get":{"summary":"Get a post","description":"Get details for a specific post.","tags":["Posts"],"parameters":[{"$ref":"#/components/parameters/WorkspaceId"},{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Post details","content":{"application/json":{"schema":{"type":"object","properties":{"post":{"$ref":"#/components/schemas/Post"}}}}}}}},"put":{"summary":"Update a post","description":"Updates an existing draft post.","tags":["Posts"],"parameters":[{"$ref":"#/components/parameters/WorkspaceId"},{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["text"],"properties":{"text":{"type":"string","maxLength":3000},"media_ids":{"type":"array","description":"Canonical write-side media contract. UUIDs of MediaItems (uploaded via POST /media). When present, the server derives legacy URL fields from the media items by type. Order is preserved.","items":{"type":"string","format":"uuid"}},"image_urls":{"type":"array","deprecated":true,"description":"Legacy write field. Prefer `media_ids`.","items":{"type":"string"}},"file_url":{"type":"string","deprecated":true,"description":"Legacy write field. Prefer `media_ids`."},"carousel_title":{"type":"string"},"video_url":{"type":"string","deprecated":true,"description":"Legacy write field. Prefer `media_ids`."},"video_title":{"type":"string"},"video_thumbnail_url":{"type":"string","deprecated":true,"description":"Legacy write field. Prefer `media_ids`."}}}}}},"responses":{"200":{"description":"Post updated"}}},"delete":{"summary":"Delete a post","description":"Deletes a draft or failed post.","tags":["Posts"],"parameters":[{"$ref":"#/components/parameters/WorkspaceId"},{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Post deleted"}}}},"/posts/{id}/schedule":{"post":{"summary":"Schedule a post","description":"Schedules a post for publication at a specific time. Pass `hour` in the workspace's local timezone (NOT UTC). For example, for 2 PM IST pass hour=14.","tags":["Posts"],"parameters":[{"$ref":"#/components/parameters/WorkspaceId"},{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"day_of_week":{"type":"integer","description":"Day of week (0=Sunday, 1=Monday, ..., 6=Saturday)"},"hour":{"type":"integer","minimum":0,"maximum":23,"description":"Hour in the workspace's local timezone (0-23). Do NOT convert to UTC."},"minute":{"type":"integer","minimum":0,"maximum":59},"day_of_the_month":{"type":"integer","minimum":1,"maximum":31},"month":{"type":"integer","minimum":1,"maximum":12},"year":{"type":"integer"}}}}}},"responses":{"200":{"description":"Post scheduled"}}}},"/posts/{id}/unschedule":{"post":{"summary":"Unschedule a post","description":"Removes scheduling and reverts post to draft.","tags":["Posts"],"parameters":[{"$ref":"#/components/parameters/WorkspaceId"},{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Post unscheduled"}}}},"/posts/{id}/publish":{"post":{"summary":"Publish a post","description":"Immediately initiates the publishing process (asynchronous).","tags":["Posts"],"parameters":[{"$ref":"#/components/parameters/WorkspaceId"},{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Publishing initiated"}}}},"/posts/queue":{"post":{"summary":"Queue a post","description":"Creates a post and auto-schedules it in the next available time slot.\nOptionally publish from a company page by providing `linked_in_company_page_id`.\n","tags":["Posts"],"parameters":[{"$ref":"#/components/parameters/WorkspaceId"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["text"],"properties":{"text":{"type":"string","maxLength":3000},"media_ids":{"type":"array","description":"Canonical write-side media contract. UUIDs of MediaItems (uploaded via POST /media). When present, derives legacy URL fields from MediaItem types. Order is preserved.","items":{"type":"string","format":"uuid"}},"image_urls":{"type":"array","deprecated":true,"description":"Legacy write field. Prefer `media_ids`.","items":{"type":"string"}},"file_url":{"type":"string","deprecated":true,"description":"Legacy write field. Prefer `media_ids`."},"carousel_title":{"type":"string"},"video_url":{"type":"string","deprecated":true,"description":"Legacy write field. Prefer `media_ids`."},"video_title":{"type":"string"},"linked_in_company_page_id":{"type":"string","format":"uuid","description":"Optional. Publish from a company page instead of personal profile."}}}}}},"responses":{"201":{"description":"Post created and scheduled"}}}},"/posts/{id}/public-link":{"post":{"summary":"Create public preview link","description":"Creates a public link to preview a post. Returns the existing link if one already exists.","tags":["Posts"],"parameters":[{"$ref":"#/components/parameters/WorkspaceId"},{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Public link created","content":{"application/json":{"schema":{"type":"object","properties":{"public_url":{"type":"string","format":"uri"}}}}}},"404":{"description":"Post not found"}}},"delete":{"summary":"Remove public preview link","description":"Removes the public preview link from a post.","tags":["Posts"],"parameters":[{"$ref":"#/components/parameters/WorkspaceId"},{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Public link removed"}}}},"/content-dna":{"get":{"summary":"Get Content DNA","description":"Returns the Content DNA profile for the workspace — professional profile, writing style, content patterns, and target audience.","tags":["Content DNA"],"parameters":[{"$ref":"#/components/parameters/WorkspaceId"}],"responses":{"200":{"description":"Content DNA profile","content":{"application/json":{"schema":{"type":"object","properties":{"content_dna":{"$ref":"#/components/schemas/ContentDna"}}}}}}}}},"/content-dna/regenerate":{"post":{"summary":"Regenerate Content DNA","description":"Triggers Content DNA regeneration from the member's published history.\nNo request body needed. LinkedIn is primary when an account or stored DNA profile URL exists.\nLinkedIn generation has no minimum post count and can use profile data alone.\nIf LinkedIn post fetching fails or no URL exists, the Supergrow fallback requires at least\nfive distinct, original, non-empty published posts.\nAmbiguous account selection or insufficient history without a URL returns 422.\nLinkedIn fetching runs in the job. Failed generation preserves the current DNA.\nExisting DNA remains available until generation succeeds. Read regeneration_status\nand regeneration_active to track progress. Regeneration replaces personal edits,\nretains populated current organization template values, and preserves existing restrictions.\nDuplicate active requests return 409. A new request can reclaim an expired one-hour lease.\n","tags":["Content DNA"],"parameters":[{"$ref":"#/components/parameters/WorkspaceId"}],"responses":{"200":{"description":"Regeneration started","content":{"application/json":{"schema":{"type":"object","properties":{"message":{"type":"string"},"content_dna":{"$ref":"#/components/schemas/ContentDna"}}}}}},"409":{"description":"Content DNA regeneration is already in progress","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"Failed to enqueue regeneration; the request claim has been released","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"Insufficient published history without a profile URL, invalid URL, or ambiguous member account","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/linked-in-accounts":{"get":{"summary":"Get LinkedIn accounts","description":"Returns LinkedIn accounts connected to the workspace. Sensitive tokens are never exposed.","tags":["LinkedIn Accounts"],"parameters":[{"$ref":"#/components/parameters/WorkspaceId"}],"responses":{"200":{"description":"List of LinkedIn accounts","content":{"application/json":{"schema":{"type":"object","properties":{"linked_in_accounts":{"type":"array","items":{"$ref":"#/components/schemas/LinkedInAccount"}}}}}}}}}},"/calendar/weekly":{"get":{"summary":"Get weekly calendar","description":"Returns the weekly calendar view with time slots and scheduled posts.","tags":["Calendar"],"parameters":[{"$ref":"#/components/parameters/WorkspaceId"},{"name":"offset","in":"query","schema":{"type":"integer","default":0},"description":"Week offset (0=current, 1=next, -1=last)"}],"responses":{"200":{"description":"Weekly calendar","content":{"application/json":{"schema":{"type":"object","properties":{"calendar":{"type":"object","description":"Weekly calendar data with time slots and associated posts"},"timezone":{"type":"string","example":"America/New_York","description":"The workspace's configured timezone"}}}}}}}}},"/calendar/monthly":{"get":{"summary":"Get monthly calendar","description":"Returns the monthly calendar view with time slots and scheduled posts.","tags":["Calendar"],"parameters":[{"$ref":"#/components/parameters/WorkspaceId"},{"name":"offset","in":"query","schema":{"type":"integer","default":0},"description":"Month offset (0=current, 1=next, -1=last)"}],"responses":{"200":{"description":"Monthly calendar","content":{"application/json":{"schema":{"type":"object","properties":{"calendar":{"type":"object","description":"Monthly calendar data with time slots and associated posts"},"timezone":{"type":"string","example":"America/New_York","description":"The workspace's configured timezone"}}}}}}}}},"/kanban":{"get":{"summary":"Get kanban board","description":"Returns the kanban board with posts grouped by status.","tags":["Kanban"],"parameters":[{"$ref":"#/components/parameters/WorkspaceId"},{"name":"page","in":"query","schema":{"type":"integer","default":1}},{"name":"per_page","in":"query","schema":{"type":"integer","default":10,"maximum":100}}],"responses":{"200":{"description":"Kanban board"}}}},"/kanban/column":{"get":{"summary":"Get kanban column","description":"Returns posts for a single status column.","tags":["Kanban"],"parameters":[{"$ref":"#/components/parameters/WorkspaceId"},{"name":"status","in":"query","required":true,"schema":{"type":"string","enum":["draft","under_review","approved","needs_changes","scheduled","published"]}},{"name":"page","in":"query","schema":{"type":"integer","default":1}},{"name":"per_page","in":"query","schema":{"type":"integer","default":10,"maximum":100}}],"responses":{"200":{"description":"Kanban column"}}}},"/analytics/weekly-reports":{"get":{"summary":"Get weekly reports","description":"Returns weekly content performance reports for the workspace.\nBy default only personal-profile reports are returned (one per week).\nUse target_type to fetch company-page reports or all reports.\n","tags":["Analytics"],"parameters":[{"$ref":"#/components/parameters/WorkspaceId"},{"name":"limit","in":"query","schema":{"type":"integer","default":12,"maximum":52}},{"name":"target_type","in":"query","schema":{"type":"string","enum":["personal","company_page","all"],"default":"personal"},"description":"Filter reports by target. Each report includes a `target` object identifying its LinkedIn account or company page."}],"responses":{"200":{"description":"List of weekly reports","content":{"application/json":{"schema":{"type":"object","properties":{"weekly_reports":{"type":"array","items":{"$ref":"#/components/schemas/WeeklyReport"}}}}}}}}}},"/analytics/followers":{"get":{"summary":"Get live followers data","description":"Returns live follower count and trend data from LinkedIn API.\nRate limited to 500 live analytics calls per day per workspace.\n","tags":["Analytics"],"parameters":[{"$ref":"#/components/parameters/WorkspaceId"},{"name":"period","in":"query","schema":{"type":"string","enum":["last_7_days","last_30_days","current_month","previous_month"],"default":"last_7_days"},"description":"Predefined period (ignored if start_date/end_date provided)"},{"name":"start_date","in":"query","schema":{"type":"string","format":"date"},"description":"Custom start date (YYYY-MM-DD). Use with end_date."},{"name":"end_date","in":"query","schema":{"type":"string","format":"date"},"description":"Custom end date (YYYY-MM-DD). Use with start_date."}],"responses":{"200":{"description":"Followers data","content":{"application/json":{"schema":{"type":"object","properties":{"current_count":{"type":"integer"},"trend":{"type":"object"}}}}}},"422":{"description":"No LinkedIn account connected"}}}},"/analytics/metrics":{"get":{"summary":"Get live engagement metrics","description":"Returns live engagement metric trends from LinkedIn API.\nSupported metrics: IMPRESSION, MEMBERS_REACHED, RESHARE, REACTION, COMMENT.\nRate limited to 500 live analytics calls per day per workspace.\n","tags":["Analytics"],"parameters":[{"$ref":"#/components/parameters/WorkspaceId"},{"name":"metric","in":"query","schema":{"type":"string","enum":["IMPRESSION","MEMBERS_REACHED","RESHARE","REACTION","COMMENT"],"default":"IMPRESSION"}},{"name":"period","in":"query","schema":{"type":"string","enum":["last_7_days","last_30_days","current_month","previous_month"],"default":"last_7_days"},"description":"Predefined period (ignored if start_date/end_date provided)"},{"name":"start_date","in":"query","schema":{"type":"string","format":"date"},"description":"Custom start date (YYYY-MM-DD). Use with end_date."},{"name":"end_date","in":"query","schema":{"type":"string","format":"date"},"description":"Custom end date (YYYY-MM-DD). Use with start_date."}],"responses":{"200":{"description":"Metric trend data","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"type":"object"}},"metric_type":{"type":"string"}}}}}},"422":{"description":"No LinkedIn account connected or invalid metric"}}}},"/extract":{"post":{"summary":"Extract text (auto-detect)","description":"Automatically detects URL type (webpage, YouTube, PDF, audio) and extracts text. Limited to 10 extractions per day per workspace.","tags":["Content Extraction"],"parameters":[{"$ref":"#/components/parameters/WorkspaceId"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["url"],"properties":{"url":{"type":"string","format":"uri"}}}}}},"responses":{"200":{"description":"Extracted text","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ExtractionResult"}}}},"429":{"description":"Daily extraction limit reached","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/extract/youtube":{"post":{"summary":"Extract YouTube transcript","description":"Extracts the transcript from a YouTube video. Limited to 10 extractions per day per workspace.","tags":["Content Extraction"],"parameters":[{"$ref":"#/components/parameters/WorkspaceId"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["url"],"properties":{"url":{"type":"string","format":"uri"}}}}}},"responses":{"200":{"description":"YouTube transcript","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ExtractionResult"}}}}}}},"/extract/pdf":{"post":{"summary":"Extract PDF text","description":"Extracts text content from a PDF file. Limited to 10 extractions per day per workspace.","tags":["Content Extraction"],"parameters":[{"$ref":"#/components/parameters/WorkspaceId"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["url"],"properties":{"url":{"type":"string","format":"uri"}}}}}},"responses":{"200":{"description":"PDF text","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ExtractionResult"}}}}}}},"/extract/audio":{"post":{"summary":"Transcribe audio","description":"Transcribes audio or video files to text. Limited to 10 extractions per day per workspace.\nSupported formats: mp3, mp4, wav, m4a, aac, ogg, flac, wma, avi, mov, wmv, flv, webm, mkv.\n","tags":["Content Extraction"],"parameters":[{"$ref":"#/components/parameters/WorkspaceId"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["url"],"properties":{"url":{"type":"string","format":"uri"}}}}}},"responses":{"200":{"description":"Transcribed text","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ExtractionResult"}}}}}}},"/questions/from-topic":{"post":{"summary":"Generate questions from a topic","description":"Generates 8-12 interview-style questions for a given topic. Questions are personalized\nusing the workspace's Content DNA (if available) and designed to extract insights\nfor LinkedIn content creation. Each question includes follow-up prompts.\n","tags":["Questions"],"parameters":[{"$ref":"#/components/parameters/WorkspaceId"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["topic"],"properties":{"topic":{"type":"string","description":"The topic to generate questions about"}}}}}},"responses":{"200":{"description":"Generated questions","content":{"application/json":{"schema":{"type":"object","properties":{"questions":{"type":"array","items":{"type":"object","properties":{"question":{"type":"string"},"follow_ups":{"type":"array","items":{"type":"string"}}}}}}}}}},"422":{"description":"Missing topic or generation failed"}}}},"/questions/from-text":{"post":{"summary":"Generate questions from text content","description":"Analyzes text content (article, blog post, transcript, etc.) and generates\ninterview-style questions to help extract insights for LinkedIn posts.\nQuestions are self-contained with context from the text.\n","tags":["Questions"],"parameters":[{"$ref":"#/components/parameters/WorkspaceId"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["text"],"properties":{"text":{"type":"string","description":"The text content to analyze and generate questions from"}}}}}},"responses":{"200":{"description":"Generated questions","content":{"application/json":{"schema":{"type":"object","properties":{"questions":{"type":"array","items":{"type":"object","properties":{"question":{"type":"string"},"follow_ups":{"type":"array","items":{"type":"string"}},"intent":{"type":"string","enum":["comprehension","application","critical_thinking","synthesis","data_analysis"]}}}}}}}}},"422":{"description":"Missing text or generation failed"}}}},"/knowledge-base/ingest":{"post":{"summary":"Ingest text content","description":"Ingests plain text into the workspace knowledge base. The content is chunked, embedded, and indexed asynchronously. Returns immediately with status \"processing\".","tags":["Knowledge Base"],"parameters":[{"$ref":"#/components/parameters/WorkspaceId"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["content"],"properties":{"content":{"type":"string","description":"The text content to ingest"},"title":{"type":"string","description":"Optional title for the knowledge base item"}}}}}},"responses":{"201":{"description":"Ingestion started","content":{"application/json":{"schema":{"type":"object","properties":{"knowledge_base_item":{"$ref":"#/components/schemas/KnowledgeBaseItem"}}}}}},"401":{"description":"Unauthorized or workspace access denied","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"content parameter missing or ingestion failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/knowledge-base":{"get":{"summary":"List knowledge base items","description":"Returns a paginated list of knowledge base items in the workspace.","tags":["Knowledge Base"],"parameters":[{"$ref":"#/components/parameters/WorkspaceId"},{"name":"source_type","in":"query","schema":{"type":"string","enum":["text","website","pdf","document","youtube","voice"]},"description":"Filter by source type"},{"name":"status","in":"query","schema":{"type":"string","enum":["draft","processing","ready","failed"]},"description":"Filter by status"},{"name":"page","in":"query","schema":{"type":"integer","default":1}},{"name":"per_page","in":"query","schema":{"type":"integer","default":20,"maximum":100}}],"responses":{"200":{"description":"List of knowledge base items","content":{"application/json":{"schema":{"type":"object","properties":{"knowledge_base_items":{"type":"array","items":{"$ref":"#/components/schemas/KnowledgeBaseItem"}},"total_count":{"type":"integer"},"page":{"type":"integer"},"per_page":{"type":"integer"}}}}}}}}},"/knowledge-base/{id}":{"get":{"summary":"Get a knowledge base item","description":"Returns a single knowledge base item by ID.","tags":["Knowledge Base"],"parameters":[{"$ref":"#/components/parameters/WorkspaceId"},{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Knowledge base item","content":{"application/json":{"schema":{"type":"object","properties":{"knowledge_base_item":{"$ref":"#/components/schemas/KnowledgeBaseItem"}}}}}},"404":{"description":"Item not found or belongs to a different workspace","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"delete":{"summary":"Delete a knowledge base item","description":"Permanently deletes a knowledge base item and its associated chunks.","tags":["Knowledge Base"],"parameters":[{"$ref":"#/components/parameters/WorkspaceId"},{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Item deleted","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","example":"ok"}}}}}},"404":{"description":"Item not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/knowledge-base/search":{"post":{"summary":"Search the knowledge base","description":"Performs semantic vector search across the workspace knowledge base. Returns both raw text chunks and extracted insights ranked by relevance score.\n\nResults include:\n- **chunks** — raw text segments from ingested documents\n- **insights** — structured data points (statistics, quotes, milestones, etc.) extracted from chunks by AI\n","tags":["Knowledge Base"],"parameters":[{"$ref":"#/components/parameters/WorkspaceId"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["query"],"properties":{"query":{"type":"string","description":"The search query"},"source_type":{"type":"string","enum":["text","website","pdf","document","youtube","voice"],"description":"Filter results to a specific source type"},"include_insights":{"type":"boolean","default":true,"description":"Set to false to return raw chunks only"},"similarity_threshold":{"type":"number","description":"Similarity threshold between 0 and 1 for filtering results"},"limit":{"type":"integer","default":10,"minimum":1,"maximum":50}}}}}},"responses":{"200":{"description":"Search results","content":{"application/json":{"schema":{"type":"object","properties":{"query":{"type":"string"},"total_results":{"type":"integer"},"results":{"type":"array","items":{"$ref":"#/components/schemas/KnowledgeBaseSearchResultItem"}}}}}}},"401":{"description":"Unauthorized or workspace access denied","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"query parameter missing","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/post-score":{"post":{"summary":"Score a LinkedIn post","description":"Analyzes a LinkedIn post on two axes and returns actionable, line-level feedback.\n\nThe response is a **backward-compatible superset**:\n\n- `score` — the per-criteria breakdown (unchanged). Existing integrations can keep\n reading this array exactly as before.\n- `analysis` — added, two-axis analysis:\n - **Performance** (`analysis.performance.score`, 0–10): will the post engage? —\n hook, clarity, readability, CTA, originality.\n - **Authenticity** (`analysis.authenticity.score`, 0–10 + `slop_risk`): does the post\n read as a genuine human, or as AI slop? `analysis.slop_issues` lists each detected\n slop pattern, worst-first, with the **exact offending line quoted** (`quoted_line`), a\n human `fix` explanation, and a **paste-ready `rewrite`** (the literal replacement text;\n empty string = delete the line). The list is empty when the post reads as genuine human writing.\n - `analysis.verdict` — `ready_to_post` (Performance ≥ 7 and slop risk low) or `needs_work`.\n - `analysis.voice_note` — how the post fits the author's voice (when Content DNA is configured).\n\nSlop detection is voice-aware: when a workspace has Content DNA, a writer's genuinely\nblunt or personal style is not flagged as slop.\n","tags":["Post Score"],"parameters":[{"$ref":"#/components/parameters/WorkspaceId"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["text"],"properties":{"text":{"type":"string","description":"The LinkedIn post text to score"}}}}}},"responses":{"200":{"description":"Post score — per-criteria breakdown plus two-axis analysis","content":{"application/json":{"schema":{"type":"object","properties":{"score":{"type":"array","description":"Per-criteria performance breakdown: 5 criteria (Hook Quality, Clarity and Coherency, Easy to read, Call-To-Action, Originality), each scored 0-2 with weight 2. Key on `criteria_title` (stable) — `criteria_name` is a descriptive question that varies per call. The overall /10 (`analysis.performance.score`) is the sum of the 5 criterion scores. When the post contains a link, an additional \"Link present\" item is appended with score -1 and no `weight` (a reach penalty), so the array can have 6 items.\n","items":{"type":"object","properties":{"criteria_name":{"type":"string","description":"Descriptive question for the criterion. Free-form and NOT stable across calls — do not key on it."},"criteria_title":{"type":"string","description":"Stable criterion key — use this to identify the criterion. \"Link present\" appears only when the post contains a link.","enum":["Hook Quality","Clarity and Coherency","Easy to read","Call-To-Action","Originality","Link present"]},"score":{"type":"integer","minimum":-1,"maximum":2,"description":"0-2 for the 5 performance criteria; -1 for the \"Link present\" penalty item. Differs from analysis.performance.score (0-10)."},"weight":{"type":"integer","nullable":true,"description":"Max points for the criterion (2 for the 5 performance criteria); absent on the \"Link present\" item."},"suggestions_to_improve":{"type":"string"},"what_was_done_well":{"type":"string"}}}},"analysis":{"type":"object","description":"Two-axis analysis (Performance + Authenticity) with line-level slop detection.","properties":{"verdict":{"type":"string","enum":["ready_to_post","needs_work"]},"performance":{"type":"object","properties":{"score":{"type":"integer","description":"0–10 engagement potential."}}},"authenticity":{"type":"object","properties":{"score":{"type":"integer","description":"0–10, where 10 reads as unmistakably human."},"slop_risk":{"type":"string","enum":["low","medium","high"]}}},"slop_issues":{"type":"array","description":"Detected AI-slop patterns, worst-first. Empty when the post reads as genuine.","items":{"type":"object","properties":{"dimension":{"type":"string","enum":["slop"],"description":"Which axis the issue belongs to (currently always \"slop\")."},"severity":{"type":"string","enum":["low","medium","high"]},"quoted_line":{"type":"string","description":"The exact offending line from the post."},"why":{"type":"string","description":"The slop pattern name and why it hurts."},"fix":{"type":"string","description":"A human-readable explanation of the fix or action."},"rewrite":{"type":"string","description":"The literal text to substitute for quoted_line, paste-ready with no quotes or commentary. An empty string means the line should be deleted."}}}},"voice_note":{"type":"array","description":"Bullet points on how the post fits the author's voice. Empty array when Content DNA is not configured.","items":{"type":"string"}}}}}}}}},"422":{"description":"Invalid input or scoring failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/posts/{id}/comments":{"post":{"summary":"Create auto-plug comments","description":"Add auto-plug comments to a post. These comments are automatically published after the post goes live,\nwith a configurable delay. Cannot add comments to already-published posts.\n","tags":["Posts"],"parameters":[{"$ref":"#/components/parameters/WorkspaceId"},{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["comments"],"properties":{"comments":{"type":"array","items":{"type":"object","required":["text","post_after_time_unit","post_after_time_unit_count"],"properties":{"text":{"type":"string","description":"The comment text"},"post_after_time_unit":{"type":"string","enum":["minutes","hours","days"],"description":"Time unit for the delay after post publication"},"post_after_time_unit_count":{"type":"integer","description":"Number of time units to wait before posting the comment","example":5}}}}}}}}},"responses":{"201":{"description":"Comments created","content":{"application/json":{"schema":{"type":"object","properties":{"comments":{"type":"array","items":{"$ref":"#/components/schemas/AutoPlugComment"}},"errors":{"type":"array","items":{"type":"object"}}}}}}},"400":{"description":"Cannot add comments to a published post"},"404":{"description":"Post not found"}}},"get":{"summary":"List auto-plug comments","description":"Returns all auto-plug comments for a post.","tags":["Posts"],"parameters":[{"$ref":"#/components/parameters/WorkspaceId"},{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"List of comments","content":{"application/json":{"schema":{"type":"object","properties":{"comments":{"type":"array","items":{"$ref":"#/components/schemas/AutoPlugComment"}}}}}}},"404":{"description":"Post not found"}}}},"/posts/{id}/comments/{comment_id}":{"delete":{"summary":"Delete an auto-plug comment","description":"Deletes a specific auto-plug comment. Cannot delete comments on published posts.","tags":["Posts"],"parameters":[{"$ref":"#/components/parameters/WorkspaceId"},{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}},{"name":"comment_id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Comment deleted"},"404":{"description":"Comment not found"},"422":{"description":"Cannot delete comments on a published post"}}}},"/company-pages":{"get":{"summary":"List company pages","description":"Returns all LinkedIn company pages connected to the workspace.\nUse the `id` from the response as `linked_in_company_page_id` when creating or queuing posts to publish from a company page.\n","tags":["Company Pages"],"parameters":[{"$ref":"#/components/parameters/WorkspaceId"}],"responses":{"200":{"description":"List of company pages","content":{"application/json":{"schema":{"type":"object","properties":{"company_pages":{"type":"array","items":{"$ref":"#/components/schemas/CompanyPage"}}}}}}}}}},"/media":{"post":{"summary":"Create a media item (returns a presigned upload URL)","description":"Reserves a MediaItem in `pending` status and returns a presigned\nS3-compatible POST URL the caller uses to upload the file directly\nto object storage. After the upload completes, call\n`POST /media/{id}/confirm` to flip status to `confirmed` and\nrecord the file size.\n\nSize limits enforced at upload time via `content-length-range`:\nimages 10MB, documents 25MB, audio 50MB, video 200MB.\n","tags":["Media"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["workspace_id","content_type","file_name"],"properties":{"workspace_id":{"type":"string","format":"uuid"},"content_type":{"type":"string","description":"MIME type (e.g. \"image/jpeg\", \"application/pdf\"). Determines media_type and size limit. Images must be `image/jpeg`, `image/png`, or `image/gif` (the formats LinkedIn accepts)."},"file_name":{"type":"string","description":"Original file name. Sanitized; the stored object_key uses a UUID + this base name."}}}}}},"responses":{"201":{"description":"Pending MediaItem and presigned upload URL","content":{"application/json":{"schema":{"type":"object","properties":{"media_item":{"$ref":"#/components/schemas/MediaItem"},"upload":{"type":"object","properties":{"url":{"type":"string","description":"Object-storage endpoint to POST the file to (multipart form)"},"fields":{"type":"object","description":"Form fields that must be included in the multipart POST (e.g. policy, signature, key)"},"key":{"type":"string","description":"Final object_key the file will land at"},"expires_in":{"type":"integer","description":"Seconds until the presigned URL expires (default 3600)"},"max_size":{"type":"integer","description":"Maximum file size in bytes"}}}}}}}},"401":{"description":"Missing/invalid credential, or the authenticated user does not have access to this workspace"},"403":{"description":"Forbidden. Either:\n- Account is blocked (`errors: [\"Your account has been blocked\"]`)\n- Caller's plan is not on the paid tier (`error_code: \"PAID_PLAN_REQUIRED\"`)\n"},"404":{"description":"Workspace not found"},"422":{"description":"Missing required fields, storage error, or an image `content_type`\nother than `image/jpeg`, `image/png`, or `image/gif`\n(`errors: [\"Unsupported image format: image/webp. LinkedIn only supports JPG, PNG, and GIF.\"]`).\n"}}},"get":{"summary":"List media items","description":"Returns confirmed media items for a workspace. Supports filtering by media type and pagination.","tags":["Media"],"parameters":[{"$ref":"#/components/parameters/WorkspaceId"},{"name":"media_type","in":"query","required":false,"schema":{"type":"string","enum":["image","video","audio","document"]},"description":"Filter by media type"},{"name":"page","in":"query","required":false,"schema":{"type":"integer","default":1}},{"name":"per_page","in":"query","required":false,"schema":{"type":"integer","default":20,"maximum":100}}],"responses":{"200":{"description":"List of confirmed media items","content":{"application/json":{"schema":{"type":"object","properties":{"media_items":{"type":"array","items":{"$ref":"#/components/schemas/MediaItem"}},"pagination":{"type":"object","properties":{"page":{"type":"integer"},"per_page":{"type":"integer"},"total":{"type":"integer"}}}}}}}}}}},"/media/{id}":{"get":{"summary":"Get a media item","description":"Returns a single confirmed media item with a presigned read URL.","tags":["Media"],"parameters":[{"$ref":"#/components/parameters/WorkspaceId"},{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Media item details","content":{"application/json":{"schema":{"type":"object","properties":{"media_item":{"$ref":"#/components/schemas/MediaItem"}}}}}},"404":{"description":"Media item not found"}}}},"/media/{id}/confirm":{"post":{"summary":"Confirm an uploaded media item","description":"Flips a `pending` MediaItem to `confirmed` after the file has been\nuploaded via the presigned URL from `POST /media`. The backend\nHEADs the object to verify it landed and records the file size.\nFor images, it also checks the uploaded bytes. The declared\n`content_type` is not trusted: a file declared as `image/png` whose\nbytes are AVIF or WebP is rejected with 422, and the MediaItem and\nits object are deleted.\nIdempotent: confirming an already-confirmed item returns 200.\n","tags":["Media"],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["workspace_id"],"properties":{"workspace_id":{"type":"string","format":"uuid"}}}}}},"responses":{"200":{"description":"Confirmed media item","content":{"application/json":{"schema":{"type":"object","properties":{"media_item":{"$ref":"#/components/schemas/MediaItem"}}}}}},"401":{"description":"Missing/invalid credential, or the authenticated user does not have access to this workspace"},"403":{"description":"Forbidden. Either:\n- Account is blocked (`errors: [\"Your account has been blocked\"]`)\n- Caller's plan is not on the paid tier (`error_code: \"PAID_PLAN_REQUIRED\"`)\n"},"404":{"description":"Media item not found in this workspace"},"422":{"description":"Either:\n- File not found in storage (upload didn't complete). The item stays `pending`; retry the upload and confirm again.\n- Unsupported image format detected from the uploaded bytes\n (`errors: [\"Unsupported image format: image/avif. LinkedIn only supports JPG, PNG, and GIF.\"]`).\n The MediaItem and its object are deleted, so this `id` is no longer valid.\n Convert the image to JPG, PNG, or GIF and start again with `POST /media`.\n"}}}}}}