openapi: 3.0.3 info: title: 'Subsig API Documentation' description: '' version: 1.0.0 servers: - url: 'https://backend-staging.subsig.com' tags: - name: Registration description: 'Create a new user account to access the application.' - name: Authentication description: "\nAPIs for user authentication" - name: 'Password Reset' description: 'Recover access to your account if you forgot your password.' - name: Reviews description: "\nAPIs for fetching aggregated reviews from multiple platforms, scoped to organizations." - name: 'Social Posts' description: "\nAPIs for fetching and filtering social media mentions from multiple platforms, scoped to organizations." - name: Endpoints description: '' - name: Invites description: "\nAPIs for managing invites" - name: Links description: "\nAPIs for managing project links" - name: Notifications description: "\nAPIs for managing notification rules" - name: Onboarding description: "\nAPIs for user onboarding flow" - name: Organisations description: "\nAPIs for managing organisations" - name: Platforms description: "\nAPIs for managing platforms" - name: Profiles description: "\nAPIs for managing product claims and product profile data.\n\nThe Profiles API group covers two related workflows:\n\n1. **Claim Profiles** - Submit and manage ownership claims for products\n2. **Product Profiles** - Edit and sync detailed product information" - name: Projects description: "\nAPIs for managing projects within organisations" - name: 'Review Views' description: "\nAPIs for managing saved review filter views (organization-scoped)." - name: 'Slack Integration' description: "\nAPIs for connecting and managing Slack workspace integration" - name: Subscriptions description: "\nAPIs for managing subscriptions" components: securitySchemes: default: type: http scheme: bearer description: 'You can retrieve your token by visiting your dashboard and clicking Generate API token.' security: - default: [] paths: /register: post: summary: 'Create Account' operationId: createAccount description: 'Register a new user account. After successful registration, the user will be automatically logged in and redirected to the dashboard.' parameters: [] responses: 201: description: 'Account created. User logged in and redirected.' content: text/plain: schema: type: string example: '' 422: description: 'Validation error.' content: application/json: schema: type: object example: message: 'The email has already been taken.' errors: email: - 'The email has already been taken.' properties: message: type: string example: 'The email has already been taken.' errors: type: object properties: email: type: array example: - 'The email has already been taken.' items: type: string tags: - Registration requestBody: required: true content: application/json: schema: type: object properties: name: type: string description: 'Full name of the user.' example: 'John Doe' email: type: string description: 'Valid email address. Must be unique.' example: john@example.com password: type: string description: 'Password (min 8 characters, at least one uppercase letter, one lowercase letter, one number and one special character).' example: SecurePass123! password_confirmation: type: string description: 'Must match password exactly.' example: SecurePass123! required: - name - email - password - password_confirmation security: [] /api/sanctum/token: post: summary: 'Create API Token' operationId: createAPIToken description: "Generate an API token for authenticated requests.\nRequires a verified email address." parameters: [] responses: 200: description: '' content: application/json: schema: type: object example: token: 1|abc123... properties: token: type: string example: 1|abc123... 422: description: '' content: application/json: schema: oneOf: - description: 'Invalid credentials' type: object example: message: 'The provided credentials are incorrect.' errors: email: - 'The provided credentials are incorrect.' properties: message: type: string example: 'The provided credentials are incorrect.' errors: type: object properties: email: type: array example: - 'The provided credentials are incorrect.' items: type: string - description: 'Email not verified' type: object example: message: 'Please verify your email address before logging in.' errors: email: - 'Please verify your email address before logging in.' properties: message: type: string example: 'Please verify your email address before logging in.' errors: type: object properties: email: type: array example: - 'Please verify your email address before logging in.' items: type: string tags: - Authentication requestBody: required: true content: application/json: schema: type: object properties: email: type: string description: "The user's email address." example: john@example.com password: type: string description: "The user's password." example: SecurePass123! required: - email - password security: [] /api/email/verify: post: summary: 'Verify Email' operationId: verifyEmail description: "Verify user's email address using the 4-digit code sent via email.\nReturns an API token on successful verification.\nVerification link is sent via email. /verify-email?code=1234&email=john@example.com" parameters: [] responses: 200: description: '' content: application/json: schema: oneOf: - description: '' type: object example: message: 'Email verified successfully.' token: 1|abc123... properties: message: type: string example: 'Email verified successfully.' token: type: string example: 1|abc123... - description: 'Already verified' type: object example: message: 'Email already verified.' token: 1|abc123... properties: message: type: string example: 'Email already verified.' token: type: string example: 1|abc123... 422: description: '' content: application/json: schema: oneOf: - description: 'Invalid code' type: object example: message: 'Invalid verification code.' errors: code: - 'Invalid verification code.' properties: message: type: string example: 'Invalid verification code.' errors: type: object properties: code: type: array example: - 'Invalid verification code.' items: type: string - description: 'Expired code' type: object example: message: 'Verification code has expired. Please request a new one.' errors: code: - 'Verification code has expired. Please request a new one.' properties: message: type: string example: 'Verification code has expired. Please request a new one.' errors: type: object properties: code: type: array example: - 'Verification code has expired. Please request a new one.' items: type: string tags: - Authentication requestBody: required: true content: application/json: schema: type: object properties: email: type: string description: "The user's email address." example: john@example.com code: type: string description: 'The 4-digit verification code.' example: '1234' required: - email - code security: [] /api/email/resend: post: summary: 'Resend Verification Code' operationId: resendVerificationCode description: "Send a new 4-digit verification code to the user's email.\nCode expires in 60 minutes." parameters: [] responses: 200: description: '' content: application/json: schema: oneOf: - description: '' type: object example: message: 'Verification code sent.' properties: message: type: string example: 'Verification code sent.' - description: 'Already verified' type: object example: message: 'Email already verified.' properties: message: type: string example: 'Email already verified.' 422: description: 'User not found' content: application/json: schema: type: object example: message: 'No account found with this email.' errors: email: - 'No account found with this email.' properties: message: type: string example: 'No account found with this email.' errors: type: object properties: email: type: array example: - 'No account found with this email.' items: type: string tags: - Authentication requestBody: required: true content: application/json: schema: type: object properties: email: type: string description: "The user's email address." example: john@example.com required: - email security: [] /api/invites/accept: post: summary: 'Register with Invite' operationId: registerWithInvite description: "Accept an invitation and create a new user account. The email address must match\nthe email address on the invite. After successful registration, the user will be\nadded to the organisation or project and will receive an email verification code.\n\nNote: This endpoint bypasses the business email requirement since the invitation\nitself validates the user's legitimacy." parameters: [] responses: 201: description: '' content: application/json: schema: type: object example: message: 'Registration successful. Please check your email to verify your account.' properties: message: type: string example: 'Registration successful. Please check your email to verify your account.' 404: description: 'Token not found' content: application/json: schema: type: object example: message: 'Invite not found.' errors: token: - 'The invite token is invalid or does not exist.' properties: message: type: string example: 'Invite not found.' errors: type: object properties: token: type: array example: - 'The invite token is invalid or does not exist.' items: type: string 410: description: '' content: application/json: schema: oneOf: - description: 'Invite expired' type: object example: message: 'Invite has expired.' errors: token: - 'This invite has expired. Please request a new invitation.' properties: message: type: string example: 'Invite has expired.' errors: type: object properties: token: type: array example: - 'This invite has expired. Please request a new invitation.' items: type: string - description: 'Invite already accepted' type: object example: message: 'Invite has already been accepted.' errors: token: - 'This invite has already been accepted.' properties: message: type: string example: 'Invite has already been accepted.' errors: type: object properties: token: type: array example: - 'This invite has already been accepted.' items: type: string 422: description: '' content: application/json: schema: oneOf: - description: 'Email mismatch' type: object example: message: 'The email address does not match the invitation.' errors: email: - 'The email address must match the email on the invitation.' properties: message: type: string example: 'The email address does not match the invitation.' errors: type: object properties: email: type: array example: - 'The email address must match the email on the invitation.' items: type: string - description: 'Validation error' type: object example: message: 'The name field is required.' errors: name: - 'The name field is required.' properties: message: type: string example: 'The name field is required.' errors: type: object properties: name: type: array example: - 'The name field is required.' items: type: string - description: 'Email already registered' type: object example: message: 'The email has already been taken.' errors: email: - 'The email has already been taken.' properties: message: type: string example: 'The email has already been taken.' errors: type: object properties: email: type: array example: - 'The email has already been taken.' items: type: string tags: - Authentication requestBody: required: true content: application/json: schema: type: object properties: token: type: string description: 'The invite token from the invitation link.' example: eEtgjrcdtubjCu4817MfGiimvC2DQLBgaI7LpY1g5kdDMK5wJlQank7ZJ6PWurmb name: type: string description: "The user's full name." example: 'John Doe' email: type: string description: "The user's email address (must match the invite email)." example: user@example.com password: type: string description: "The user's password (min 8 characters)." example: SecurePass123! password_confirmation: type: string description: 'Password confirmation.' example: SecurePass123! required: - token - name - email - password - password_confirmation security: [] /api/user: get: summary: 'Get Current User' operationId: getCurrentUser description: "Get the authenticated user's details including organisation and subscription information." parameters: [] responses: 200: description: '' content: application/json: schema: type: object example: name: 'John Doe' email: john@example.com email_verified_at: '2025-12-04T12:00:00.000000Z' created_at: '2025-12-04T10:00:00.000000Z' organisation: uuid: 550e8400-e29b-41d4-a716-446655440000 name: 'Acme Inc' website: 'https://acme.com' product_logo: 'https://example.com/logo.png' role: organisation_owner subscription: id: 1 stripe_price_id: price_1234567890 name: 'Pro Plan' status: active expiration_date: '2025-12-31T23:59:59.000000Z' trial_end_date: '2025-12-11T23:59:59.000000Z' properties: name: type: string example: 'John Doe' email: type: string example: john@example.com email_verified_at: type: string example: '2025-12-04T12:00:00.000000Z' created_at: type: string example: '2025-12-04T10:00:00.000000Z' organisation: type: object properties: uuid: type: string example: 550e8400-e29b-41d4-a716-446655440000 name: type: string example: 'Acme Inc' website: type: string example: 'https://acme.com' product_logo: type: string example: 'https://example.com/logo.png' role: type: string example: organisation_owner subscription: type: object properties: id: type: integer example: 1 stripe_price_id: type: string example: price_1234567890 name: type: string example: 'Pro Plan' status: type: string example: active expiration_date: type: string example: '2025-12-31T23:59:59.000000Z' trial_end_date: type: string example: '2025-12-11T23:59:59.000000Z' 401: description: Unauthenticated content: application/json: schema: type: object example: message: Unauthenticated. properties: message: type: string example: Unauthenticated. tags: - Authentication /login: post: summary: 'Log In' operationId: logIn description: 'Authenticate with your email and password to start a session. On success, you receive a token for subsequent requests.' parameters: [] responses: 200: description: 'Login successful. Session started.' content: text/plain: schema: type: string example: '' 422: description: 'Invalid credentials.' content: application/json: schema: type: object example: message: 'These credentials do not match our records.' errors: email: - 'These credentials do not match our records.' properties: message: type: string example: 'These credentials do not match our records.' errors: type: object properties: email: type: array example: - 'These credentials do not match our records.' items: type: string tags: - Authentication requestBody: required: true content: application/json: schema: type: object properties: email: type: string description: 'Your registered email address.' example: john@example.com password: type: string description: 'Your account password.' example: SecurePass123! remember: type: boolean description: 'Stay logged in for extended period.' example: true required: - email - password security: [] /logout: post: summary: 'Log Out' operationId: logOut description: 'End your current session. You will need to log in again to access protected resources.' parameters: [] responses: 200: description: 'Logged out successfully.' content: text/plain: schema: type: string example: '' 401: description: 'Not logged in.' content: application/json: schema: type: object example: message: Unauthenticated. properties: message: type: string example: Unauthenticated. tags: - Authentication /forgot-password: post: summary: 'Request Password Reset' operationId: requestPasswordReset description: 'Send a password reset link to your email. The link expires after 60 minutes. Same response for security even if email not found.' parameters: [] responses: 200: description: 'Reset link sent.' content: application/json: schema: type: object example: status: 'We have emailed your password reset link.' properties: status: type: string example: 'We have emailed your password reset link.' tags: - 'Password Reset' requestBody: required: true content: application/json: schema: type: object properties: email: type: string description: 'Email address associated with your account.' example: john@example.com required: - email security: [] /reset-password: post: summary: 'Reset Password' operationId: resetPassword description: 'Set a new password using the token from your email. Token is valid for 60 minutes.' parameters: [] responses: 200: description: 'Password reset successful.' content: application/json: schema: type: object example: status: 'Your password has been reset.' properties: status: type: string example: 'Your password has been reset.' 422: description: 'Invalid or expired token.' content: application/json: schema: type: object example: message: 'This password reset token is invalid.' errors: email: - 'This password reset token is invalid.' properties: message: type: string example: 'This password reset token is invalid.' errors: type: object properties: email: type: array example: - 'This password reset token is invalid.' items: type: string tags: - 'Password Reset' requestBody: required: true content: application/json: schema: type: object properties: token: type: string description: 'Reset token from the email link.' example: a1b2c3d4e5f6g7h8i9j0 email: type: string description: 'Your account email address.' example: john@example.com password: type: string description: 'Password (min 8 characters, at least one letter and one number).' example: NewSecurePass123! password_confirmation: type: string description: 'Must match new password exactly.' example: NewSecurePass123! required: - token - email - password - password_confirmation security: [] '/api/organisations/{organisation_uuid}/reviews': post: summary: 'Get Organization Reviews' operationId: getOrganizationReviews description: "Fetch and aggregate reviews from multiple projects/brands within an organization.\nReviews are fetched from external API, persisted to database, and returned with filters applied.\nSupports filtering by multiple brands (projects), platforms, ratings, languages, and more." parameters: [] responses: 200: description: '' content: application/json: schema: type: object example: data: organisation_id: 660e8400-e29b-41d4-a716-446655440001 organisation_name: 'Acme Corp' reviews: current_page: 1 data: - id: 1 scraper_review_id: 10452 organisation_id: 5 project_id: 30 platform: Capterra platform_icon: null rating: 5.0 content: 'Great product with excellent features...' author: 'John Smith' job_role: 'Product Manager' language: en date: '2026-01-13' link_url: 'https://www.capterra.com/reviews/' direct_review_url: 'https://scraper.example.com/reviews' created_at: '2026-01-15T09:30:21.000000Z' per_page: 10 total: 150 properties: data: type: object properties: organisation_id: type: string example: 660e8400-e29b-41d4-a716-446655440001 organisation_name: type: string example: 'Acme Corp' reviews: type: object properties: current_page: type: integer example: 1 data: type: array example: - id: 1 scraper_review_id: 10452 organisation_id: 5 project_id: 30 platform: Capterra platform_icon: null rating: 5 content: 'Great product with excellent features...' author: 'John Smith' job_role: 'Product Manager' language: en date: '2026-01-13' link_url: 'https://www.capterra.com/reviews/' direct_review_url: 'https://scraper.example.com/reviews' created_at: '2026-01-15T09:30:21.000000Z' items: type: object properties: id: type: integer example: 1 scraper_review_id: type: integer example: 10452 organisation_id: type: integer example: 5 project_id: type: integer example: 30 platform: type: string example: Capterra platform_icon: type: string example: null rating: type: number example: 5.0 content: type: string example: 'Great product with excellent features...' author: type: string example: 'John Smith' job_role: type: string example: 'Product Manager' language: type: string example: en date: type: string example: '2026-01-13' link_url: type: string example: 'https://www.capterra.com/reviews/' direct_review_url: type: string example: 'https://scraper.example.com/reviews' created_at: type: string example: '2026-01-15T09:30:21.000000Z' per_page: type: integer example: 10 total: type: integer example: 150 403: description: '' content: application/json: schema: type: object example: message: 'You do not have access to this organisation.' properties: message: type: string example: 'You do not have access to this organisation.' 404: description: '' content: application/json: schema: type: object example: message: 'Organisation not found.' properties: message: type: string example: 'Organisation not found.' tags: - Reviews requestBody: required: false content: application/json: schema: type: object properties: brands: type: array description: 'Optional - Filter by brand/project UUIDs. Empty/omitted = all brands.' example: - uuid-1 - uuid-2 items: type: string view_uuid: type: string description: 'Optional - Apply filters from a saved view. When provided, all other filters (except search and page) are ignored.' example: a1b2c3d4-e5f6-g7h8-i9j0 nullable: true platforms: type: array description: 'Optional - Filter by platforms. Ignored if view_uuid is provided.' example: - G2 - Capterra items: type: string date_range: type: object description: '' example: null properties: type: type: string description: 'This field is required when date_range is present.' example: all_time enum: - preset - custom - all_time value: type: string description: 'This field is required when date_range.type is preset.' example: all_time enum: - last_14_days - last_3_months - last_12_months - last_3_years - all_time nullable: true date_range_custom: type: object description: '' example: null properties: start: type: string description: 'This field is required when date_range_custom is present. Must be a valid date.' example: '2026-02-03T13:58:49' end: type: string description: 'This field is required when date_range_custom is present. Must be a valid date. Must be a date after or equal to date_range_custom.start.' example: '2052-02-27' nullable: true date_from: type: string description: 'Optional - Start date. Ignored if view_uuid is provided.' example: '2026-01-01' nullable: true date_to: type: string description: 'Optional - End date. Ignored if view_uuid is provided.' example: '2026-01-24' nullable: true rating_buckets: type: array description: 'Optional - Filter by rating buckets. Ignored if view_uuid is provided.' example: - '5.0' - 4.0-4.9 items: type: string languages: type: array description: 'Optional - Filter by languages (ISO 639-1 codes). Ignored if view_uuid is provided.' example: - en - de items: type: string read_status: type: string description: 'Optional - Filter by read status. Ignored if view_uuid is provided.' example: unread nullable: true search: type: string description: 'Optional - Keyword search. Can be used with view_uuid to search within saved view filters.' example: 'customer support' nullable: true sort_by: type: string description: '' example: review_date enum: - review_date - rating - platform - created_at nullable: true sort_direction: type: string description: '' example: desc enum: - asc - desc nullable: true period_in_days: type: integer description: 'Optional - Number of days to fetch reviews for. Default: 30. Ignored if view_uuid is provided.' example: 30 nullable: true page: type: integer description: 'Optional - Page number for pagination. Default: 1.' example: 1 nullable: true parameters: - in: path name: organisation_uuid description: 'The organization UUID.' example: 660e8400-e29b-41d4-a716-446655440001 required: true schema: type: string '/api/organisations/{organisation_uuid}/reviews/analytics': post: summary: 'Get Review Analytics' operationId: getReviewAnalytics description: "Calculate analytics metrics for reviews including new reviews count, average rating,\nreviews per month, with comparison to previous period. Also includes rating distribution\nand platform breakdown." parameters: [] responses: 200: description: '' content: application/json: schema: type: object example: data: - product_uuid: 660e8400-e29b-41d4-a716-446655440001 product_name: 'Acme Product' product_logo: 'https://...' period: start: '2025-10-28' end: '2026-01-27' duration: 3 metrics: new_reviews: current: 2 previous: 10 change_percentage: -80.0 trend: down average_rating: current: 4.1 previous: 4.4 change_percentage: -6.8 trend: down reviews_per_month: current: 2 previous: 6 change_percentage: -68.3 trend: down review_velocity: interval: week data: - group: 2025-10-W5 count: 1 cumulative_before: 0 rating: 4.2 breakdown: ratings: '5.0': 0 '4.0': 2 '3.0': 0 '2.0': 0 '1.0': 0 platforms: G2: 1 Capterra: 1 competitors: - project_uuid: 660e8400-e29b-41d4-a716-446655440001 product_name: 'Base Product' product_logo: 'https://...' is_competitor: false total_reviews: current: 10 previous: 5 change_percentage: 100.0 trend: up average_rating: current: 4.3 previous: 4.1 change_percentage: 4.9 trend: up rank: 1 applied_filters: brands: - uuid-1 platforms: - G2 - Capterra date_range: type: preset value: last_3_months organisation_id: 660e8400-e29b-41d4-a716-446655440001 organisation_name: 'Acme Corp' properties: data: type: array example: - product_uuid: 660e8400-e29b-41d4-a716-446655440001 product_name: 'Acme Product' product_logo: 'https://...' period: start: '2025-10-28' end: '2026-01-27' duration: 3 metrics: new_reviews: current: 2 previous: 10 change_percentage: -80 trend: down average_rating: current: 4.1 previous: 4.4 change_percentage: -6.8 trend: down reviews_per_month: current: 2 previous: 6 change_percentage: -68.3 trend: down review_velocity: interval: week data: - group: 2025-10-W5 count: 1 cumulative_before: 0 rating: 4.2 breakdown: ratings: '5.0': 0 '4.0': 2 '3.0': 0 '2.0': 0 '1.0': 0 platforms: G2: 1 Capterra: 1 items: type: object properties: product_uuid: type: string example: 660e8400-e29b-41d4-a716-446655440001 product_name: type: string example: 'Acme Product' product_logo: type: string example: 'https://...' period: type: object properties: start: type: string example: '2025-10-28' end: type: string example: '2026-01-27' duration: type: integer example: 3 metrics: type: object properties: new_reviews: type: object properties: current: type: integer example: 2 previous: type: integer example: 10 change_percentage: type: number example: -80.0 trend: type: string example: down average_rating: type: object properties: current: type: number example: 4.1 previous: type: number example: 4.4 change_percentage: type: number example: -6.8 trend: type: string example: down reviews_per_month: type: object properties: current: type: integer example: 2 previous: type: integer example: 6 change_percentage: type: number example: -68.3 trend: type: string example: down review_velocity: type: object properties: interval: type: string example: week data: type: array example: - group: 2025-10-W5 count: 1 cumulative_before: 0 rating: 4.2 items: type: object properties: group: { type: string, example: 2025-10-W5 } count: { type: integer, example: 1 } cumulative_before: { type: integer, example: 0 } rating: { type: number, example: 4.2 } breakdown: type: object properties: ratings: type: object properties: '5.0': type: integer example: 0 '4.0': type: integer example: 2 '3.0': type: integer example: 0 '2.0': type: integer example: 0 '1.0': type: integer example: 0 platforms: type: object properties: G2: type: integer example: 1 Capterra: type: integer example: 1 competitors: type: array example: - project_uuid: 660e8400-e29b-41d4-a716-446655440001 product_name: 'Base Product' product_logo: 'https://...' is_competitor: false total_reviews: current: 10 previous: 5 change_percentage: 100 trend: up average_rating: current: 4.3 previous: 4.1 change_percentage: 4.9 trend: up rank: 1 items: type: object properties: project_uuid: type: string example: 660e8400-e29b-41d4-a716-446655440001 product_name: type: string example: 'Base Product' product_logo: type: string example: 'https://...' is_competitor: type: boolean example: false total_reviews: type: object properties: current: type: integer example: 10 previous: type: integer example: 5 change_percentage: type: number example: 100.0 trend: type: string example: up average_rating: type: object properties: current: type: number example: 4.3 previous: type: number example: 4.1 change_percentage: type: number example: 4.9 trend: type: string example: up rank: type: integer example: 1 applied_filters: type: object properties: brands: type: array example: - uuid-1 items: type: string platforms: type: array example: - G2 - Capterra items: type: string date_range: type: object properties: type: type: string example: preset value: type: string example: last_3_months organisation_id: type: string example: 660e8400-e29b-41d4-a716-446655440001 organisation_name: type: string example: 'Acme Corp' 403: description: '' content: application/json: schema: type: object example: message: 'You do not have access to this organisation.' properties: message: type: string example: 'You do not have access to this organisation.' 404: description: '' content: application/json: schema: type: object example: message: 'Organisation not found.' properties: message: type: string example: 'Organisation not found.' tags: - Reviews requestBody: required: false content: application/json: schema: type: object properties: brands: type: array description: 'Optional - Filter by brand/project UUIDs. Empty/omitted = all brands. Ignored if compare_products is provided.' example: - uuid-1 - uuid-2 items: type: string compare_products: type: array description: 'Optional - Compare up to 2 products. When provided, returns array of analytics per product. Takes precedence over brands.' example: - uuid-1 - uuid-2 items: type: string platforms: type: array description: 'Optional - Filter by platforms.' example: - G2 - Capterra items: type: string date_range: type: object description: 'Optional - Preset date range.' example: type: preset value: last_3_months properties: type: type: string description: 'This field is required when date_range is present.' example: preset enum: - preset - all_time value: type: string description: 'This field is required when date_range.type is preset.' example: last_12_months enum: - last_14_days - last_3_months - last_12_months - last_3_years - all_time nullable: true date_range_custom: type: object description: 'Optional - Custom date range.' example: start: '2025-10-01' end: '2026-01-27' properties: start: type: string description: 'This field is required when date_range_custom is present. Must be a valid date.' example: '2026-02-03T13:58:49' end: type: string description: 'This field is required when date_range_custom is present. Must be a valid date. Must be a date after or equal to date_range_custom.start.' example: '2052-02-27' nullable: true parameters: - in: path name: organisation_uuid description: 'The organization UUID.' example: 660e8400-e29b-41d4-a716-446655440001 required: true schema: type: string '/api/organisations/{organisation_uuid}/social': post: summary: 'Get Organization Social Posts' operationId: getOrganizationSocialPosts description: "Fetch and aggregate social posts from multiple projects/brands within an organization.\nPosts are fetched from external Scraper API, persisted to database, and returned with filters applied.\nSupports filtering by brands, platforms, types, sentiments, keywords, and more." parameters: [] responses: 200: description: '' content: application/json: schema: type: object example: data: posts: - uuid: 770e8400-e29b-41d4-a716-446655440002 platform: reddit community_name: saas content_type: post brand_keyword: Acme title: 'Great product discussion' text: "We've been using this for months..." post_date: '2026-01-20T10:00:00.000000Z' url: 'https://reddit.com/r/saas/comments/123' sentiment: positive intensity: 5 score_0_100: 85 user_name: johndoe upvotes_count: 42 comments_count: 10 project: uuid: 660e8400-e29b-41d4-a716-446655440001 product_name: 'Acme CRM' product_logo: 'https://...' created_at: '2026-01-20T09:30:21.000000Z' updated_at: '2026-01-20T09:30:21.000000Z' meta: current_page: 1 per_page: 10 total: 838 last_page: 17 applied_filters: brands: - uuid1 - uuid2 platforms: - reddit - github types: - post sentiments: - positive - negative keywords: - Loom - Figma properties: data: type: object properties: posts: type: array example: - uuid: 770e8400-e29b-41d4-a716-446655440002 platform: reddit community_name: saas content_type: post brand_keyword: Acme title: 'Great product discussion' text: "We've been using this for months..." post_date: '2026-01-20T10:00:00.000000Z' url: 'https://reddit.com/r/saas/comments/123' sentiment: positive intensity: 5 score_0_100: 85 user_name: johndoe upvotes_count: 42 comments_count: 10 project: uuid: 660e8400-e29b-41d4-a716-446655440001 product_name: 'Acme CRM' product_logo: 'https://...' created_at: '2026-01-20T09:30:21.000000Z' updated_at: '2026-01-20T09:30:21.000000Z' items: type: object properties: uuid: type: string example: 770e8400-e29b-41d4-a716-446655440002 platform: type: string example: reddit community_name: type: string example: saas content_type: type: string example: post brand_keyword: type: string example: Acme title: type: string example: 'Great product discussion' text: type: string example: "We've been using this for months..." post_date: type: string example: '2026-01-20T10:00:00.000000Z' url: type: string example: 'https://reddit.com/r/saas/comments/123' sentiment: type: string example: positive intensity: type: integer example: 5 score_0_100: type: integer example: 85 user_name: type: string example: johndoe upvotes_count: type: integer example: 42 comments_count: type: integer example: 10 project: type: object properties: uuid: type: string example: 660e8400-e29b-41d4-a716-446655440001 product_name: type: string example: 'Acme CRM' product_logo: type: string example: 'https://...' created_at: type: string example: '2026-01-20T09:30:21.000000Z' updated_at: type: string example: '2026-01-20T09:30:21.000000Z' meta: type: object properties: current_page: type: integer example: 1 per_page: type: integer example: 10 total: type: integer example: 838 last_page: type: integer example: 17 applied_filters: type: object properties: brands: type: array example: - uuid1 - uuid2 items: type: string platforms: type: array example: - reddit - github items: type: string types: type: array example: - post items: type: string sentiments: type: array example: - positive - negative items: type: string keywords: type: array example: - Loom - Figma items: type: string 403: description: '' content: application/json: schema: type: object example: message: 'You do not have access to this organisation.' properties: message: type: string example: 'You do not have access to this organisation.' 404: description: '' content: application/json: schema: type: object example: message: 'Organisation not found.' properties: message: type: string example: 'Organisation not found.' tags: - 'Social Posts' requestBody: required: false content: application/json: schema: type: object properties: brands: type: array description: 'Optional - Filter by brand/project UUIDs. Empty/omitted = all brands.' example: - uuid-1 - uuid-2 items: type: string view_uuid: type: string description: 'Optional - Apply filters from a saved view. When provided, all other filters (except search, page, and per_page) are ignored.' example: a1b2c3d4-e5f6-g7h8-i9j0 nullable: true platforms: type: array description: 'Optional - Filter by platforms. Ignored if view_uuid is provided.' example: - reddit - github items: type: string types: type: array description: 'Optional - Filter by content type. Ignored if view_uuid is provided.' example: - post - comment items: type: string sentiments: type: array description: 'Optional - Filter by sentiment. Ignored if view_uuid is provided.' example: - positive - negative items: type: string keywords: type: array description: 'Optional - Filter by keywords. Ignored if view_uuid is provided.' example: - Loom - Figma items: type: string date_range: type: object description: 'Optional - Preset date range. Ignored if view_uuid is provided.' example: type: preset value: last_3_months properties: type: type: string description: 'This field is required when date_range is present.' example: custom enum: - preset - custom - all_time value: type: string description: 'This field is required when date_range.type is preset.' example: last_3_years enum: - last_14_days - last_3_months - last_12_months - last_3_years - all_time nullable: true date_range_custom: type: object description: 'Optional - Custom date range. Ignored if view_uuid is provided.' example: start: '2026-01-01' end: '2026-01-24' properties: start: type: string description: 'This field is required when date_range_custom is present. Must be a valid date.' example: '2026-02-03T13:58:49' end: type: string description: 'This field is required when date_range_custom is present. Must be a valid date. Must be a date after or equal to date_range_custom.start.' example: '2052-02-27' nullable: true read_status: type: string description: 'Optional - Filter by read status. Ignored if view_uuid is provided.' example: unread nullable: true search: type: string description: 'Optional - Keyword search. Can be used with view_uuid to search within saved view filters.' example: 'customer support' nullable: true sort_by: type: string description: 'Optional - Sort field. Ignored if view_uuid is provided.' example: post_date nullable: true sort_direction: type: string description: 'Optional - Sort direction. Ignored if view_uuid is provided.' example: desc nullable: true page: type: integer description: 'Optional - Page number for pagination. Default: 1.' example: 1 nullable: true per_page: type: integer description: 'Optional - Items per page. Default: 10. Maximum: 100.' example: 10 nullable: true parameters: - in: path name: organisation_uuid description: 'The organization UUID.' example: 660e8400-e29b-41d4-a716-446655440001 required: true schema: type: string '/api/organisations/{organisation_uuid}/social/analytics': post: summary: 'Organisation-level social analytics.' operationId: organisationLevelSocialAnalytics description: "Returns mention count and average sentiment with period-over-period comparison.\nFilters: brands (social_posts.brand_id), platforms, date_range, date_range_custom (start/end, same as social posts), or custom_date_range (from/to)." parameters: [] responses: { } tags: - 'Social Posts' requestBody: required: true content: application/json: schema: type: object properties: brands: type: array description: '' example: - 16 items: type: integer platforms: type: array description: '' example: - reddit items: type: string enum: - reddit - github - stackoverflow - hackernews date_range: type: object description: '' example: null properties: type: type: string description: 'This field is required when date_range is present.' example: all_time enum: - preset - all_time value: type: string description: 'This field is required when date_range.type is preset.' example: last_3_years enum: - last_14_days - last_3_months - last_12_months - last_3_years - all_time nullable: true custom_date_range: type: object description: '' example: null properties: from: type: string description: 'This field is required when custom_date_range is present. Must be a valid date.' example: '2026-02-03T13:58:49' to: type: string description: 'This field is required when custom_date_range is present. Must be a valid date. Must be a date after or equal to custom_date_range.from.' example: '2052-02-27' nullable: true date_range_custom: type: object description: '' example: null properties: start: type: string description: 'This field is required when date_range_custom is present. Must be a valid date.' example: '2026-02-03T13:58:49' end: type: string description: 'This field is required when date_range_custom is present. Must be a valid date. Must be a date after or equal to date_range_custom.start.' example: '2052-02-27' nullable: true required: - platforms security: [] parameters: - in: path name: organisation_uuid description: '' example: b47b1b40-d5f7-4e9c-959a-b7affbc9965c required: true schema: type: string '/api/organisations/{organisation_uuid}/social/views': get: summary: 'List Social Post Views' operationId: listSocialPostViews description: 'Get all saved filter views for the current user in this organization.' parameters: [] responses: 200: description: '' content: application/json: schema: type: object example: data: - uuid: a1b2c3d4-e5f6-g7h8-i9j0 name: 'Critical Alerts' filters: platforms: - reddit sentiments: - negative created_at: '2026-01-26T10:00:00.000000Z' properties: data: type: array example: - uuid: a1b2c3d4-e5f6-g7h8-i9j0 name: 'Critical Alerts' filters: platforms: - reddit sentiments: - negative created_at: '2026-01-26T10:00:00.000000Z' items: type: object properties: uuid: type: string example: a1b2c3d4-e5f6-g7h8-i9j0 name: type: string example: 'Critical Alerts' filters: type: object properties: platforms: type: array example: - reddit items: type: string sentiments: type: array example: - negative items: type: string created_at: type: string example: '2026-01-26T10:00:00.000000Z' tags: - 'Social Posts' post: summary: 'Store Social Post View' operationId: storeSocialPostView description: 'Create a new saved filter view for social posts.' parameters: [] responses: 201: description: '' content: application/json: schema: type: object example: data: uuid: a1b2c3d4-e5f6-g7h8-i9j0 name: 'Critical Alerts' filters: platforms: - reddit sentiments: - negative created_at: '2026-01-26T10:00:00.000000Z' properties: data: type: object properties: uuid: type: string example: a1b2c3d4-e5f6-g7h8-i9j0 name: type: string example: 'Critical Alerts' filters: type: object properties: platforms: type: array example: - reddit items: type: string sentiments: type: array example: - negative items: type: string created_at: type: string example: '2026-01-26T10:00:00.000000Z' tags: - 'Social Posts' requestBody: required: true content: application/json: schema: type: object properties: name: type: string description: 'The name of the view.' example: 'Critical Alerts' filters: type: object description: 'Filter configuration.' example: platforms: - reddit sentiments: - negative properties: brands: type: array description: 'Must be a valid UUID. The uuid of an existing record in the projects table. The uuid of an existing record in the projects table.' example: - 6ff8f7f6-1eb3-3525-be4a-3932c805afed items: type: string platforms: type: array description: '' example: - reddit items: type: string enum: - reddit - github - stackoverflow - hackernews types: type: array description: '' example: - comment items: type: string enum: - post - comment sentiments: type: array description: '' example: - positive items: type: string enum: - positive - neutral - negative keywords: type: array description: 'Must not be greater than 255 characters.' example: - g items: type: string date_range: type: object description: '' example: null properties: type: type: string description: 'This field is required when filters.date_range is present.' example: custom enum: - preset - custom - all_time value: type: string description: 'This field is required when filters.date_range.type is preset.' example: all_time enum: - last_14_days - last_3_months - last_12_months - last_3_years - all_time nullable: true date_range_custom: type: object description: '' example: null properties: start: type: string description: 'This field is required when filters.date_range_custom is present. Must be a valid date.' example: '2026-02-03T13:58:49' end: type: string description: 'This field is required when filters.date_range_custom is present. Must be a valid date. Must be a date after or equal to filters.date_range_custom.start.' example: '2052-02-27' nullable: true read_status: type: string description: '' example: unread enum: - all - read - unread nullable: true search: type: string description: 'Must not be greater than 500 characters.' example: 'n' nullable: true sort_by: type: string description: '' example: comments_count enum: - post_date - sentiment - platform - upvotes_count - comments_count - created_at nullable: true sort_direction: type: string description: '' example: asc enum: - asc - desc nullable: true required: - name - filters parameters: - in: path name: organisation_uuid description: 'The organization UUID.' example: 6ff8f7f6-1eb3-3525-be4a-3932c805afed required: true schema: type: string '/api/organisations/{organisation_uuid}/social/views/{view_uuid}': get: summary: 'Show Social Post View' operationId: showSocialPostView description: 'Get a specific saved filter view.' parameters: [] responses: 200: description: '' content: application/json: schema: type: object example: data: uuid: a1b2c3d4-e5f6-g7h8-i9j0 name: 'Critical Alerts' filters: platforms: - reddit sentiments: - negative created_at: '2026-01-26T10:00:00.000000Z' properties: data: type: object properties: uuid: type: string example: a1b2c3d4-e5f6-g7h8-i9j0 name: type: string example: 'Critical Alerts' filters: type: object properties: platforms: type: array example: - reddit items: type: string sentiments: type: array example: - negative items: type: string created_at: type: string example: '2026-01-26T10:00:00.000000Z' tags: - 'Social Posts' put: summary: 'Update Social Post View' operationId: updateSocialPostView description: 'Update a saved filter view.' parameters: [] responses: 200: description: '' content: application/json: schema: type: object example: data: uuid: a1b2c3d4-e5f6-g7h8-i9j0 name: 'Critical Alerts Updated' filters: platforms: - reddit sentiments: - negative updated_at: '2026-01-26T11:00:00.000000Z' properties: data: type: object properties: uuid: type: string example: a1b2c3d4-e5f6-g7h8-i9j0 name: type: string example: 'Critical Alerts Updated' filters: type: object properties: platforms: type: array example: - reddit items: type: string sentiments: type: array example: - negative items: type: string updated_at: type: string example: '2026-01-26T11:00:00.000000Z' tags: - 'Social Posts' requestBody: required: false content: application/json: schema: type: object properties: sort_order: type: integer description: 'Must be at least 0.' example: 27 name: type: string description: 'optional The name of the view.' example: architecto filters: type: object description: 'optional Filter configuration.' example: [] properties: brands: type: array description: 'Must be a valid UUID. The uuid of an existing record in the projects table. The uuid of an existing record in the projects table.' example: - 6b72fe4a-5b40-307c-bc24-f79acf9a1bb9 items: type: string platforms: type: array description: '' example: - github items: type: string enum: - reddit - github - stackoverflow - hackernews types: type: array description: '' example: - comment items: type: string enum: - post - comment sentiments: type: array description: '' example: - neutral items: type: string enum: - positive - neutral - negative keywords: type: array description: 'Must not be greater than 255 characters.' example: - m items: type: string date_range: type: object description: '' example: null properties: type: type: string description: 'This field is required when filters.date_range is present.' example: all_time enum: - preset - custom - all_time value: type: string description: 'This field is required when filters.date_range.type is preset.' example: last_3_months enum: - last_14_days - last_3_months - last_12_months - last_3_years - all_time nullable: true date_range_custom: type: object description: '' example: null properties: start: type: string description: 'This field is required when filters.date_range_custom is present. Must be a valid date.' example: '2026-02-03T13:58:49' end: type: string description: 'This field is required when filters.date_range_custom is present. Must be a valid date. Must be a date after or equal to filters.date_range_custom.start.' example: '2052-02-27' nullable: true read_status: type: string description: '' example: all enum: - all - read - unread nullable: true search: type: string description: 'Must not be greater than 500 characters.' example: 'n' nullable: true sort_by: type: string description: '' example: platform enum: - post_date - sentiment - platform - upvotes_count - comments_count - created_at nullable: true sort_direction: type: string description: '' example: asc enum: - asc - desc nullable: true delete: summary: 'Delete Social Post View' operationId: deleteSocialPostView description: 'Delete a saved filter view.' parameters: [] responses: 204: description: '' content: application/json: schema: type: object example: { } properties: { } tags: - 'Social Posts' parameters: - in: path name: organisation_uuid description: 'The organization UUID.' example: 6ff8f7f6-1eb3-3525-be4a-3932c805afed required: true schema: type: string - in: path name: view_uuid description: 'The view UUID.' example: 6ff8f7f6-1eb3-3525-be4a-3932c805afed required: true schema: type: string /api/webhook: post: summary: '' operationId: postApiWebhook description: '' parameters: [] responses: { } tags: - Endpoints security: [] /api/invites/validate: post: summary: 'Validate Invite' operationId: validateInvite description: "Validate an invite token and return the associated email if the invite is valid.\nThis endpoint is public and does not require authentication." parameters: [] responses: 200: description: '' content: application/json: schema: type: object example: email: user@example.com properties: email: type: string example: user@example.com 404: description: 'Invite not found' content: application/json: schema: type: object example: message: 'Invite not found.' properties: message: type: string example: 'Invite not found.' 410: description: '' content: application/json: schema: oneOf: - description: 'Invite expired' type: object example: message: 'This invite has expired.' properties: message: type: string example: 'This invite has expired.' - description: 'Invite no longer valid' type: object example: message: 'This invite is no longer valid.' properties: message: type: string example: 'This invite is no longer valid.' tags: - Invites requestBody: required: true content: application/json: schema: type: object properties: token: type: string description: 'The invite token.' example: abc123def456... required: - token security: [] /api/invites: get: summary: 'List Invites' operationId: listInvites description: "Get invites based on context:\n- Organisation level (no project_id): Returns all organisation invites + all project invites for the organisation\n- Project level (with project_id): Returns only invites for the specified project" parameters: - in: query name: project_id description: 'Optional. Filter invites by project UUID.' example: 660e8400-e29b-41d4-a716-446655440001 required: false schema: type: string description: 'Optional. Filter invites by project UUID.' example: 660e8400-e29b-41d4-a716-446655440001 responses: 200: description: '' content: application/json: schema: type: array items: type: object properties: id: type: integer example: 1 email: type: string example: user@example.com type: type: string example: organisation status: type: string example: pending expires_at: type: string example: '2025-12-31T10:00:00.000000Z' organisation_id: type: string example: 550e8400-e29b-41d4-a716-446655440000 organisation: type: object properties: uuid: type: string example: 550e8400-e29b-41d4-a716-446655440000 name: type: string example: 'Acme Corp' project_id: type: string example: null project: type: string example: null inviter: type: object properties: name: type: string example: 'John Admin' email: type: string example: admin@acme.com created_at: type: string example: '2025-12-24T10:00:00.000000Z' updated_at: type: string example: '2025-12-24T10:00:00.000000Z' example: - id: 1 email: user@example.com type: organisation status: pending expires_at: '2025-12-31T10:00:00.000000Z' organisation_id: 550e8400-e29b-41d4-a716-446655440000 organisation: uuid: 550e8400-e29b-41d4-a716-446655440000 name: 'Acme Corp' project_id: null project: null inviter: name: 'John Admin' email: admin@acme.com created_at: '2025-12-24T10:00:00.000000Z' updated_at: '2025-12-24T10:00:00.000000Z' - id: 2 email: developer@example.com type: project status: pending expires_at: '2025-12-31T10:00:00.000000Z' organisation_id: null organisation: null project_id: 660e8400-e29b-41d4-a716-446655440001 project: uuid: 660e8400-e29b-41d4-a716-446655440001 name: 'My Product' inviter: name: 'John Admin' email: admin@acme.com created_at: '2025-12-24T10:00:00.000000Z' updated_at: '2025-12-24T10:00:00.000000Z' 403: description: 'No access to project' content: application/json: schema: type: object example: message: 'You do not have access to this project.' properties: message: type: string example: 'You do not have access to this project.' 404: description: 'Project not found' content: application/json: schema: type: object example: message: 'Project not found.' properties: message: type: string example: 'Project not found.' 422: description: 'No organisation context' content: application/json: schema: type: object example: message: 'No organisation context found.' errors: organisation: - 'Please select an organisation or set current organisation.' properties: message: type: string example: 'No organisation context found.' errors: type: object properties: organisation: type: array example: - 'Please select an organisation or set current organisation.' items: type: string tags: - Invites post: summary: 'Create Invite' operationId: createInvite description: "Create a new invite for a user to join an organisation or project.\nFor organisation invites, the organisation is automatically derived from the authenticated user's current organisation context." parameters: [] responses: 201: description: '' content: application/json: schema: type: object example: message: 'Invite created successfully.' data: id: 1 email: user@example.com type: organisation status: pending token: abc123... expires_at: '2025-12-31T10:00:00.000000Z' organisation_id: 550e8400-e29b-41d4-a716-446655440000 project_id: null created_at: '2025-12-24T10:00:00.000000Z' updated_at: '2025-12-24T10:00:00.000000Z' properties: message: type: string example: 'Invite created successfully.' data: type: object properties: id: type: integer example: 1 email: type: string example: user@example.com type: type: string example: organisation status: type: string example: pending token: type: string example: abc123... expires_at: type: string example: '2025-12-31T10:00:00.000000Z' organisation_id: type: string example: 550e8400-e29b-41d4-a716-446655440000 project_id: type: string example: null created_at: type: string example: '2025-12-24T10:00:00.000000Z' updated_at: type: string example: '2025-12-24T10:00:00.000000Z' 422: description: 'No organisation context' content: application/json: schema: type: object example: message: 'No organisation context found.' errors: organisation: - 'Please select an organisation or set current organisation.' properties: message: type: string example: 'No organisation context found.' errors: type: object properties: organisation: type: array example: - 'Please select an organisation or set current organisation.' items: type: string tags: - Invites requestBody: required: true content: application/json: schema: type: object properties: email: type: string description: 'The email address to send the invite to.' example: user@example.com type: type: string description: 'The type of invite (organisation or project).' example: organisation project_id: type: string description: 'The project UUID (required for project invites).' example: 660e8400-e29b-41d4-a716-446655440001 nullable: true required: - email - type '/api/invites/{invite_id}': delete: summary: 'Delete Invite' operationId: deleteInvite description: 'Delete an invite. Only the inviter or organisation owners can delete invites.' parameters: [] responses: 200: description: '' content: application/json: schema: type: object example: message: 'Invite deleted successfully.' properties: message: type: string example: 'Invite deleted successfully.' 403: description: 'Not authorized' content: application/json: schema: type: object example: message: 'You are not authorized to delete this invite.' properties: message: type: string example: 'You are not authorized to delete this invite.' 404: description: 'Invite not found' content: application/json: schema: type: object example: message: 'Invite not found.' properties: message: type: string example: 'Invite not found.' 422: description: 'No organisation context' content: application/json: schema: type: object example: message: 'No organisation context found.' errors: organisation: - 'Please select an organisation or set current organisation.' properties: message: type: string example: 'No organisation context found.' errors: type: object properties: organisation: type: array example: - 'Please select an organisation or set current organisation.' items: type: string tags: - Invites parameters: - in: path name: invite_id description: 'The invite ID.' example: 1 required: true schema: type: integer '/api/projects/{project_uuid}/links': get: summary: 'List Project Links' operationId: listProjectLinks description: 'Get all links associated with a specific project.' parameters: [] responses: 200: description: '' content: application/json: schema: type: object example: data: - uuid: 550e8400-e29b-41d4-a716-446655440000 platform: g2 url: 'https://g2.com/products/acme' enabled: true created_at: '2026-01-22T10:00:00.000000Z' updated_at: '2026-01-22T10:00:00.000000Z' properties: data: type: array example: - uuid: 550e8400-e29b-41d4-a716-446655440000 platform: g2 url: 'https://g2.com/products/acme' enabled: true created_at: '2026-01-22T10:00:00.000000Z' updated_at: '2026-01-22T10:00:00.000000Z' items: type: object properties: uuid: type: string example: 550e8400-e29b-41d4-a716-446655440000 platform: type: string example: g2 url: type: string example: 'https://g2.com/products/acme' enabled: type: boolean example: true created_at: type: string example: '2026-01-22T10:00:00.000000Z' updated_at: type: string example: '2026-01-22T10:00:00.000000Z' 403: description: 'No Access' content: application/json: schema: type: object example: message: 'You do not have access to this project.' properties: message: type: string example: 'You do not have access to this project.' 404: description: 'Project Not Found' content: application/json: schema: type: object example: message: 'Project not found.' properties: message: type: string example: 'Project not found.' tags: - Links parameters: - in: path name: project_uuid description: 'The project UUID.' example: 660e8400-e29b-41d4-a716-446655440001 required: true schema: type: string /api/notifications: get: summary: 'List Notifications' operationId: listNotifications description: 'Get all notification rules for the current organisation.' parameters: [] responses: 200: description: '' content: application/json: schema: type: array items: type: object properties: uuid: type: string example: 770e8400-e29b-41d4-a716-446655440000 name: type: string example: 'Daily Review Alerts' trigger_type: type: string example: new_review languages: type: array example: - en - es items: type: string auto_translate: type: boolean example: true rating_filters: type: array example: - 1 - 2 - 3 items: type: integer sentiment_filters: type: array example: - negative items: type: string condition_filters: type: object properties: logic: type: string example: and rules: type: array example: - field: platforms operator: contains value: - g2 items: type: object properties: field: type: string example: platforms operator: type: string example: contains value: type: array example: - g2 items: type: string notification_frequency: type: string example: instant read_status: type: string example: all is_active: type: boolean example: true channels: type: array example: [] projects: type: array example: [] created_at: type: string example: '2025-12-31T12:00:00.000000Z' updated_at: type: string example: '2025-12-31T12:00:00.000000Z' example: - uuid: 770e8400-e29b-41d4-a716-446655440000 name: 'Daily Review Alerts' trigger_type: new_review languages: - en - es auto_translate: true rating_filters: - 1 - 2 - 3 sentiment_filters: - negative condition_filters: logic: and rules: - field: platforms operator: contains value: - g2 notification_frequency: instant read_status: all is_active: true channels: [] projects: [] created_at: '2025-12-31T12:00:00.000000Z' updated_at: '2025-12-31T12:00:00.000000Z' tags: - Notifications post: summary: 'Create Notification' operationId: createNotification description: 'Create a new notification rule with channels and project mappings.' parameters: [] responses: 201: description: Success content: application/json: schema: type: object example: message: 'Notification created successfully.' data: uuid: 770e8400-e29b-41d4-a716-446655440000 name: 'Daily Review Alerts' trigger_type: new_review languages: - en - es auto_translate: true rating_filters: - 1 - 2 - 3 - 4 - 5 sentiment_filters: - positive - neutral - negative condition_filters: logic: and rules: - field: platforms operator: contains value: - g2 - capterra - field: sentiment operator: contains value: - negative notification_frequency: instant read_status: unread keyword: - ai - support content_type: post is_active: true channels: - uuid: 880e8400-e29b-41d4-a716-446655440001 channel_type: slack is_active: true config: channel_names: - alerts - incidents channel_url: 'https://hooks.slack.com/services/xxx' recipients: - alerts@company.com url: 'https://your-server.com/webhook/notifications' projects: - uuid: 990e8400-e29b-41d4-a716-446655440002 project_uuid: 5f1812b1-15f7-432d-996a-9ab5cfbe01d6 project_name: 'Acme App' is_active: true product_logo: 'https://cdn.example.com/logo.png' platforms: review: - g2 - capterra social: - reddit created_at: '2025-12-31T12:00:00.000000Z' updated_at: '2025-12-31T12:00:00.000000Z' properties: message: type: string example: 'Notification created successfully.' data: type: object properties: uuid: type: string example: 770e8400-e29b-41d4-a716-446655440000 name: type: string example: 'Daily Review Alerts' trigger_type: type: string example: new_review languages: type: array example: - en - es items: type: string auto_translate: type: boolean example: true rating_filters: type: array example: - 1 - 2 - 3 - 4 - 5 items: type: integer sentiment_filters: type: array example: - positive - neutral - negative items: type: string condition_filters: type: object properties: logic: type: string example: and rules: type: array example: - field: platforms operator: contains value: - g2 - capterra - field: sentiment operator: contains value: - negative items: type: object properties: field: type: string example: platforms operator: type: string example: contains value: type: array example: - g2 - capterra items: type: string notification_frequency: type: string example: instant read_status: type: string example: unread keyword: type: array example: - ai - support items: type: string content_type: type: string example: post is_active: type: boolean example: true channels: type: array example: - uuid: 880e8400-e29b-41d4-a716-446655440001 channel_type: slack is_active: true config: channel_names: - alerts - incidents channel_url: 'https://hooks.slack.com/services/xxx' recipients: - alerts@company.com url: 'https://your-server.com/webhook/notifications' items: type: object properties: uuid: type: string example: 880e8400-e29b-41d4-a716-446655440001 channel_type: type: string example: slack is_active: type: boolean example: true config: type: object properties: channel_names: type: array example: - alerts - incidents items: type: string channel_url: type: string example: 'https://hooks.slack.com/services/xxx' recipients: type: array example: - alerts@company.com items: type: string url: type: string example: 'https://your-server.com/webhook/notifications' projects: type: array example: - uuid: 990e8400-e29b-41d4-a716-446655440002 project_uuid: 5f1812b1-15f7-432d-996a-9ab5cfbe01d6 project_name: 'Acme App' is_active: true product_logo: 'https://cdn.example.com/logo.png' platforms: review: - g2 - capterra social: - reddit items: type: object properties: uuid: type: string example: 990e8400-e29b-41d4-a716-446655440002 project_uuid: type: string example: 5f1812b1-15f7-432d-996a-9ab5cfbe01d6 project_name: type: string example: 'Acme App' is_active: type: boolean example: true product_logo: type: string example: 'https://cdn.example.com/logo.png' platforms: type: object properties: review: type: array example: - g2 - capterra items: type: string social: type: array example: - reddit items: type: string created_at: type: string example: '2025-12-31T12:00:00.000000Z' updated_at: type: string example: '2025-12-31T12:00:00.000000Z' 422: description: 'Validation Error' content: application/json: schema: type: object example: message: 'The name field is required. (and 2 more errors)' errors: name: - 'The name field is required.' channels: - 'At least one channel is required.' projects: - 'Select at least one brand.' properties: message: type: string example: 'The name field is required. (and 2 more errors)' errors: type: object properties: name: type: array example: - 'The name field is required.' items: type: string channels: type: array example: - 'At least one channel is required.' items: type: string projects: type: array example: - 'Select at least one brand.' items: type: string tags: - Notifications requestBody: required: true content: application/json: schema: type: object properties: name: type: string description: 'The notification name.' example: 'Daily Review Alerts' trigger_type: type: string description: 'The trigger type. Defaults to new_review.' example: new_review is_active: type: boolean description: 'Whether the notification is active. Defaults to true.' example: true languages: type: array description: 'Language filters (empty = all languages).' example: - en - es - fr - de items: type: string auto_translate: type: boolean description: 'Auto-translate to English. Defaults to false.' example: true rating_filters: type: array description: 'Rating filters 1-5 (empty = all stars).' example: - 1 - 2 - 3 - 4 - 5 items: type: integer sentiment_filters: type: array description: 'Sentiment filters (empty = all).' example: - positive - neutral - negative items: type: string keyword: type: array description: 'Keyword filters (empty or omitted = all).' example: - ai - support items: type: string content_type: type: string description: 'Content type: post, comment, all. Defaults to all.' example: post notification_frequency: type: string description: 'Notification delivery frequency: instant, daily. Defaults to instant.' example: instant read_status: type: string description: 'Filter by read status: all, read, unread. Defaults to all.' example: all condition_filters: type: object description: 'Advanced condition filters with AND/OR logic.' example: [] properties: logic: type: string description: 'Logic operator for combining rules: and, or.' example: and rules: type: array description: 'Array of filter rules or nested groups.' example: - [] items: type: object properties: field: type: string description: 'Filter field: brands, platforms, read_status, keywords, type, sentiment, content, user.' example: platforms operator: type: string description: 'Filter operator: contains, not_contains.' example: contains value: type: mixed description: 'Filter value (string or array depending on field).' example: '["g2", "capterra"]' logic: type: string description: 'For nested groups: and, or.' example: or rules: type: array description: 'Nested rules array for grouped conditions.' example: - [] items: type: object required: - logic - rules nullable: true channels: type: array description: 'Delivery channels configuration. At least one required.' example: - [] items: type: object properties: channel_type: type: string description: 'Channel type: slack, email, ms_teams, lark, webhook.' example: slack is_active: type: boolean description: 'Whether channel is active.' example: true config: type: object description: 'Channel-specific configuration.' example: [] properties: channel_names: type: array description: 'Slack channel names.' example: - alerts - incidents items: type: string channel_url: type: string description: 'Slack channel webhook URL.' example: 'https://hooks.slack.com/services/xxx' nullable: true recipients: type: array description: 'Email recipients (required if email channel is active).' example: - alerts@company.com items: type: string url: type: string description: 'Custom webhook URL (required if webhook channel is active).' example: 'https://your-server.com/webhook/notifications' nullable: true nullable: true required: - channel_type projects: type: array description: 'Project/brand mappings. At least one required.' example: - [] items: type: object properties: project_uuid: type: string description: 'The project UUID.' example: 5f1812b1-15f7-432d-996a-9ab5cfbe01d6 is_active: type: boolean description: 'Whether project mapping is active.' example: true platforms: type: object description: 'Platform selections for this project.' example: [] properties: review: type: array description: 'Review platforms to monitor. Supported: g2, capterra, software_advice, trustpilot, omr_reviews, clutch, sourceforge, product_hunt, hubspot_directory, goodfirms, chrome_web_store, google_workspace_marketplace, subscribed_fyi, google_maps, google_play, apple_app_store, play_store, app_store, google_reviews, gartner, getapp, yotpo, product_review, trustradius.' example: - g2 - capterra - product_hunt - google_play items: type: string social: type: array description: 'Social platforms to monitor.' example: - reddit - hackernews - linkedin items: type: string nullable: true product_logo: type: string description: 'Logo URL for the project (optional).' example: 'https://cdn.example.com/logo.png' nullable: true required: - project_uuid required: - name - channels - projects '/api/notifications/{uuid}': get: summary: 'Get Notification' operationId: getNotification description: 'Get details of a specific notification.' parameters: [] responses: 200: description: '' content: application/json: schema: type: object example: uuid: 770e8400-e29b-41d4-a716-446655440000 name: 'Daily Review Alerts' trigger_type: new_review languages: - en - es auto_translate: true rating_filters: - 1 - 2 - 3 sentiment_filters: - negative condition_filters: logic: and rules: - field: platforms operator: contains value: - g2 notification_frequency: instant read_status: all is_active: true channels: [] projects: [] created_at: '2025-12-31T12:00:00.000000Z' updated_at: '2025-12-31T12:00:00.000000Z' properties: uuid: type: string example: 770e8400-e29b-41d4-a716-446655440000 name: type: string example: 'Daily Review Alerts' trigger_type: type: string example: new_review languages: type: array example: - en - es items: type: string auto_translate: type: boolean example: true rating_filters: type: array example: - 1 - 2 - 3 items: type: integer sentiment_filters: type: array example: - negative items: type: string condition_filters: type: object properties: logic: type: string example: and rules: type: array example: - field: platforms operator: contains value: - g2 items: type: object properties: field: type: string example: platforms operator: type: string example: contains value: type: array example: - g2 items: type: string notification_frequency: type: string example: instant read_status: type: string example: all is_active: type: boolean example: true channels: type: array example: [] projects: type: array example: [] created_at: type: string example: '2025-12-31T12:00:00.000000Z' updated_at: type: string example: '2025-12-31T12:00:00.000000Z' tags: - Notifications put: summary: 'Update Notification' operationId: updateNotification description: 'Update an existing notification rule with channels and project mappings.' parameters: [] responses: 200: description: Success content: application/json: schema: type: object example: message: 'Notification updated successfully.' data: uuid: 770e8400-e29b-41d4-a716-446655440000 name: 'Updated Review Alerts' trigger_type: new_review languages: - en auto_translate: false rating_filters: - 1 - 2 sentiment_filters: - negative condition_filters: logic: and rules: - field: sentiment operator: contains value: - negative notification_frequency: daily read_status: unread keyword: - ai - support content_type: comment is_active: true channels: [] projects: [] created_at: '2025-12-31T12:00:00.000000Z' updated_at: '2025-12-31T13:00:00.000000Z' properties: message: type: string example: 'Notification updated successfully.' data: type: object properties: uuid: type: string example: 770e8400-e29b-41d4-a716-446655440000 name: type: string example: 'Updated Review Alerts' trigger_type: type: string example: new_review languages: type: array example: - en items: type: string auto_translate: type: boolean example: false rating_filters: type: array example: - 1 - 2 items: type: integer sentiment_filters: type: array example: - negative items: type: string condition_filters: type: object properties: logic: type: string example: and rules: type: array example: - field: sentiment operator: contains value: - negative items: type: object properties: field: type: string example: sentiment operator: type: string example: contains value: type: array example: - negative items: type: string notification_frequency: type: string example: daily read_status: type: string example: unread keyword: type: array example: - ai - support items: type: string content_type: type: string example: comment is_active: type: boolean example: true channels: type: array example: [] projects: type: array example: [] created_at: type: string example: '2025-12-31T12:00:00.000000Z' updated_at: type: string example: '2025-12-31T13:00:00.000000Z' 404: description: 'Not Found' content: application/json: schema: type: object example: message: 'Notification not found.' properties: message: type: string example: 'Notification not found.' tags: - Notifications requestBody: required: true content: application/json: schema: type: object properties: name: type: string description: 'The notification name.' example: 'Daily Review Alerts' trigger_type: type: string description: 'The trigger type.' example: new_review is_active: type: boolean description: 'Whether the notification is active.' example: true languages: type: array description: 'Language filters (empty = all languages).' example: - en - es - fr - de items: type: string auto_translate: type: boolean description: 'Auto-translate to English.' example: true rating_filters: type: array description: 'Rating filters 1-5.' example: - 1 - 2 - 3 - 4 - 5 items: type: integer sentiment_filters: type: array description: 'Sentiment filters.' example: - positive - neutral - negative items: type: string keyword: type: array description: 'Keyword filters (empty or omitted = all).' example: - ai - support items: type: string content_type: type: string description: 'Content type: post, comment, all. Defaults to all.' example: post notification_frequency: type: string description: 'Notification delivery frequency: instant, daily.' example: instant read_status: type: string description: 'Filter by read status: all, read, unread.' example: all condition_filters: type: object description: 'Advanced condition filters with AND/OR logic.' example: [] properties: logic: type: string description: 'Logic operator for combining rules: and, or.' example: and rules: type: array description: 'Array of filter rules or nested groups.' example: - [] items: type: object properties: field: type: string description: 'Filter field: brands, platforms, read_status, keywords, type, sentiment, content, user.' example: platforms operator: type: string description: 'Filter operator: contains, not_contains.' example: contains value: type: mixed description: 'Filter value (string or array depending on field).' example: '["g2", "capterra"]' required: - logic - rules nullable: true channels: type: array description: 'Delivery channels configuration.' example: - [] items: type: object properties: channel_type: type: string description: 'Channel type: slack, email, ms_teams, lark, webhook.' example: slack is_active: type: boolean description: 'Whether channel is active.' example: true config: type: object description: 'Channel-specific configuration.' example: [] properties: channel_names: type: array description: 'Slack channel names.' example: - alerts - incidents items: type: string channel_url: type: string description: 'Slack channel webhook URL.' example: 'https://hooks.slack.com/services/xxx' nullable: true recipients: type: array description: 'Email recipients (required if email channel is active).' example: - alerts@company.com items: type: string url: type: string description: 'Custom webhook URL (required if webhook channel is active).' example: 'https://your-server.com/webhook/notifications' nullable: true nullable: true required: - channel_type projects: type: array description: 'Project/brand mappings.' example: - [] items: type: object properties: project_uuid: type: string description: 'The project UUID.' example: 5f1812b1-15f7-432d-996a-9ab5cfbe01d6 is_active: type: boolean description: 'Whether project mapping is active.' example: true platforms: type: object description: 'Platform selections for this project.' example: [] properties: review: type: array description: '' example: - software_advice items: type: string enum: - g2 - capterra - trustpilot - trustradius - product_hunt - gartner - getapp - yotpo - software_advice - omr_reviews - product_review - subscribed_fyi - app_store - play_store - google_reviews - clutch - sourceforge - hubspot_directory - goodfirms - chrome_web_store - google_workspace_marketplace - google_maps - google_play - apple_app_store social: type: array description: '' example: - reddit items: type: string enum: - reddit - github - stackoverflow - hackernews - youtube - linkedin - x nullable: true product_logo: type: string description: 'Must not be greater than 2048 characters.' example: g nullable: true required: - project_uuid required: - name - channels - projects delete: summary: 'Delete Notification' operationId: deleteNotification description: 'Delete a notification and all associated channels and project mappings.' parameters: [] responses: 200: description: '' content: application/json: schema: type: object example: message: 'Notification deleted successfully.' properties: message: type: string example: 'Notification deleted successfully.' 404: description: '' content: application/json: schema: type: object example: message: 'Notification not found.' properties: message: type: string example: 'Notification not found.' tags: - Notifications parameters: - in: path name: uuid description: 'The notification UUID.' example: 770e8400-e29b-41d4-a716-446655440000 required: true schema: type: string /api/onboarding: post: summary: 'Complete Onboarding' operationId: completeOnboarding description: "Create an organisation and project in a single step during onboarding.\nThe authenticated user becomes the organisation owner." parameters: [] responses: 201: description: '' content: application/json: schema: type: object example: message: 'Onboarding completed successfully.' data: organisation_uuid: 550e8400-e29b-41d4-a716-446655440000 product_uuid: 660e8400-e29b-41d4-a716-446655440001 organisation: uuid: 550e8400-e29b-41d4-a716-446655440000 name: 'Acme Inc' website: 'https://acme.com' project: uuid: 660e8400-e29b-41d4-a716-446655440001 product_name: 'Acme CRM' product_website: 'https://acme.com' product_logo: 'https://cdn.brandfetch.io/acme.com/fallback/lettermark/icon?c=BRANDFETCH_CLIENT_ID' review_platforms: { } reddit_keywords: - acme - 'acme crm' reddit_brand_name: Acme scraper_sync: success: true synced_platforms: - g2 - capterra errors: [] brand_sync: success: true brand_id: '12345' error: null properties: message: type: string example: 'Onboarding completed successfully.' data: type: object properties: organisation_uuid: type: string example: 550e8400-e29b-41d4-a716-446655440000 product_uuid: type: string example: 660e8400-e29b-41d4-a716-446655440001 organisation: type: object properties: uuid: type: string example: 550e8400-e29b-41d4-a716-446655440000 name: type: string example: 'Acme Inc' website: type: string example: 'https://acme.com' project: type: object properties: uuid: type: string example: 660e8400-e29b-41d4-a716-446655440001 product_name: type: string example: 'Acme CRM' product_website: type: string example: 'https://acme.com' product_logo: type: string example: 'https://cdn.brandfetch.io/acme.com/fallback/lettermark/icon?c=BRANDFETCH_CLIENT_ID' review_platforms: type: object properties: { } reddit_keywords: type: array example: - acme - 'acme crm' items: type: string reddit_brand_name: type: string example: Acme scraper_sync: type: object properties: success: type: boolean example: true synced_platforms: type: array example: - g2 - capterra items: type: string errors: type: array example: [] brand_sync: type: object properties: success: type: boolean example: true brand_id: type: string example: '12345' error: type: string example: null 422: description: 'Validation error' content: application/json: schema: type: object example: message: 'The organisation name field is required.' errors: organisation_name: - 'The organisation name field is required.' properties: message: type: string example: 'The organisation name field is required.' errors: type: object properties: organisation_name: type: array example: - 'The organisation name field is required.' items: type: string tags: - Onboarding requestBody: required: true content: application/json: schema: type: object properties: organisation_name: type: string description: 'The organisation/company name.' example: 'Acme Inc' product_name: type: string description: 'The product name.' example: 'Acme CRM' product_website: type: string description: 'The product website URL.' example: 'https://acme.com' product_logo: type: string description: 'The product logo URL.' example: 'https://cdn.brandfetch.io/acme.com/fallback/lettermark/icon?c=BRANDFETCH_CLIENT_ID' review_platforms: type: object description: 'Review platform settings with enabled status and URLs.' example: g2: enabled: true url: 'https://g2.com/products/acme' capterra: enabled: false url: null properties: { } reddit_keywords: type: array description: 'Reddit keyword variations to track.' example: - acme - 'acme crm' items: type: string reddit_brand_name: type: string description: 'The brand name for Reddit tracking.' example: Acme required: - organisation_name - product_name put: summary: 'Update Onboarding' operationId: updateOnboarding description: 'Update the current organisation and project for the authenticated user.' parameters: [] responses: 200: description: '' content: application/json: schema: type: object example: message: 'Onboarding updated successfully.' data: organisation: uuid: 550e8400-e29b-41d4-a716-446655440000 name: 'Acme Inc' website: 'https://acme.com' project: uuid: 660e8400-e29b-41d4-a716-446655440001 product_name: 'Acme CRM' product_website: 'https://acme.com' product_logo: 'https://cdn.brandfetch.io/acme.com/fallback/lettermark/icon?c=BRANDFETCH_CLIENT_ID' review_platforms: { } reddit_keywords: - acme - 'acme crm' reddit_brand_name: Acme scraper_sync: success: true synced_platforms: - g2 - capterra errors: [] brand_sync: success: true brand_id: '12345' error: null properties: message: type: string example: 'Onboarding updated successfully.' data: type: object properties: organisation: type: object properties: uuid: type: string example: 550e8400-e29b-41d4-a716-446655440000 name: type: string example: 'Acme Inc' website: type: string example: 'https://acme.com' project: type: object properties: uuid: type: string example: 660e8400-e29b-41d4-a716-446655440001 product_name: type: string example: 'Acme CRM' product_website: type: string example: 'https://acme.com' product_logo: type: string example: 'https://cdn.brandfetch.io/acme.com/fallback/lettermark/icon?c=BRANDFETCH_CLIENT_ID' review_platforms: type: object properties: { } reddit_keywords: type: array example: - acme - 'acme crm' items: type: string reddit_brand_name: type: string example: Acme scraper_sync: type: object properties: success: type: boolean example: true synced_platforms: type: array example: - g2 - capterra items: type: string errors: type: array example: [] brand_sync: type: object properties: success: type: boolean example: true brand_id: type: string example: '12345' error: type: string example: null 404: description: 'No organisation or project found' content: application/json: schema: type: object example: message: 'No organisation or project found for the current user.' properties: message: type: string example: 'No organisation or project found for the current user.' 422: description: 'Validation error' content: application/json: schema: type: object example: message: 'The organisation name field is required.' errors: organisation_name: - 'The organisation name field is required.' properties: message: type: string example: 'The organisation name field is required.' errors: type: object properties: organisation_name: type: array example: - 'The organisation name field is required.' items: type: string tags: - Onboarding requestBody: required: true content: application/json: schema: type: object properties: organisation_name: type: string description: 'The organisation/company name.' example: 'Acme Inc' product_name: type: string description: 'The product name.' example: 'Acme CRM' product_website: type: string description: 'The product website URL.' example: 'https://acme.com' product_logo: type: string description: 'The product logo URL.' example: 'https://cdn.brandfetch.io/acme.com/fallback/lettermark/icon?c=BRANDFETCH_CLIENT_ID' review_platforms: type: object description: 'Review platform settings with enabled status and URLs.' example: g2: enabled: true url: 'https://g2.com/products/acme' capterra: enabled: false url: null properties: { } reddit_keywords: type: array description: 'Reddit keyword variations to track.' example: - acme - 'acme crm' items: type: string reddit_brand_name: type: string description: 'The brand name for Reddit tracking.' example: Acme required: - organisation_name - product_name /api/platform-urls: post: summary: 'Search Platform URLs' operationId: searchPlatformURLs description: "Search for a product's listing pages across multiple review platforms using Perplexity AI-powered web search.\nEach URL is validated and assigned a status: valid, invalid, or needs_review." parameters: [] responses: 200: description: '' content: application/json: schema: oneOf: - description: 'All platforms' type: object example: product_name: Slack product_website: 'https://slack.com' reviews: g2: url: 'https://www.g2.com/products/slack/reviews' status: valid capterra: url: 'https://www.capterra.com/p/135003/Slack/' status: valid software_advice: url: 'https://www.softwareadvice.com/team-communication/slack-profile/' status: needs_review trustpilot: url: 'https://www.trustpilot.com/review/slack.com' status: valid omr_reviews: url: 'https://omr.com/reviews/product/slack' status: valid clutch: url: null status: invalid sourceforge: url: 'https://sourceforge.net/software/product/Slack/' status: valid product_hunt: url: 'https://www.producthunt.com/products/slack' status: valid hubspot_directory: url: null status: invalid goodfirms: url: 'https://www.goodfirms.co/software/slack' status: valid chrome_web_store: url: null status: invalid google_workspace_marketplace: url: 'https://workspace.google.com/marketplace/app/slack/429783454934' status: needs_review app_store: url: null status: invalid play_store: url: null status: invalid subscribed_fyi: url: 'https://subscribed.fyi/slack/reviews/' status: valid properties: product_name: type: string example: Slack product_website: type: string example: 'https://slack.com' reviews: type: object properties: g2: type: object properties: url: type: string example: 'https://www.g2.com/products/slack/reviews' status: type: string example: valid capterra: type: object properties: url: type: string example: 'https://www.capterra.com/p/135003/Slack/' status: type: string example: valid software_advice: type: object properties: url: type: string example: 'https://www.softwareadvice.com/team-communication/slack-profile/' status: type: string example: needs_review trustpilot: type: object properties: url: type: string example: 'https://www.trustpilot.com/review/slack.com' status: type: string example: valid omr_reviews: type: object properties: url: type: string example: 'https://omr.com/reviews/product/slack' status: type: string example: valid clutch: type: object properties: url: type: string example: null status: type: string example: invalid sourceforge: type: object properties: url: type: string example: 'https://sourceforge.net/software/product/Slack/' status: type: string example: valid product_hunt: type: object properties: url: type: string example: 'https://www.producthunt.com/products/slack' status: type: string example: valid hubspot_directory: type: object properties: url: type: string example: null status: type: string example: invalid goodfirms: type: object properties: url: type: string example: 'https://www.goodfirms.co/software/slack' status: type: string example: valid chrome_web_store: type: object properties: url: type: string example: null status: type: string example: invalid google_workspace_marketplace: type: object properties: url: type: string example: 'https://workspace.google.com/marketplace/app/slack/429783454934' status: type: string example: needs_review app_store: type: object properties: url: type: string example: null status: type: string example: invalid play_store: type: object properties: url: type: string example: null status: type: string example: invalid subscribed_fyi: type: object properties: url: type: string example: 'https://subscribed.fyi/slack/reviews/' status: type: string example: valid - description: 'Filtered by platform' type: object example: product_name: Slack product_website: 'https://slack.com' reviews: g2: url: 'https://www.g2.com/products/slack/reviews' status: valid properties: product_name: type: string example: Slack product_website: type: string example: 'https://slack.com' reviews: type: object properties: g2: type: object properties: url: type: string example: 'https://www.g2.com/products/slack/reviews' status: type: string example: valid 422: description: 'Validation error' content: application/json: schema: type: object example: message: 'The product name field is required.' errors: product_name: - 'The product name field is required.' properties: message: type: string example: 'The product name field is required.' errors: type: object properties: product_name: type: array example: - 'The product name field is required.' items: type: string tags: - Onboarding requestBody: required: true content: application/json: schema: type: object properties: product_name: type: string description: 'The name of the product to search for.' example: Slack product_website: type: string description: "The product's website URL." example: 'https://slack.com' platform: type: string description: "Optional platform filter. If provided, only returns that platform's URL. Valid values: g2, capterra, software_advice, trustpilot, omr_reviews, clutch, sourceforge, product_hunt, hubspot_directory, goodfirms, chrome_web_store, google_workspace_marketplace, play_store, app_store, subscribed_fyi." example: g2 nullable: true required: - product_name - product_website /api/reddit-keywords: post: summary: 'Generate Reddit Keywords' operationId: generateRedditKeywords description: 'Generate commonly used variations of a brand name for Reddit tracking, including misspellings, abbreviations, and nicknames.' parameters: [] responses: 200: description: '' content: application/json: schema: type: object example: brand_name: Salesforce top_variations: exact: Salesforce misspellings: - Salesfoce - Salseforce - Saleforce abbreviations: - SF - SFDC nicknames: - 'The Force' - 'SF CRM' - 'Sales Cloud' properties: brand_name: type: string example: Salesforce top_variations: type: object properties: exact: type: string example: Salesforce misspellings: type: array example: - Salesfoce - Salseforce - Saleforce items: type: string abbreviations: type: array example: - SF - SFDC items: type: string nicknames: type: array example: - 'The Force' - 'SF CRM' - 'Sales Cloud' items: type: string 422: description: 'Validation error' content: application/json: schema: type: object example: message: 'The brand name field is required.' errors: brand_name: - 'The brand name field is required.' properties: message: type: string example: 'The brand name field is required.' errors: type: object properties: brand_name: type: array example: - 'The brand name field is required.' items: type: string tags: - Onboarding requestBody: required: true content: application/json: schema: type: object properties: brand_name: type: string description: 'The brand name to generate variations for.' example: Salesforce required: - brand_name /api/organisations: get: summary: 'List Organisations' operationId: listOrganisations description: 'Get all organisations the authenticated user belongs to.' parameters: [] responses: 200: description: '' content: application/json: schema: type: object example: data: - uuid: 550e8400-e29b-41d4-a716-446655440000 name: 'Acme Inc' website: 'https://acme.com' role: organisation_owner created_at: '2025-12-10T10:00:00.000000Z' updated_at: '2025-12-10T10:00:00.000000Z' properties: data: type: array example: - uuid: 550e8400-e29b-41d4-a716-446655440000 name: 'Acme Inc' website: 'https://acme.com' role: organisation_owner created_at: '2025-12-10T10:00:00.000000Z' updated_at: '2025-12-10T10:00:00.000000Z' items: type: object properties: uuid: type: string example: 550e8400-e29b-41d4-a716-446655440000 name: type: string example: 'Acme Inc' website: type: string example: 'https://acme.com' role: type: string example: organisation_owner created_at: type: string example: '2025-12-10T10:00:00.000000Z' updated_at: type: string example: '2025-12-10T10:00:00.000000Z' tags: - Organisations post: summary: 'Create Organisation' operationId: createOrganisation description: 'Create a new organisation. The authenticated user becomes the admin.' parameters: [] responses: 201: description: '' content: application/json: schema: type: object example: message: 'Organisation created successfully.' data: uuid: 550e8400-e29b-41d4-a716-446655440000 name: 'Acme Inc' website: 'https://acme.com' role: organisation_owner created_at: '2025-12-10T10:00:00.000000Z' updated_at: '2025-12-10T10:00:00.000000Z' properties: message: type: string example: 'Organisation created successfully.' data: type: object properties: uuid: type: string example: 550e8400-e29b-41d4-a716-446655440000 name: type: string example: 'Acme Inc' website: type: string example: 'https://acme.com' role: type: string example: organisation_owner created_at: type: string example: '2025-12-10T10:00:00.000000Z' updated_at: type: string example: '2025-12-10T10:00:00.000000Z' tags: - Organisations requestBody: required: true content: application/json: schema: type: object properties: name: type: string description: 'The organisation name.' example: 'Acme Inc' website: type: string description: 'optional The organisation website URL.' example: 'https://acme.com' nullable: true required: - name '/api/organisations/{uuid}': get: summary: 'Get Organisation' operationId: getOrganisation description: 'Get details of a specific organisation.' parameters: [] responses: 200: description: '' content: application/json: schema: type: object example: data: uuid: 550e8400-e29b-41d4-a716-446655440000 name: 'Acme Inc' website: 'https://acme.com' role: organisation_owner created_at: '2025-12-10T10:00:00.000000Z' updated_at: '2025-12-10T10:00:00.000000Z' properties: data: type: object properties: uuid: type: string example: 550e8400-e29b-41d4-a716-446655440000 name: type: string example: 'Acme Inc' website: type: string example: 'https://acme.com' role: type: string example: organisation_owner created_at: type: string example: '2025-12-10T10:00:00.000000Z' updated_at: type: string example: '2025-12-10T10:00:00.000000Z' 403: description: 'No access' content: application/json: schema: type: object example: message: 'You do not have access to this organisation.' properties: message: type: string example: 'You do not have access to this organisation.' tags: - Organisations put: summary: 'Update Organisation' operationId: updateOrganisation description: "Update an organisation's details. Requires admin role." parameters: [] responses: 200: description: '' content: application/json: schema: type: object example: message: 'Organisation updated successfully.' data: uuid: 550e8400-e29b-41d4-a716-446655440000 name: 'Acme Corp' website: 'https://acme.com' role: organisation_owner created_at: '2025-12-10T10:00:00.000000Z' updated_at: '2025-12-10T10:00:00.000000Z' properties: message: type: string example: 'Organisation updated successfully.' data: type: object properties: uuid: type: string example: 550e8400-e29b-41d4-a716-446655440000 name: type: string example: 'Acme Corp' website: type: string example: 'https://acme.com' role: type: string example: organisation_owner created_at: type: string example: '2025-12-10T10:00:00.000000Z' updated_at: type: string example: '2025-12-10T10:00:00.000000Z' 403: description: 'Not admin' content: application/json: schema: type: object example: message: 'You must be an organisation admin to perform this action.' properties: message: type: string example: 'You must be an organisation admin to perform this action.' tags: - Organisations requestBody: required: true content: application/json: schema: type: object properties: name: type: string description: 'The organisation name.' example: 'Acme Corp' website: type: string description: 'optional The organisation website URL.' example: 'https://acme.com' nullable: true required: - name parameters: - in: path name: uuid description: '' example: b47b1b40-d5f7-4e9c-959a-b7affbc9965c required: true schema: type: string - in: path name: organisation description: 'The organisation UUID.' example: 550e8400-e29b-41d4-a716-446655440000 required: true schema: type: string '/api/organisations/{organisation_uuid}/switch': post: summary: 'Switch Current Organisation' operationId: switchCurrentOrganisation description: "Set the user's current organisation for subsequent requests." parameters: [] responses: 200: description: '' content: application/json: schema: type: object example: message: 'Switched to organisation successfully.' data: uuid: 550e8400-e29b-41d4-a716-446655440000 name: 'Acme Inc' properties: message: type: string example: 'Switched to organisation successfully.' data: type: object properties: uuid: type: string example: 550e8400-e29b-41d4-a716-446655440000 name: type: string example: 'Acme Inc' 403: description: 'No access' content: application/json: schema: type: object example: message: 'You do not have access to this organisation.' properties: message: type: string example: 'You do not have access to this organisation.' tags: - Organisations parameters: - in: path name: organisation_uuid description: '' example: b47b1b40-d5f7-4e9c-959a-b7affbc9965c required: true schema: type: string - in: path name: organisation description: 'The organisation UUID.' example: 550e8400-e29b-41d4-a716-446655440000 required: true schema: type: string /api/platforms: get: summary: 'List All Platforms' operationId: listAllPlatforms description: 'Get all platforms.' parameters: [] responses: 200: description: '' content: application/json: schema: type: array items: type: object properties: uuid: type: string example: 550e8400-e29b-41d4-a716-446655440000 name: type: string example: G2 created_at: type: string example: '2026-01-08T10:00:00.000000Z' updated_at: type: string example: '2026-01-08T10:00:00.000000Z' example: - uuid: 550e8400-e29b-41d4-a716-446655440000 name: G2 created_at: '2026-01-08T10:00:00.000000Z' updated_at: '2026-01-08T10:00:00.000000Z' tags: - Platforms post: summary: 'Create Platform' operationId: createPlatform description: 'Create a new platform.' parameters: [] responses: 201: description: '' content: application/json: schema: type: object example: message: 'Platform created successfully.' data: uuid: 550e8400-e29b-41d4-a716-446655440000 name: G2 created_at: '2026-01-08T10:00:00.000000Z' updated_at: '2026-01-08T10:00:00.000000Z' properties: message: type: string example: 'Platform created successfully.' data: type: object properties: uuid: type: string example: 550e8400-e29b-41d4-a716-446655440000 name: type: string example: G2 created_at: type: string example: '2026-01-08T10:00:00.000000Z' updated_at: type: string example: '2026-01-08T10:00:00.000000Z' 422: description: 'Validation Error' content: application/json: schema: type: object example: message: 'The name has already been taken.' errors: name: - 'The name has already been taken.' properties: message: type: string example: 'The name has already been taken.' errors: type: object properties: name: type: array example: - 'The name has already been taken.' items: type: string tags: - Platforms requestBody: required: true content: application/json: schema: type: object properties: name: type: string description: 'The platform name.' example: G2 required: - name '/api/platforms/{uuid}': patch: summary: 'Update Platform' operationId: updatePlatform description: "Update a platform's details." parameters: [] responses: 200: description: '' content: application/json: schema: type: object example: message: 'Platform updated successfully.' data: uuid: 550e8400-e29b-41d4-a716-446655440000 name: Capterra created_at: '2026-01-08T10:00:00.000000Z' updated_at: '2026-01-08T10:30:00.000000Z' properties: message: type: string example: 'Platform updated successfully.' data: type: object properties: uuid: type: string example: 550e8400-e29b-41d4-a716-446655440000 name: type: string example: Capterra created_at: type: string example: '2026-01-08T10:00:00.000000Z' updated_at: type: string example: '2026-01-08T10:30:00.000000Z' 404: description: 'Not Found' content: application/json: schema: type: object example: message: 'Platform not found.' properties: message: type: string example: 'Platform not found.' 422: description: 'Validation Error' content: application/json: schema: type: object example: message: 'The name has already been taken.' errors: name: - 'The name has already been taken.' properties: message: type: string example: 'The name has already been taken.' errors: type: object properties: name: type: array example: - 'The name has already been taken.' items: type: string tags: - Platforms requestBody: required: true content: application/json: schema: type: object properties: name: type: string description: 'The platform name.' example: Capterra required: - name delete: summary: 'Delete Platform' operationId: deletePlatform description: 'Delete a platform.' parameters: [] responses: 200: description: '' content: application/json: schema: type: object example: message: 'Platform deleted successfully.' properties: message: type: string example: 'Platform deleted successfully.' 404: description: 'Not Found' content: application/json: schema: type: object example: message: 'Platform not found.' properties: message: type: string example: 'Platform not found.' tags: - Platforms parameters: - in: path name: uuid description: 'The platform UUID.' example: 550e8400-e29b-41d4-a716-446655440000 required: true schema: type: string /api/products/search: get: summary: 'Search Products' operationId: searchProducts description: 'Search for products in the Curiosity database to claim.' parameters: - in: query name: q description: 'The search query.' example: slack required: true schema: type: string description: 'The search query.' example: slack - in: query name: limit description: 'The maximum number of results (default 10, max 50).' example: 10 required: false schema: type: integer description: 'The maximum number of results (default 10, max 50).' example: 10 responses: 200: description: '' content: application/json: schema: type: object example: data: products: - id: 123 name: Slack url: 'https://slack.com' logo_path: products/slack-logo.png properties: data: type: object properties: products: type: array example: - id: 123 name: Slack url: 'https://slack.com' logo_path: products/slack-logo.png items: type: object properties: id: type: integer example: 123 name: type: string example: Slack url: type: string example: 'https://slack.com' logo_path: type: string example: products/slack-logo.png tags: - Profiles /api/claim-profiles: get: summary: 'List All Claim Profiles' operationId: listAllClaimProfiles description: 'Get all claim profiles across all organisations. Intended for admin panel usage.' parameters: [] responses: 200: description: '' content: application/json: schema: type: array items: type: object properties: uuid: type: string example: 880e8400-e29b-41d4-a716-446655440000 business_email: type: string example: john@company.com job_title: type: string example: 'Product Manager' business_phone: type: string example: '+1234567890' status: type: string example: pending project: type: object properties: uuid: type: string example: 660e8400-e29b-41d4-a716-446655440001 product_name: type: string example: 'Acme App' organisation: type: object properties: uuid: type: string example: 550e8400-e29b-41d4-a716-446655440000 name: type: string example: 'Acme Corp' status_updated_by: type: string example: null created_at: type: string example: '2026-01-03T12:00:00.000000Z' updated_at: type: string example: '2026-01-03T12:00:00.000000Z' example: - uuid: 880e8400-e29b-41d4-a716-446655440000 business_email: john@company.com job_title: 'Product Manager' business_phone: '+1234567890' status: pending project: uuid: 660e8400-e29b-41d4-a716-446655440001 product_name: 'Acme App' organisation: uuid: 550e8400-e29b-41d4-a716-446655440000 name: 'Acme Corp' status_updated_by: null created_at: '2026-01-03T12:00:00.000000Z' updated_at: '2026-01-03T12:00:00.000000Z' tags: - Profiles post: summary: 'Create Claim Profile' operationId: createClaimProfile description: "Create a new claim profile for a project. The organisation is automatically\nderived from the authenticated user's current organisation context." parameters: [] responses: 201: description: Success content: application/json: schema: type: object example: message: 'Claim profile created successfully.' data: uuid: 880e8400-e29b-41d4-a716-446655440000 scraper_product_id: 123 product_name: Slack product_url: 'https://slack.com' business_email: john@company.com job_title: 'Product Manager' business_phone: '+1234567890' status: pending created_at: '2026-01-03T12:00:00.000000Z' updated_at: '2026-01-03T12:00:00.000000Z' properties: message: type: string example: 'Claim profile created successfully.' data: type: object properties: uuid: type: string example: 880e8400-e29b-41d4-a716-446655440000 scraper_product_id: type: integer example: 123 product_name: type: string example: Slack product_url: type: string example: 'https://slack.com' business_email: type: string example: john@company.com job_title: type: string example: 'Product Manager' business_phone: type: string example: '+1234567890' status: type: string example: pending created_at: type: string example: '2026-01-03T12:00:00.000000Z' updated_at: type: string example: '2026-01-03T12:00:00.000000Z' 403: description: 'No organisation context' content: application/json: schema: type: object example: message: 'This action is unauthorized.' properties: message: type: string example: 'This action is unauthorized.' 422: description: 'Validation Error' content: application/json: schema: type: object example: message: 'The project is required. (and 1 more error)' errors: project_id: - 'The project is required.' business_email: - 'Please use a business email address.' properties: message: type: string example: 'The project is required. (and 1 more error)' errors: type: object properties: project_id: type: array example: - 'The project is required.' items: type: string business_email: type: array example: - 'Please use a business email address.' items: type: string tags: - Profiles requestBody: required: true content: application/json: schema: type: object properties: project_id: type: string description: 'The project UUID.' example: 660e8400-e29b-41d4-a716-446655440001 scraper_product_id: type: integer description: 'The ID of the product from Curiosity.' example: 123 product_name: type: string description: 'The name of the product being claimed.' example: Slack product_url: type: string description: 'The URL of the product.' example: 'https://slack.com' nullable: true business_email: type: string description: 'The business email address.' example: john@company.com job_title: type: string description: 'The job title.' example: 'Product Manager' business_phone: type: string description: 'The business phone number (optional).' example: '+1234567890' nullable: true required: - project_id - scraper_product_id - product_name - business_email - job_title '/api/projects/{project_uuid}/profile': get: summary: 'Get Product Profile' operationId: getProductProfile description: "Get the product profile for a specific project.\nReturns all profile data including content, SEO meta, categories, and sync status." parameters: [] responses: 200: description: '' content: application/json: schema: type: object example: data: uuid: 770e8400-e29b-41d4-a716-446655440002 curiosity_product_id: 123 name: Slack url: 'https://slack.com' logo_path: products/slack-logo.png local_logo: null subtitle: '

