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: Onboarding description: "\nAPIs for user onboarding flow" - name: Organisations description: "\nAPIs for managing organisations" - name: Projects description: "\nAPIs for managing projects within organisations" 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/user: get: summary: 'Get Current User' operationId: getCurrentUser description: "Get the authenticated user's details." parameters: [] responses: 200: description: '' content: text/plain: schema: type: string example: "{\n \"name\": \"John Doe\",\n \"email\": \"john@example.com\",\n \"email_verified_at\": \"2025-12-04T12:00:00.000000Z\",\n \"created_at\": \"2025-12-04T10:00:00.000000Z\",\n}" 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/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 name: 'Acme Inc' 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 properties: message: type: string example: 'Onboarding completed successfully.' data: type: object properties: organisation: type: object properties: uuid: type: string example: 550e8400-e29b-41d4-a716-446655440000 name: type: string example: 'Acme Inc' 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 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: [] 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/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' 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' 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' 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' 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' 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' 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' 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' 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' 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' 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' required: - name parameters: - in: path name: uuid description: '' example: 97d834ee-0d40-4d59-96c4-02f6c0950554 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: 97d834ee-0d40-4d59-96c4-02f6c0950554 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': 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: [] 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 required: - product_name parameters: - in: path name: organisation_uuid description: '' example: 97d834ee-0d40-4d59-96c4-02f6c0950554 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: [] 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 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: 97d834ee-0d40-4d59-96c4-02f6c0950554 required: true schema: type: string - in: path name: uuid description: '' example: 00fcc5d7-fa44-4a32-ac7e-3649acd09c2a 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