Where work happens

' overview: '

Slack is a messaging platform...

' pricing: '

Free, Pro $7.25/user, Business+ $12.50/user

' verified_badge: true parent_category: id: 5 name: Communication review_platforms: g2: enabled: true url: 'https://g2.com/products/slack' score: 4.5 reviews_count: 120 capterra: enabled: true url: 'https://capterra.com/p/123/slack' score: 4.6 reviews_count: 85 trustpilot: score: 3.6 reviews_count: 11218 categories: - id: 1 name: Communication segments: - id: 1 name: Enterprise search_fields: built_for: - id: 1 name: 'Marketing Teams' platform: - id: 2 name: Web pricing_model: - id: 3 name: Subscription competitors: - id: 456 name: 'Microsoft Teams' url: 'https://teams.microsoft.com' logo_path: null videos: - 'https://www.youtube.com/watch?v=abc' awards: - id: 1 name: 'Best Communication Tool 2025' deal: 'Get 20% off annual plans' deals_meta_title: 'Best Slack Deals' deals_meta_description: null cancellation_content: '

To cancel your subscription...

' cancellation_content_summary: '

Cancel anytime from settings

' book_demo_url: 'https://slack.com/demo' pricing_url: 'https://slack.com/pricing' pros_cons: "Pros: Easy to use\nCons: Can be expensive" analysis: '

Detailed analysis of Slack...

' faq: "Q: How much does it cost?\nA: Free tier available" alternatives_text: '

Consider Microsoft Teams or Discord...

' pricing_range: $0-$15/user/mo is_ai_powered: false meta: main_page: title: 'Slack - Where Work Happens' description: 'Team messaging platform' deals: title: 'Slack Deals' description: 'Best Slack discounts' cancellation: title: 'Cancel Slack' description: 'How to cancel' sync_status: synced synced_at: '2026-01-06T10:00:00.000000Z' local_changes_at: null last_change_request: uuid: 880e8400-e29b-41d4-a716-446655440003 status: pending requested_at: '2026-01-06T11:00:00.000000Z' reviewed_at: null created_at: '2026-01-06T09:00:00.000000Z' updated_at: '2026-01-06T10:00:00.000000Z' properties: data: type: object properties: uuid: type: string example: 770e8400-e29b-41d4-a716-446655440002 curiosity_product_id: type: integer example: 123 name: type: string example: Slack url: type: string example: 'https://slack.com' logo_path: type: string example: products/slack-logo.png local_logo: type: string example: null subtitle: type: string example: '

Where work happens

' overview: type: string example: '

Slack is a messaging platform...

' pricing: type: string example: '

Free, Pro $7.25/user, Business+ $12.50/user

' verified_badge: type: boolean example: true parent_category: type: object properties: id: type: integer example: 5 name: type: string example: Communication review_platforms: type: object properties: g2: type: object properties: enabled: type: boolean example: true url: type: string example: 'https://g2.com/products/slack' score: type: number example: 4.5 reviews_count: type: integer example: 120 capterra: type: object properties: enabled: type: boolean example: true url: type: string example: 'https://capterra.com/p/123/slack' score: type: number example: 4.6 reviews_count: type: integer example: 85 trustpilot: type: object properties: score: type: number example: 3.6 reviews_count: type: integer example: 11218 categories: type: array example: - id: 1 name: Communication items: type: object properties: id: type: integer example: 1 name: type: string example: Communication segments: type: array example: - id: 1 name: Enterprise items: type: object properties: id: type: integer example: 1 name: type: string example: Enterprise search_fields: type: object properties: built_for: type: array example: - id: 1 name: 'Marketing Teams' items: type: object properties: id: type: integer example: 1 name: type: string example: 'Marketing Teams' platform: type: array example: - id: 2 name: Web items: type: object properties: id: type: integer example: 2 name: type: string example: Web pricing_model: type: array example: - id: 3 name: Subscription items: type: object properties: id: type: integer example: 3 name: type: string example: Subscription competitors: type: array example: - id: 456 name: 'Microsoft Teams' url: 'https://teams.microsoft.com' logo_path: null items: type: object properties: id: type: integer example: 456 name: type: string example: 'Microsoft Teams' url: type: string example: 'https://teams.microsoft.com' logo_path: type: string example: null videos: type: array example: - 'https://www.youtube.com/watch?v=abc' items: type: string awards: type: array example: - id: 1 name: 'Best Communication Tool 2025' items: type: object properties: id: type: integer example: 1 name: type: string example: 'Best Communication Tool 2025' deal: type: string example: 'Get 20% off annual plans' deals_meta_title: type: string example: 'Best Slack Deals' deals_meta_description: type: string example: null cancellation_content: type: string example: '

To cancel your subscription...

' cancellation_content_summary: type: string example: '

Cancel anytime from settings

' book_demo_url: type: string example: 'https://slack.com/demo' pricing_url: type: string example: 'https://slack.com/pricing' pros_cons: type: string example: "Pros: Easy to use\nCons: Can be expensive" analysis: type: string example: '

Detailed analysis of Slack...

' faq: type: string example: "Q: How much does it cost?\nA: Free tier available" alternatives_text: type: string example: '

Consider Microsoft Teams or Discord...

' pricing_range: type: string example: $0-$15/user/mo is_ai_powered: type: boolean example: false meta: type: object properties: main_page: type: object properties: title: type: string example: 'Slack - Where Work Happens' description: type: string example: 'Team messaging platform' deals: type: object properties: title: type: string example: 'Slack Deals' description: type: string example: 'Best Slack discounts' cancellation: type: object properties: title: type: string example: 'Cancel Slack' description: type: string example: 'How to cancel' sync_status: type: string example: synced synced_at: type: string example: '2026-01-06T10:00:00.000000Z' local_changes_at: type: string example: null last_change_request: type: object properties: uuid: type: string example: 880e8400-e29b-41d4-a716-446655440003 status: type: string example: pending requested_at: type: string example: '2026-01-06T11:00:00.000000Z' reviewed_at: type: string example: null created_at: type: string example: '2026-01-06T09:00:00.000000Z' updated_at: type: string example: '2026-01-06T10:00:00.000000Z' 404: description: '' content: application/json: schema: oneOf: - description: '' type: object example: message: 'Project not found.' properties: message: type: string example: 'Project not found.' - description: '' type: object example: message: 'This project does not have a product profile yet.' properties: message: type: string example: 'This project does not have a product profile yet.' tags: - Profiles put: summary: 'Update Product Profile' operationId: updateProductProfile description: "Submit a change request for the product profile. Changes are stored as a pending\nrequest for admin review. Once approved, changes will be applied and synced to Curiosity.\nFields like subtitle, overview, pricing, analysis, and alternatives_text support HTML/richtext." parameters: [] responses: 200: description: '' content: application/json: schema: type: object example: message: 'Change request submitted for review.' data: change_request_uuid: 880e8400-e29b-41d4-a716-446655440003 status: pending properties: message: type: string example: 'Change request submitted for review.' data: type: object properties: change_request_uuid: type: string example: 880e8400-e29b-41d4-a716-446655440003 status: type: string example: pending 404: description: '' content: application/json: schema: type: object example: message: 'This project does not have a product profile yet.' properties: message: type: string example: 'This project does not have a product profile yet.' tags: - Profiles requestBody: required: false content: application/json: schema: type: object properties: subtitle: type: string description: 'The product tagline (HTML supported).' example: '

Where work happens

' overview: type: string description: 'The product description (HTML supported).' example: '

Slack is a messaging platform...

' pricing: type: string description: 'Pricing information (HTML supported).' example: '

Free, Pro $7.25/user

' parent_category_id: type: integer description: 'The parent category ID from Curiosity.' example: 5 review_platforms: type: object description: 'Review platform URLs and data.' example: [] properties: g2: type: object description: '' example: url: 'https://g2.com/products/slack' properties: url: type: string description: 'G2 review page URL.' example: 'https://g2.com/products/slack' enabled: type: boolean description: 'Whether G2 is enabled.' example: true capterra: type: object description: '' example: url: 'https://capterra.com/p/123/slack' properties: url: type: string description: 'Capterra review page URL.' example: 'https://capterra.com/p/123/slack' enabled: type: boolean description: 'Whether Capterra is enabled.' example: true videos: type: array description: 'List of video URLs.' example: - architecto items: type: string categories: type: array description: 'List of category objects with id and name.' example: - [] items: type: object segments: type: array description: 'List of segment objects with id and name.' example: - [] items: type: object search_fields: type: object description: 'Search field categorization by type (built_for, platform, pricing_model arrays).' example: [] properties: { } competitors: type: array description: 'List of competitor products with id, name, url, logo_path.' example: - [] items: type: object deal: type: string description: 'Deal content/description.' example: architecto deals_meta_title: type: string description: 'SEO meta title for deals page.' example: 'Best Slack Deals 2026' deals_meta_description: type: string description: 'SEO meta description for deals page.' example: architecto cancellation_content: type: string description: 'How to cancel subscription content (HTML supported).' example: architecto cancellation_content_summary: type: string description: 'Summary of cancellation info (HTML supported).' example: architecto book_demo_url: type: string description: 'URL to book a demo.' example: 'https://slack.com/demo' pricing_url: type: string description: 'URL to pricing page.' example: 'https://slack.com/pricing' pros_cons: type: string description: 'Product pros and cons.' example: architecto analysis: type: string description: 'Detailed product analysis (HTML supported).' example: architecto faq: type: string description: 'Frequently asked questions.' example: architecto alternatives_text: type: string description: 'Description of alternatives (HTML supported).' example: architecto pricing_range: type: string description: 'Price tier display.' example: $99-$999/mo is_ai_powered: type: boolean description: 'Whether product uses AI technology.' example: true meta: type: object description: 'SEO meta tags for various pages.' example: [] properties: main_page: type: object description: '' example: title: 'Slack - Where Work Happens' properties: title: type: string description: 'Main page meta title.' example: 'Slack - Where Work Happens' description: type: string description: 'Main page meta description.' example: 'Eius et animi quos velit et.' deals: type: object description: '' example: title: architecto properties: title: type: string description: 'Deals page meta title.' example: architecto description: type: string description: 'Deals page meta description.' example: 'Eius et animi quos velit et.' cancellation: type: object description: '' example: title: architecto properties: title: type: string description: 'Cancellation page meta title.' example: architecto description: type: string description: 'Cancellation page meta description.' example: 'Eius et animi quos velit et.' parameters: - in: path name: project_uuid description: 'The project UUID.' example: 660e8400-e29b-41d4-a716-446655440001 required: true schema: type: string '/api/projects/{project_uuid}/profile/logo': post: summary: 'Upload Product Logo' operationId: uploadProductLogo description: "Upload a new logo image for the product profile. The logo is stored locally\nand will be synced to Curiosity when you push changes." parameters: [] responses: 200: description: '' content: application/json: schema: type: object example: message: 'Logo uploaded successfully.' data: local_logo: product-logos/abc123.png sync_status: local_changes local_changes_at: '2026-01-06T11:00:00.000000Z' properties: message: type: string example: 'Logo uploaded successfully.' data: type: object properties: local_logo: type: string example: product-logos/abc123.png sync_status: type: string example: local_changes local_changes_at: type: string example: '2026-01-06T11:00:00.000000Z' 404: description: '' content: application/json: schema: type: object example: message: 'This project does not have a product profile yet.' properties: message: type: string example: 'This project does not have a product profile yet.' 422: description: '' content: application/json: schema: type: object example: message: 'The logo field is required.' errors: logo: - 'The logo field is required.' properties: message: type: string example: 'The logo field is required.' errors: type: object properties: logo: type: array example: - 'The logo field is required.' items: type: string tags: - Profiles requestBody: required: true content: multipart/form-data: schema: type: object properties: logo: type: string format: binary description: 'The logo image file (max 5MB, must be an image).' required: - logo parameters: - in: path name: project_uuid description: 'The project UUID.' example: 660e8400-e29b-41d4-a716-446655440001 required: true schema: type: string '/api/projects/{project_uuid}/profile/status': get: summary: 'Get Sync Status' operationId: getSyncStatus description: "Get the current sync status of a project's product profile." parameters: [] responses: 200: description: '' content: application/json: schema: oneOf: - description: '' type: object example: data: has_profile: true has_curiosity_link: true sync_status: local_changes synced_at: '2026-01-06T10:00:00.000000Z' local_changes_at: '2026-01-06T11:00:00.000000Z' last_change_request: uuid: 880e8400-e29b-41d4-a716-446655440003 status: pending requested_at: '2026-01-06T11:00:00.000000Z' reviewed_at: null properties: data: type: object properties: has_profile: type: boolean example: true has_curiosity_link: type: boolean example: true sync_status: type: string example: local_changes synced_at: type: string example: '2026-01-06T10:00:00.000000Z' local_changes_at: type: string example: '2026-01-06T11:00:00.000000Z' last_change_request: type: object properties: uuid: type: string example: 880e8400-e29b-41d4-a716-446655440003 status: type: string example: pending requested_at: type: string example: '2026-01-06T11:00:00.000000Z' reviewed_at: type: string example: null - description: '' type: object example: data: has_profile: false has_curiosity_link: true sync_status: null synced_at: null local_changes_at: null last_change_request: null properties: data: type: object properties: has_profile: type: boolean example: false has_curiosity_link: type: boolean example: true sync_status: type: string example: null synced_at: type: string example: null local_changes_at: type: string example: null last_change_request: type: string example: null tags: - Profiles parameters: - in: path name: project_uuid description: 'The project UUID.' example: 660e8400-e29b-41d4-a716-446655440001 required: true schema: type: string /api/catalog/parent-categories: get: summary: 'List Parent Categories' operationId: listParentCategories description: "Get all top-level parent categories for product classification.\nParent categories represent broad product domains." parameters: [] responses: 200: description: '' content: application/json: schema: type: object example: data: - id: 1 name: Analytics - id: 2 name: Communication - id: 3 name: Marketing - id: 4 name: 'Project Management' - id: 5 name: Sales properties: data: type: array example: - id: 1 name: Analytics - id: 2 name: Communication - id: 3 name: Marketing - id: 4 name: 'Project Management' - id: 5 name: Sales items: type: object properties: id: type: integer example: 1 name: type: string example: Analytics tags: - Profiles /api/catalog/categories: get: summary: 'List Categories' operationId: listCategories description: "Get all detailed categories for product classification.\nCategories are more specific than parent categories and can be assigned to products." parameters: [] responses: 200: description: '' content: application/json: schema: type: object example: data: - id: 1 name: 'Video Conferencing' - id: 2 name: 'Team Chat' - id: 3 name: 'Email Marketing' - id: 4 name: CRM - id: 5 name: 'Task Management' properties: data: type: array example: - id: 1 name: 'Video Conferencing' - id: 2 name: 'Team Chat' - id: 3 name: 'Email Marketing' - id: 4 name: CRM - id: 5 name: 'Task Management' items: type: object properties: id: type: integer example: 1 name: type: string example: 'Video Conferencing' tags: - Profiles /api/catalog/segments: get: summary: 'List Segments' operationId: listSegments description: "Get all market segments for product targeting.\nSegments define the target audience or market size for products." parameters: [] responses: 200: description: '' content: application/json: schema: type: object example: data: - id: 1 name: Enterprise - id: 2 name: Mid-Market - id: 3 name: SMB - id: 4 name: Startup - id: 5 name: Freelancer properties: data: type: array example: - id: 1 name: Enterprise - id: 2 name: Mid-Market - id: 3 name: SMB - id: 4 name: Startup - id: 5 name: Freelancer items: type: object properties: id: type: integer example: 1 name: type: string example: Enterprise tags: - Profiles /api/catalog/search-fields: get: summary: 'List Search Fields' operationId: listSearchFields description: "Get all search field options grouped by type.\nSearch fields are structured attributes used for filtering and discovery.\n\n**Field Types:**\n- `built_for` - Target user roles or teams (e.g., \"Marketing Teams\", \"Developers\")\n- `platform` - Deployment platforms (e.g., \"Web\", \"iOS\", \"Android\", \"Desktop\")\n- `pricing_model` - Business models (e.g., \"Subscription\", \"One-time\", \"Freemium\")" parameters: [] responses: 200: description: '' content: application/json: schema: type: object example: data: built_for: - id: 1 name: 'Marketing Teams' - id: 2 name: 'Sales Teams' - id: 3 name: Developers - id: 4 name: 'HR Teams' platform: - id: 1 name: Web - id: 2 name: iOS - id: 3 name: Android - id: 4 name: Desktop pricing_model: - id: 1 name: Subscription - id: 2 name: 'One-time Purchase' - id: 3 name: Freemium - id: 4 name: Usage-based properties: data: type: object properties: built_for: type: array example: - id: 1 name: 'Marketing Teams' - id: 2 name: 'Sales Teams' - id: 3 name: Developers - id: 4 name: 'HR Teams' items: type: object properties: id: type: integer example: 1 name: type: string example: 'Marketing Teams' platform: type: array example: - id: 1 name: Web - id: 2 name: iOS - id: 3 name: Android - id: 4 name: Desktop items: type: object properties: id: type: integer example: 1 name: type: string example: Web pricing_model: type: array example: - id: 1 name: Subscription - id: 2 name: 'One-time Purchase' - id: 3 name: Freemium - id: 4 name: Usage-based items: type: object properties: id: type: integer example: 1 name: type: string example: Subscription tags: - Profiles /api/external/claim-profiles: post: summary: 'Create Claim Profile (External)' operationId: createClaimProfileExternal description: "Create a new claim profile from an external website. This endpoint does not require\nuser authentication but requires a valid external API key." parameters: [] responses: 201: description: Success content: application/json: schema: type: object example: message: 'Claim profile submitted successfully.' data: uuid: 880e8400-e29b-41d4-a716-446655440000 product_name: Lovable business_email: john@company.com status: pending properties: message: type: string example: 'Claim profile submitted successfully.' data: type: object properties: uuid: type: string example: 880e8400-e29b-41d4-a716-446655440000 product_name: type: string example: Lovable business_email: type: string example: john@company.com status: type: string example: pending 401: description: Unauthorized content: application/json: schema: type: object example: message: Unauthorized. properties: message: type: string example: Unauthorized. 422: description: 'Validation Error' content: application/json: schema: type: object example: message: 'The business email is required.' errors: business_email: - 'The business email is required.' properties: message: type: string example: 'The business email is required.' errors: type: object properties: business_email: type: array example: - 'The business email is required.' items: type: string tags: - Profiles requestBody: required: true content: application/json: schema: type: object properties: scraper_product_id: type: integer description: 'The ID of the product from Curiosity.' example: 123 product_name: type: string description: 'The name of the product being claimed.' example: Lovable product_url: type: string description: 'The URL of the product.' example: 'https://lovable.dev' nullable: true business_email: type: string description: 'The business email address.' example: john@company.com job_title: type: string description: 'The job title.' example: 'Product Manager' nullable: true business_phone: type: string description: 'The business phone number (optional).' example: '+1234567890' nullable: true required: - scraper_product_id - product_name - business_email security: [] '/api/organisations/{organisation_uuid}/projects': get: summary: 'List Projects' operationId: listProjects description: 'Get all projects in an organisation that the user has access to.' parameters: [] responses: 200: description: '' content: application/json: schema: type: object example: data: - uuid: 660e8400-e29b-41d4-a716-446655440001 product_name: 'Acme App' product_website: 'https://acme.com' review_platforms: g2: enabled: true url: 'https://g2.com/products/acme' reddit_keywords: - acme - 'acme app' created_at: '2025-12-10T10:00:00.000000Z' updated_at: '2025-12-10T10:00:00.000000Z' properties: data: type: array example: - uuid: 660e8400-e29b-41d4-a716-446655440001 product_name: 'Acme App' product_website: 'https://acme.com' review_platforms: g2: enabled: true url: 'https://g2.com/products/acme' reddit_keywords: - acme - 'acme app' created_at: '2025-12-10T10:00:00.000000Z' updated_at: '2025-12-10T10:00:00.000000Z' items: type: object properties: uuid: type: string example: 660e8400-e29b-41d4-a716-446655440001 product_name: type: string example: 'Acme App' product_website: type: string example: 'https://acme.com' review_platforms: type: object properties: g2: type: object properties: enabled: type: boolean example: true url: type: string example: 'https://g2.com/products/acme' reddit_keywords: type: array example: - acme - 'acme app' items: type: string created_at: type: string example: '2025-12-10T10:00:00.000000Z' updated_at: type: string example: '2025-12-10T10:00:00.000000Z' tags: - Projects post: summary: 'Create Project' operationId: createProject description: 'Create a new project in an organisation. Requires organisation admin role.' parameters: [] responses: 201: description: '' content: application/json: schema: type: object example: message: 'Project created successfully.' data: uuid: 660e8400-e29b-41d4-a716-446655440001 product_name: 'Acme App' product_website: 'https://acme.com' product_logo: 'https://cdn.brandfetch.io/acme.com/fallback/lettermark/icon?c=BRANDFETCH_CLIENT_ID' review_platforms: { } reddit_keywords: - acme reddit_brand_name: Acme created_at: '2025-12-10T10:00:00.000000Z' updated_at: '2025-12-10T10:00:00.000000Z' properties: message: type: string example: 'Project created successfully.' data: type: object properties: uuid: type: string example: 660e8400-e29b-41d4-a716-446655440001 product_name: type: string example: 'Acme App' product_website: type: string example: 'https://acme.com' product_logo: type: string example: 'https://cdn.brandfetch.io/acme.com/fallback/lettermark/icon?c=BRANDFETCH_CLIENT_ID' review_platforms: type: object properties: { } reddit_keywords: type: array example: - acme items: type: string reddit_brand_name: type: string example: Acme created_at: type: string example: '2025-12-10T10:00:00.000000Z' updated_at: type: string example: '2025-12-10T10:00:00.000000Z' tags: - Projects requestBody: required: true content: application/json: schema: type: object properties: product_name: type: string description: 'The product name.' example: 'Acme App' product_website: type: string description: 'The product website URL.' example: 'https://acme.com' product_logo: type: string description: 'The product logo URL.' example: 'https://cdn.brandfetch.io/acme.com/fallback/lettermark/icon?c=BRANDFETCH_CLIENT_ID' review_platforms: type: object description: 'Review platform settings.' example: g2: enabled: true url: 'https://g2.com/products/acme' capterra: enabled: false url: null properties: { } reddit_keywords: type: array description: 'Reddit keyword variations.' example: - acme - 'acme app' items: type: string reddit_brand_name: type: string description: 'The brand name for Reddit tracking.' example: Acme social_platform: type: object description: 'Social platform configuration.' example: twitter: enabled: true url: 'https://twitter.com/acme' properties: { } required: - product_name parameters: - in: path name: organisation_uuid description: '' example: b47b1b40-d5f7-4e9c-959a-b7affbc9965c required: true schema: type: string - in: path name: organisation description: 'The organisation UUID.' example: 550e8400-e29b-41d4-a716-446655440000 required: true schema: type: string '/api/organisations/{organisation_uuid}/projects/{uuid}': get: summary: 'Get Project' operationId: getProject description: 'Get details of a specific project.' parameters: [] responses: 200: description: '' content: application/json: schema: type: object example: data: uuid: 660e8400-e29b-41d4-a716-446655440001 product_name: 'Acme App' product_website: 'https://acme.com' review_platforms: g2: enabled: true url: 'https://g2.com/products/acme' reddit_keywords: - acme - 'acme app' created_at: '2025-12-10T10:00:00.000000Z' updated_at: '2025-12-10T10:00:00.000000Z' properties: data: type: object properties: uuid: type: string example: 660e8400-e29b-41d4-a716-446655440001 product_name: type: string example: 'Acme App' product_website: type: string example: 'https://acme.com' review_platforms: type: object properties: g2: type: object properties: enabled: type: boolean example: true url: type: string example: 'https://g2.com/products/acme' reddit_keywords: type: array example: - acme - 'acme app' items: type: string created_at: type: string example: '2025-12-10T10:00:00.000000Z' updated_at: type: string example: '2025-12-10T10:00:00.000000Z' tags: - Projects put: summary: 'Update Project' operationId: updateProject description: "Update a project's details. Requires organisation admin role." parameters: [] responses: 200: description: '' content: application/json: schema: type: object example: message: 'Project updated successfully.' data: uuid: 660e8400-e29b-41d4-a716-446655440001 product_name: 'Acme App Pro' product_website: 'https://acme.com' product_logo: 'https://cdn.brandfetch.io/acme.com/fallback/lettermark/icon?c=BRANDFETCH_CLIENT_ID' review_platforms: { } reddit_keywords: - acme - 'acme pro' reddit_brand_name: Acme created_at: '2025-12-10T10:00:00.000000Z' updated_at: '2025-12-10T10:00:00.000000Z' properties: message: type: string example: 'Project updated successfully.' data: type: object properties: uuid: type: string example: 660e8400-e29b-41d4-a716-446655440001 product_name: type: string example: 'Acme App Pro' product_website: type: string example: 'https://acme.com' product_logo: type: string example: 'https://cdn.brandfetch.io/acme.com/fallback/lettermark/icon?c=BRANDFETCH_CLIENT_ID' review_platforms: type: object properties: { } reddit_keywords: type: array example: - acme - 'acme pro' items: type: string reddit_brand_name: type: string example: Acme created_at: type: string example: '2025-12-10T10:00:00.000000Z' updated_at: type: string example: '2025-12-10T10:00:00.000000Z' tags: - Projects requestBody: required: true content: application/json: schema: type: object properties: product_name: type: string description: 'The product name.' example: 'Acme App Pro' product_website: type: string description: 'The product website URL.' example: 'https://acme.com' product_logo: type: string description: 'The product logo URL.' example: 'https://cdn.brandfetch.io/acme.com/fallback/lettermark/icon?c=BRANDFETCH_CLIENT_ID' review_platforms: type: object description: 'Review platform settings.' example: g2: enabled: true url: 'https://g2.com/products/acme' capterra: enabled: false url: null properties: { } reddit_keywords: type: array description: 'Reddit keyword variations.' example: - acme - 'acme pro' items: type: string reddit_brand_name: type: string description: 'The brand name for Reddit tracking.' example: Acme social_platform: type: object description: 'Social platform configuration.' example: twitter: enabled: true url: 'https://twitter.com/acme' properties: { } required: - product_name delete: summary: 'Delete Project' operationId: deleteProject description: 'Delete a project. Requires organisation admin role.' parameters: [] responses: 200: description: '' content: application/json: schema: type: object example: message: 'Project deleted successfully.' properties: message: type: string example: 'Project deleted successfully.' tags: - Projects parameters: - in: path name: organisation_uuid description: '' example: b47b1b40-d5f7-4e9c-959a-b7affbc9965c required: true schema: type: string - in: path name: uuid description: '' example: d94fca2e-1789-491b-88a9-03b5738dd218 required: true schema: type: string - in: path name: organisation description: 'The organisation UUID.' example: 550e8400-e29b-41d4-a716-446655440000 required: true schema: type: string - in: path name: project description: 'The project UUID.' example: 660e8400-e29b-41d4-a716-446655440001 required: true schema: type: string '/api/organisations/{organisation_uuid}/review-views': get: summary: 'Get All Review Views' operationId: getAllReviewViews description: "List all saved review views for the authenticated user in a specific organisation.\nViews are ordered by sort_order and creation date." parameters: [] responses: 200: description: '' content: application/json: schema: type: object example: data: - uuid: a1b2c3d4-... name: 'Critical Reviews' sort_order: 0 filters: brands: - uuid-1 - uuid-2 platforms: - G2 - Capterra rating_buckets: - 1.0-1.9 - 2.0-2.9 date_range: type: preset value: last_3_months created_at: '2026-01-15T10:30:00.000Z' updated_at: '2026-01-20T14:22:00.000Z' properties: data: type: array example: - uuid: a1b2c3d4-... name: 'Critical Reviews' sort_order: 0 filters: brands: - uuid-1 - uuid-2 platforms: - G2 - Capterra rating_buckets: - 1.0-1.9 - 2.0-2.9 date_range: type: preset value: last_3_months created_at: '2026-01-15T10:30:00.000Z' updated_at: '2026-01-20T14:22:00.000Z' items: type: object properties: uuid: type: string example: a1b2c3d4-... name: type: string example: 'Critical Reviews' sort_order: type: integer example: 0 filters: type: object properties: brands: type: array example: - uuid-1 - uuid-2 items: type: string platforms: type: array example: - G2 - Capterra items: type: string rating_buckets: type: array example: - 1.0-1.9 - 2.0-2.9 items: type: string date_range: type: object properties: type: type: string example: preset value: type: string example: last_3_months created_at: type: string example: '2026-01-15T10:30:00.000Z' updated_at: type: string example: '2026-01-20T14:22:00.000Z' tags: - 'Review Views' post: summary: 'Create Review View' operationId: createReviewView description: 'Create a new saved review view for the authenticated user.' parameters: [] responses: 201: description: '' content: text/plain: schema: type: string example: "{\n \"data\": {\n \"uuid\": \"e5f6g7h8-...\",\n \"name\": \"High Priority Reviews\",\n \"sort_order\": 1,\n \"filters\": {...},\n \"created_at\": \"2026-01-24T10:00:00.000Z\",\n \"updated_at\": \"2026-01-24T10:00:00.000Z\"\n }\n}" tags: - 'Review Views' requestBody: required: true content: application/json: schema: type: object properties: name: type: string description: 'The name of the view. Must be unique per user/organisation.' example: 'High Priority Reviews' filters: type: object description: 'Filter configuration.' example: platforms: - G2 rating_buckets: - 1.0-1.9 properties: brands: type: array description: 'Filter by brands (project UUIDs).' example: - uuid-1 - uuid-2 items: type: string platforms: type: array description: 'Platforms to filter by.' example: - G2 - Capterra items: type: string date_range: type: object description: 'Date range preset.' example: type: preset value: last_3_months properties: type: type: string description: 'This field is required when filters.date_range is present.' example: preset enum: - preset - custom - all_time value: type: string description: 'This field is required when filters.date_range.type is preset.' example: last_3_months enum: - last_14_days - last_3_months - last_12_months - last_3_years - all_time nullable: true date_range_custom: type: object description: 'Custom date range.' example: start: '2026-01-01' end: '2026-01-24' properties: start: type: string description: 'This field is required when filters.date_range_custom is present. Must be a valid date.' example: '2026-02-03T13:58:49' end: type: string description: 'This field is required when filters.date_range_custom is present. Must be a valid date. Must be a date after or equal to filters.date_range_custom.start.' example: '2052-02-27' nullable: true rating_buckets: type: array description: 'Rating buckets.' example: - '5.0' - 4.0-4.9 items: type: string languages: type: array description: 'Language filters (ISO 639-1 codes).' example: - en - de - es items: type: string read_status: type: string description: 'Read status filter.' example: unread nullable: true search: type: string description: 'Keyword search.' example: 'customer support' nullable: true sort_by: type: string description: '' example: platform enum: - review_date - rating - platform - created_at nullable: true sort_direction: type: string description: '' example: asc enum: - asc - desc nullable: true required: - name - filters parameters: - in: path name: organisation_uuid description: 'The organisation UUID.' example: 660e8400-e29b-41d4-a716-446655440001 required: true schema: type: string '/api/organisations/{organisation_uuid}/review-views/{view_uuid}': get: summary: 'Get Review View' operationId: getReviewView description: 'Get a specific saved review view.' parameters: [] responses: 200: description: '' content: text/plain: schema: type: string example: "{\n \"data\": {\n \"uuid\": \"a1b2c3d4-...\",\n \"name\": \"Critical Reviews\",\n \"sort_order\": 0,\n \"filters\": {...},\n \"created_at\": \"2026-01-15T10:30:00.000Z\",\n \"updated_at\": \"2026-01-20T14:22:00.000Z\"\n }\n}" tags: - 'Review Views' put: summary: 'Update Review View' operationId: updateReviewView description: 'Update an existing saved review view. Can update name and/or filters.' parameters: [] responses: 200: description: '' content: text/plain: schema: type: string example: "{\n \"data\": {\n \"uuid\": \"a1b2c3d4-...\",\n \"name\": \"Critical Reviews - Updated\",\n \"sort_order\": 0,\n \"filters\": {...},\n \"created_at\": \"2026-01-15T10:30:00.000Z\",\n \"updated_at\": \"2026-01-24T11:15:00.000Z\"\n }\n}" tags: - 'Review Views' requestBody: required: false content: application/json: schema: type: object properties: name: type: string description: 'The name of the view.' example: 'Critical Reviews - Updated' filters: type: object description: 'Filter configuration.' example: platforms: - G2 properties: brands: type: array description: 'Must be a valid UUID. The uuid of an existing record in the projects table. The uuid of an existing record in the projects table.' example: - a4855dc5-0acb-33c3-b921-f4291f719ca0 items: type: string platforms: type: array description: '' example: - 'Chrome Web Store' items: type: string enum: - g2 - capterra - software_advice - trustpilot - omr_reviews - clutch - sourceforge - product_hunt - hubspot_directory - goodfirms - chrome_web_store - google_workspace_marketplace - play_store - app_store - subscribed_fyi - google_reviews - G2 - Capterra - 'Software Advice' - Trustpilot - 'OMR Reviews' - Clutch - SourceForge - 'Product Hunt' - 'HubSpot Directory' - GoodFirms - 'Chrome Web Store' - 'Google Workspace Marketplace' - 'Play Store' - 'App Store' - Subscribed.FYI - 'Google Reviews' date_range: type: object description: '' example: null properties: type: type: string description: 'This field is required when filters.date_range is present.' example: all_time enum: - preset - custom - all_time value: type: string description: 'This field is required when filters.date_range.type is preset.' example: last_3_months enum: - last_14_days - last_3_months - last_12_months - last_3_years - all_time nullable: true date_range_custom: type: object description: '' example: null properties: start: type: string description: 'This field is required when filters.date_range_custom is present. Must be a valid date.' example: '2026-02-03T13:58:49' end: type: string description: 'This field is required when filters.date_range_custom is present. Must be a valid date. Must be a date after or equal to filters.date_range_custom.start.' example: '2052-02-27' nullable: true rating_buckets: type: array description: '' example: - '5.0' items: type: string enum: - '5.0' - 4.0-4.9 - 3.0-3.9 - 2.0-2.9 - 1.0-1.9 languages: type: array description: '' example: - architecto items: type: string read_status: type: string description: '' example: all enum: - all - read - unread nullable: true search: type: string description: 'Must not be greater than 500 characters.' example: 'n' nullable: true sort_by: type: string description: '' example: created_at enum: - review_date - rating - platform - created_at nullable: true sort_direction: type: string description: '' example: asc enum: - asc - desc nullable: true delete: summary: 'Delete Review View' operationId: deleteReviewView description: 'Delete a saved review view.' parameters: [] responses: 204: description: '' content: application/json: schema: type: object example: { } properties: { } tags: - 'Review Views' parameters: - in: path name: organisation_uuid description: 'The organisation UUID.' example: 660e8400-e29b-41d4-a716-446655440001 required: true schema: type: string - in: path name: view_uuid description: 'The view UUID.' example: a1b2c3d4-e5f6-g7h8-i9j0-k1l2m3n4o5p6 required: true schema: type: string /api/integrations/slack/callback: get: summary: 'Slack OAuth Callback' operationId: slackOAuthCallback description: "Handles the OAuth callback from Slack after user authorization.\nExchanges the authorization code for an access token.\nThis endpoint is unauthenticated - uses cached state for auth context." parameters: - in: query name: code description: 'The authorization code from Slack.' example: 123456789.abcdef required: false schema: type: string description: 'The authorization code from Slack.' example: 123456789.abcdef - in: query name: state description: 'The state parameter for CSRF protection.' example: abc123... required: false schema: type: string description: 'The state parameter for CSRF protection.' example: abc123... - in: query name: error description: 'OAuth error if user denied access.' example: access_denied required: false schema: type: string description: 'OAuth error if user denied access.' example: access_denied responses: 302: description: '' content: text/plain: schema: oneOf: - description: Success type: string example: 'Redirects to /dashboard/notification/overview?slack_connected=true&workspace={name}' - description: Error type: string example: 'Redirects to /dashboard/notification/overview?slack_error={error_code}' - description: '' type: string example: "\n\n \n \n \n\n Redirecting to https://subsig-frontend.vercel.app/dashboard/notification/overview?slack_error=architecto\n \n \n Redirecting to https://subsig-frontend.vercel.app/dashboard/notification/overview?slack_error=architecto.\n \n" tags: - 'Slack Integration' requestBody: required: true content: application/json: schema: type: object properties: code: type: string description: 'This field is required when error is not present.' example: architecto state: type: string description: 'Must be 64 characters.' example: ngzmiyvdljnikhwaykcmyuwpwlvqwrsitcpscqldzsnrwtujwvlxjklqppwqbewt error: type: string description: '' example: architecto required: - state security: [] /api/integrations/slack/connect: get: summary: 'Initiate Slack OAuth' operationId: initiateSlackOAuth description: "Starts the OAuth flow to connect a Slack workspace.\nRedirects to Slack's authorization page." parameters: [] responses: 302: description: 'Redirect to Slack' content: text/plain: schema: type: string example: 'Redirects to Slack OAuth page' 400: description: 'Not configured' content: application/json: schema: type: object example: error: configuration message: 'Slack integration is not configured.' properties: error: type: string example: configuration message: type: string example: 'Slack integration is not configured.' 401: description: '' content: application/json: schema: type: object example: message: Unauthenticated. properties: message: type: string example: Unauthenticated. 403: description: 'No organisation' content: application/json: schema: type: object example: message: 'Organisation context required.' properties: message: type: string example: 'Organisation context required.' tags: - 'Slack Integration' /api/integrations/slack/status: get: summary: 'Get Slack Connection Status' operationId: getSlackConnectionStatus description: 'Returns the current Slack connection status for the organisation.' parameters: [] responses: 200: description: '' content: application/json: schema: oneOf: - description: Connected type: object example: connected: true team_id: T123456789 team_name: 'My Workspace' scopes: 'chat:write,channels:read' connected_at: '2025-01-01T12:00:00Z' properties: connected: type: boolean example: true team_id: type: string example: T123456789 team_name: type: string example: 'My Workspace' scopes: type: string example: 'chat:write,channels:read' connected_at: type: string example: '2025-01-01T12:00:00Z' - description: 'Not connected' type: object example: connected: false properties: connected: type: boolean example: false - description: 'Invalid token' type: object example: connected: false error: token_invalid message: 'Slack connection needs to be re-authorized.' properties: connected: type: boolean example: false error: type: string example: token_invalid message: type: string example: 'Slack connection needs to be re-authorized.' 403: description: 'No organisation' content: application/json: schema: type: object example: message: 'Organisation context required.' properties: message: type: string example: 'Organisation context required.' tags: - 'Slack Integration' /api/integrations/slack/disconnect: post: summary: 'Disconnect Slack' operationId: disconnectSlack description: 'Removes the Slack workspace connection for the organisation.' parameters: [] responses: 200: description: '' content: application/json: schema: oneOf: - description: Success type: object example: success: true message: 'Slack connection removed.' properties: success: type: boolean example: true message: type: string example: 'Slack connection removed.' - description: 'Not connected' type: object example: success: true message: 'No Slack connection found.' properties: success: type: boolean example: true message: type: string example: 'No Slack connection found.' 403: description: 'No organisation' content: application/json: schema: type: object example: message: 'Organisation context required.' properties: message: type: string example: 'Organisation context required.' tags: - 'Slack Integration' /api/integrations/slack/channels: get: summary: 'List Slack Channels' operationId: listSlackChannels description: 'Fetches the list of channels from the connected Slack workspace.' parameters: [] responses: 200: description: Success content: application/json: schema: type: object example: ok: true channels: - id: C123456789 name: general is_member: true - id: C987654321 name: random is_member: false properties: ok: type: boolean example: true channels: type: array example: - id: C123456789 name: general is_member: true - id: C987654321 name: random is_member: false items: type: object properties: id: type: string example: C123456789 name: type: string example: general is_member: type: boolean example: true 401: description: '' content: application/json: schema: oneOf: - description: 'Not connected' type: object example: ok: false error: not_connected message: 'Not connected to Slack.' properties: ok: type: boolean example: false error: type: string example: not_connected message: type: string example: 'Not connected to Slack.' - description: 'Invalid token' type: object example: ok: false error: token_invalid message: 'Slack connection needs to be re-authorized.' properties: ok: type: boolean example: false error: type: string example: token_invalid message: type: string example: 'Slack connection needs to be re-authorized.' 403: description: 'No organisation' content: application/json: schema: type: object example: message: 'Organisation context required.' properties: message: type: string example: 'Organisation context required.' tags: - 'Slack Integration' /api/subscription-plans: get: summary: 'List Subscription Plans' operationId: listSubscriptionPlans description: 'Get all available subscription plans.' parameters: [] responses: 200: description: '' content: application/json: schema: type: object example: data: - id: 1 stripe_price_id: price_1234567890 name: 'Pro Plan' description: 'Professional features' amount: 2999 currency: usd interval: month properties: data: type: array example: - id: 1 stripe_price_id: price_1234567890 name: 'Pro Plan' description: 'Professional features' amount: 2999 currency: usd interval: month items: type: object properties: id: type: integer example: 1 stripe_price_id: type: string example: price_1234567890 name: type: string example: 'Pro Plan' description: type: string example: 'Professional features' amount: type: integer example: 2999 currency: type: string example: usd interval: type: string example: month tags: - Subscriptions /api/subscriptions/checkout: post: summary: 'Create Checkout Session' operationId: createCheckoutSession description: 'Create a Stripe Checkout session for subscribing to a plan.' parameters: [] responses: 200: description: '' content: application/json: schema: type: object example: checkout_url: 'https://checkout.stripe.com/pay/cs_test_...' properties: checkout_url: type: string example: 'https://checkout.stripe.com/pay/cs_test_...' 403: description: 'No organisation access' content: application/json: schema: type: object example: message: 'You do not have access to this organisation.' properties: message: type: string example: 'You do not have access to this organisation.' 404: description: 'Plan not found' content: application/json: schema: type: object example: message: 'Subscription plan not found.' properties: message: type: string example: 'Subscription plan not found.' tags: - Subscriptions requestBody: required: true content: application/json: schema: type: object properties: plan_id: type: integer description: 'The subscription plan ID.' example: 1 success_url: type: string description: 'The URL to redirect to after successful payment.' example: 'https://app.example.com/subscription/success' cancel_url: type: string description: 'The URL to redirect to if payment is canceled.' example: 'https://app.example.com/subscription/cancel' required: - plan_id - success_url - cancel_url /api/subscriptions/current: get: summary: 'Get Current Subscription' operationId: getCurrentSubscription description: 'Get the current subscription for the organisation.' parameters: [] responses: 200: description: '' content: application/json: schema: type: object example: data: id: 1 stripe_subscription_id: sub_1234567890 status: active current_period_start: '2025-12-01T00:00:00.000000Z' current_period_end: '2026-01-01T00:00:00.000000Z' plan: id: 1 stripe_price_id: price_1234567890 description: 'Professional features' name: 'Pro Plan' amount: 2999 currency: usd interval: month features: - 'Feature 1' - 'Feature 2' properties: data: type: object properties: id: type: integer example: 1 stripe_subscription_id: type: string example: sub_1234567890 status: type: string example: active current_period_start: type: string example: '2025-12-01T00:00:00.000000Z' current_period_end: type: string example: '2026-01-01T00:00:00.000000Z' plan: type: object properties: id: type: integer example: 1 stripe_price_id: type: string example: price_1234567890 description: type: string example: 'Professional features' name: type: string example: 'Pro Plan' amount: type: integer example: 2999 currency: type: string example: usd interval: type: string example: month features: type: array example: - 'Feature 1' - 'Feature 2' items: type: string 403: description: 'No organisation access' content: application/json: schema: type: object example: message: 'You do not have access to this organisation.' properties: message: type: string example: 'You do not have access to this organisation.' 404: description: 'No subscription' content: application/json: schema: type: object example: message: 'No subscription found for this organisation.' properties: message: type: string example: 'No subscription found for this organisation.' tags: - Subscriptions