MENU navbar-image

Introduction

This documentation aims to provide all the information you need to work with our API.

Mail test server: https://backend-staging.subsig.com:8025 (HTTP only)

Postman collection: https://backend-staging.subsig.com/docs.postman

OpenAPI spec: https://backend-staging.subsig.com/docs.openapi

Test Users (Development Only)

The following test accounts are available for testing purposes:

Email Password Organisation Role
admin@acme.com password Acme Corporation admin
admin@acme.com password Acme Corporation organisation_owner
member@acme.com password Acme Corporation organisation_member
project@acme.com password Acme Corporation project_member (CRM & Analytics only)
alice@techstart.com password TechStart Inc organisation_owner

Note: admin@acme.com is also a member of TechStart Inc for testing multi-organisation switching.

Authenticating requests

To authenticate requests, include an Authorization header with the value "Bearer 1|abc123...".

All authenticated endpoints are marked with a requires authentication badge in the documentation below.

You can retrieve your token by visiting your dashboard and clicking Generate API token.

Registration

Create a new user account to access the application.

Create Account

Register a new user account. After successful registration, the user will be automatically logged in and redirected to the dashboard.

Example request:
const url = new URL(
    "https://backend-staging.subsig.com/register"
);

const headers = {
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "name": "John Doe",
    "email": "john@example.com",
    "password": "SecurePass123!",
    "password_confirmation": "SecurePass123!"
};

fetch(url, {
    method: "POST",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());

Example response (201, Account created. User logged in and redirected.):



 

Example response (422, Validation error.):


{
    "message": "The email has already been taken.",
    "errors": {
        "email": [
            "The email has already been taken."
        ]
    }
}
 

Request      

POST register

Headers

Content-Type        

Example: application/json

Accept        

Example: application/json

Body Parameters

name   string     

Full name of the user. Example: John Doe

email   string     

Valid email address. Must be unique. Example: john@example.com

password   string     

Password (min 8 characters, at least one uppercase letter, one lowercase letter, one number and one special character). Example: SecurePass123!

password_confirmation   string     

Must match password exactly. Example: SecurePass123!

Authentication

APIs for user authentication

Create API Token

Generate an API token for authenticated requests. Requires a verified email address.

Example request:
const url = new URL(
    "https://backend-staging.subsig.com/api/sanctum/token"
);

const headers = {
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "email": "john@example.com",
    "password": "SecurePass123!"
};

fetch(url, {
    method: "POST",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());

Example response (200):


{
    "token": "1|abc123..."
}
 

Example response (201, Email not verified):


{
    "message": "Registration successful. Please check your email to verify your account.",
    "redirect_link": "https://frontend.example.com/register?email=john%40example.com",
    "needsEmailVerification": true
}
 

Example response (422, Invalid credentials):


{
    "message": "The provided credentials are incorrect.",
    "errors": {
        "email": [
            "The provided credentials are incorrect."
        ]
    }
}
 

Request      

POST api/sanctum/token

Headers

Content-Type        

Example: application/json

Accept        

Example: application/json

Body Parameters

email   string     

The user's email address. Example: john@example.com

password   string     

The user's password. Example: SecurePass123!

Verify Email

Verify user's email address using the 4-digit code sent via email. Returns an API token on successful verification. Verification link is sent via email. /verify-email?code=1234&email=john@example.com

Example request:
const url = new URL(
    "https://backend-staging.subsig.com/api/email/verify"
);

const headers = {
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "email": "john@example.com",
    "code": "1234"
};

fetch(url, {
    method: "POST",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());

Example response (200):


{
    "message": "Email verified successfully.",
    "token": "1|abc123..."
}
 

Example response (200, Already verified):


{
    "message": "Email already verified.",
    "token": "1|abc123..."
}
 

Example response (422, Invalid code):


{
    "message": "Invalid verification code.",
    "errors": {
        "code": [
            "Invalid verification code."
        ]
    }
}
 

Example response (422, Expired code):


{
    "message": "Verification code has expired. Please request a new one.",
    "errors": {
        "code": [
            "Verification code has expired. Please request a new one."
        ]
    }
}
 

Request      

POST api/email/verify

Headers

Content-Type        

Example: application/json

Accept        

Example: application/json

Body Parameters

email   string     

The user's email address. Example: john@example.com

code   string     

The 4-digit verification code. Example: 1234

Resend Verification Code

Send a new 4-digit verification code to the user's email. Code expires in 60 minutes.

Example request:
const url = new URL(
    "https://backend-staging.subsig.com/api/email/resend"
);

const headers = {
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "email": "john@example.com"
};

fetch(url, {
    method: "POST",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());

Example response (200):


{
    "message": "Verification code sent."
}
 

Example response (200, Already verified):


{
    "message": "Email already verified."
}
 

Example response (422, User not found):


{
    "message": "No account found with this email.",
    "errors": {
        "email": [
            "No account found with this email."
        ]
    }
}
 

Request      

POST api/email/resend

Headers

Content-Type        

Example: application/json

Accept        

Example: application/json

Body Parameters

email   string     

The user's email address. Example: john@example.com

Authenticate with Google

Verify a Google ID token and log in or register based on intent.

Example request:
const url = new URL(
    "https://backend-staging.subsig.com/api/auth/google"
);

const headers = {
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "token": "eyJhbGciOiJSUzI1NiIs...",
    "intent": "login",
    "appsumo_registration_token": "architecto",
    "device_name": "n"
};

fetch(url, {
    method: "POST",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());

Example response (200):


{
    "token": "1|abc123...",
    "user": {
        "name": "John Doe",
        "email": "john@example.com",
        "avatar": "https://lh3.googleusercontent.com/..."
    }
}
 

Example response (401, Invalid token):


{
    "message": "Invalid Google token."
}
 

Example response (401, Google account conflict):


{
    "message": "This account is linked to a different Google account."
}
 

Example response (404, Account not found):


{
    "code": "ACCOUNT_NOT_FOUND",
    "message": "Account not found.",
    "errors": {
        "email": [
            "No account exists for this Google email."
        ]
    }
}
 

Example response (422, Account already exists):


{
    "code": "ACCOUNT_ALREADY_EXISTS",
    "message": "An account with this email already exists. Please log in.",
    "errors": {
        "email": [
            "An account with this email already exists. Please log in."
        ]
    }
}
 

Example response (422, Validation error):


{
    "message": "The token field is required.",
    "errors": {
        "token": [
            "The token field is required."
        ]
    }
}
 

Request      

POST api/auth/google

Headers

Content-Type        

Example: application/json

Accept        

Example: application/json

Body Parameters

token   string     

The Google ID token from the frontend. Example: eyJhbGciOiJSUzI1NiIs...

intent   string     

The auth flow: login (existing users only) or register (new users only). Example: login

appsumo_registration_token   string  optional    

Example: architecto

device_name   string  optional    

Must not be greater than 255 characters. Example: n

Authenticate with LinkedIn

Verify a LinkedIn access token and log in or register based on intent.

Example request:
const url = new URL(
    "https://backend-staging.subsig.com/api/auth/linkedin"
);

const headers = {
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "token": "AQV...",
    "intent": "login",
    "appsumo_registration_token": "architecto",
    "device_name": "n"
};

fetch(url, {
    method: "POST",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());

Example response (200):


{
    "token": "1|abc123...",
    "user": {
        "name": "John Doe",
        "email": "john@example.com",
        "avatar": "https://media.licdn.com/..."
    }
}
 

Example response (401, Invalid token):


{
    "message": "Invalid LinkedIn token."
}
 

Example response (401, LinkedIn account conflict):


{
    "message": "This account is linked to a different LinkedIn account."
}
 

Example response (404, Account not found):


{
    "code": "ACCOUNT_NOT_FOUND",
    "message": "Account not found.",
    "errors": {
        "email": [
            "No account exists for this LinkedIn email."
        ]
    }
}
 

Example response (422, Account already exists):


{
    "code": "ACCOUNT_ALREADY_EXISTS",
    "message": "An account with this email already exists. Please log in.",
    "errors": {
        "email": [
            "An account with this email already exists. Please log in."
        ]
    }
}
 

Example response (422, Validation error):


{
    "message": "The token field is required.",
    "errors": {
        "token": [
            "The token field is required."
        ]
    }
}
 

Request      

POST api/auth/linkedin

Headers

Content-Type        

Example: application/json

Accept        

Example: application/json

Body Parameters

token   string     

The LinkedIn access token from the frontend. Example: AQV...

intent   string     

The auth flow: login (existing users only) or register (new users only). Example: login

appsumo_registration_token   string  optional    

Example: architecto

device_name   string  optional    

Must not be greater than 255 characters. Example: n

Change Password

requires authentication

Change password for an authenticated API user and return a fresh token.

Example request:
const url = new URL(
    "https://backend-staging.subsig.com/api/auth/change-password"
);

const headers = {
    "Authorization": "Bearer 1|abc123...",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "current_password": "SecurePass123!",
    "password": "NewSecurePass123!"
};

fetch(url, {
    method: "POST",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());

Example response (200):


{
    "message": "Password updated successfully.",
    "token": "1|abc123..."
}
 

Example response (401, Unauthenticated):


{
    "message": "Unauthenticated."
}
 

Example response (422, Current password is incorrect):


{
    "message": "The given data was invalid.",
    "errors": {
        "current_password": [
            "The provided password does not match your current password."
        ]
    }
}
 

Request      

POST api/auth/change-password

Headers

Authorization        

Example: Bearer 1|abc123...

Content-Type        

Example: application/json

Accept        

Example: application/json

Body Parameters

current_password   string     

User's current password. Example: SecurePass123!

password   string     

New password (min 8 characters). Example: NewSecurePass123!

Set Password

requires authentication

Set a password for an authenticated user who signed up via Google or LinkedIn. Plaintext is never required for the previous random placeholder password.

Example request:
const url = new URL(
    "https://backend-staging.subsig.com/api/auth/set-password"
);

const headers = {
    "Authorization": "Bearer 1|abc123...",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "password": "NewSecurePass123!",
    "password_confirmation": "NewSecurePass123!"
};

fetch(url, {
    method: "POST",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());

Example response (200):


{
    "message": "Password set successfully.",
    "token": "1|abc123..."
}
 

Example response (422, Password already set):


{
    "message": "A password is already set. Use change-password instead.",
    "error": {
        "code": "password_already_set"
    }
}
 

Request      

POST api/auth/set-password

Headers

Authorization        

Example: Bearer 1|abc123...

Content-Type        

Example: application/json

Accept        

Example: application/json

Body Parameters

password   string     

New password (min 8 characters). Example: NewSecurePass123!

password_confirmation   string     

Must match password. Example: NewSecurePass123!

Complete Profile

requires authentication

Fill in the profile details collected on the "About you" step right after email-only registration (see CreateNewUser::create()). Password is only required the first time -- once hasUsablePassword() is true, omit it and use change-password instead.

Example request:
const url = new URL(
    "https://backend-staging.subsig.com/api/auth/complete-profile"
);

const headers = {
    "Authorization": "Bearer 1|abc123...",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "name": "Jane Doe",
    "password": "NewSecurePass123!",
    "job_role": "founder_ceo",
    "company_name": "Acme Corporation",
    "phone": "+14155552671",
    "agree_to_terms": true,
    "agree_to_marketing": false,
    "password_confirmation": "NewSecurePass123!"
};

fetch(url, {
    method: "POST",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());

Example response (200):


{
    "message": "Profile completed successfully.",
    "token": "1|abc123...",
    "user": {}
}
 

Example response (422, Password already set):


{
    "message": "A password is already set. Use change-password instead.",
    "error": {
        "code": "password_already_set"
    }
}
 

Request      

POST api/auth/complete-profile

Headers

Authorization        

Example: Bearer 1|abc123...

Content-Type        

Example: application/json

Accept        

Example: application/json

Body Parameters

name   string     

Full name. Example: Jane Doe

password   string  optional    

Required on first call only. Example: NewSecurePass123!

job_role   string     

What best describes the user. Example: founder_ceo

company_name   string     

Example: Acme Corporation

phone   string  optional    

Example: +14155552671

agree_to_terms   boolean     

Must be true. Example: true

agree_to_marketing   boolean  optional    

Example: false

password_confirmation   string  optional    

Required with password. Example: NewSecurePass123!

Register with Invite

Accept an invitation and create a new user account. The email address must match the email address on the invite. After successful registration, the user will be added to the organisation or project and will receive an email verification code.

Note: This endpoint bypasses the business email requirement since the invitation itself validates the user's legitimacy.

Example request:
const url = new URL(
    "https://backend-staging.subsig.com/api/invites/accept"
);

const headers = {
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "token": "eEtgjrcdtubjCu4817MfGiimvC2DQLBgaI7LpY1g5kdDMK5wJlQank7ZJ6PWurmb",
    "name": "John Doe",
    "email": "user@example.com",
    "password": "SecurePass123!",
    "password_confirmation": "SecurePass123!"
};

fetch(url, {
    method: "POST",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());

Example response (201):


{
    "message": "Registration successful. Please check your email to verify your account."
}
 

Example response (404, Token not found):


{
    "message": "Invite not found.",
    "errors": {
        "token": [
            "The invite token is invalid or does not exist."
        ]
    }
}
 

Example response (410, Invite expired):


{
    "message": "Invite has expired.",
    "errors": {
        "token": [
            "This invite has expired. Please request a new invitation."
        ]
    }
}
 

Example response (410, Invite already accepted):


{
    "message": "Invite has already been accepted.",
    "errors": {
        "token": [
            "This invite has already been accepted."
        ]
    }
}
 

Example response (422, Email mismatch):


{
    "message": "The email address does not match the invitation.",
    "errors": {
        "email": [
            "The email address must match the email on the invitation."
        ]
    }
}
 

Example response (422, Validation error):


{
    "message": "The name field is required.",
    "errors": {
        "name": [
            "The name field is required."
        ]
    }
}
 

Example response (422, Email already registered):


{
    "message": "The email has already been taken.",
    "errors": {
        "email": [
            "The email has already been taken."
        ]
    }
}
 

Request      

POST api/invites/accept

Headers

Content-Type        

Example: application/json

Accept        

Example: application/json

Body Parameters

token   string     

The invite token from the invitation link. Example: eEtgjrcdtubjCu4817MfGiimvC2DQLBgaI7LpY1g5kdDMK5wJlQank7ZJ6PWurmb

name   string     

The user's full name. Example: John Doe

email   string     

The user's email address (must match the invite email). Example: user@example.com

password   string     

The user's password (min 8 characters). Example: SecurePass123!

password_confirmation   string     

Password confirmation. Example: SecurePass123!

Get Current User

requires authentication

Get the authenticated user's details including organisation and subscription information.

Example request:
const url = new URL(
    "https://backend-staging.subsig.com/api/user"
);

const headers = {
    "Authorization": "Bearer 1|abc123...",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());

Example response (200):


{
    "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"
    }
}
 

Example response (401, Unauthenticated):


{
    "message": "Unauthenticated."
}
 

Request      

GET api/user

Headers

Authorization        

Example: Bearer 1|abc123...

Content-Type        

Example: application/json

Accept        

Example: application/json

Update Profile

requires authentication

Update the authenticated user's own profile fields. name is kept in sync as "{first_name} {last_name}" for every other place in the app that still reads it.

Example request:
const url = new URL(
    "https://backend-staging.subsig.com/api/user"
);

const headers = {
    "Authorization": "Bearer 1|abc123...",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "first_name": "Jane",
    "last_name": "Doe",
    "phone": "+1 555 123 4567",
    "job_role": "Marketing",
    "company_name": "Acme Inc"
};

fetch(url, {
    method: "PATCH",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());

Request      

PATCH api/user

Headers

Authorization        

Example: Bearer 1|abc123...

Content-Type        

Example: application/json

Accept        

Example: application/json

Body Parameters

first_name   string     

Example: Jane

last_name   string     

Example: Doe

phone   string  optional    

Example: +1 555 123 4567

job_role   string     

Example: Marketing

company_name   string     

Example: Acme Inc

Delete Account

requires authentication

Permanently delete the authenticated user's own account. Any organisation this user is the sole member of is deleted along with it (see UserDeletionService); shared organisations are left intact for the remaining members.

Example request:
const url = new URL(
    "https://backend-staging.subsig.com/api/user"
);

const headers = {
    "Authorization": "Bearer 1|abc123...",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

fetch(url, {
    method: "DELETE",
    headers,
}).then(response => response.json());

Request      

DELETE api/user

Headers

Authorization        

Example: Bearer 1|abc123...

Content-Type        

Example: application/json

Accept        

Example: application/json

Log In

Authenticate with your email and password to start a session. On success, you receive a token for subsequent requests.

Example request:
const url = new URL(
    "https://backend-staging.subsig.com/login"
);

const headers = {
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "email": "john@example.com",
    "password": "SecurePass123!",
    "remember": true
};

fetch(url, {
    method: "POST",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());

Example response (200, Login successful. Session started.):



 

Example response (422, Invalid credentials.):


{
    "message": "These credentials do not match our records.",
    "errors": {
        "email": [
            "These credentials do not match our records."
        ]
    }
}
 

Request      

POST login

Headers

Content-Type        

Example: application/json

Accept        

Example: application/json

Body Parameters

email   string     

Your registered email address. Example: john@example.com

password   string     

Your account password. Example: SecurePass123!

remember   boolean  optional    

Stay logged in for extended period. Example: true

Log Out

requires authentication

End your current session. You will need to log in again to access protected resources.

Example request:
const url = new URL(
    "https://backend-staging.subsig.com/logout"
);

const headers = {
    "Content-Type": "application/json",
    "Accept": "application/json",
};

fetch(url, {
    method: "POST",
    headers,
}).then(response => response.json());

Example response (200, Logged out successfully.):



 

Example response (401, Not logged in.):


{
    "message": "Unauthenticated."
}
 

Request      

POST logout

Headers

Content-Type        

Example: application/json

Accept        

Example: application/json

Password Reset

Recover access to your account if you forgot your password.

Request Password Reset

Send a password reset link to your email. The link expires after 60 minutes. Same response for security even if email not found.

Example request:
const url = new URL(
    "https://backend-staging.subsig.com/forgot-password"
);

const headers = {
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "email": "john@example.com"
};

fetch(url, {
    method: "POST",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());

Example response (200, Reset link sent.):


{
    "status": "We have emailed your password reset link."
}
 

Request      

POST forgot-password

Headers

Content-Type        

Example: application/json

Accept        

Example: application/json

Body Parameters

email   string     

Email address associated with your account. Example: john@example.com

Reset Password

Set a new password using the token from your email. Token is valid for 60 minutes.

Example request:
const url = new URL(
    "https://backend-staging.subsig.com/reset-password"
);

const headers = {
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "token": "a1b2c3d4e5f6g7h8i9j0",
    "email": "john@example.com",
    "password": "NewSecurePass123!",
    "password_confirmation": "NewSecurePass123!"
};

fetch(url, {
    method: "POST",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());

Example response (200, Password reset successful.):


{
    "status": "Your password has been reset."
}
 

Example response (422, Invalid or expired token.):


{
    "message": "This password reset token is invalid.",
    "errors": {
        "email": [
            "This password reset token is invalid."
        ]
    }
}
 

Request      

POST reset-password

Headers

Content-Type        

Example: application/json

Accept        

Example: application/json

Body Parameters

token   string     

Reset token from the email link. Example: a1b2c3d4e5f6g7h8i9j0

email   string     

Your account email address. Example: john@example.com

password   string     

Password (min 8 characters, at least one letter and one number). Example: NewSecurePass123!

password_confirmation   string     

Must match new password exactly. Example: NewSecurePass123!

AI Traffic

The AI Traffics dashboard's GA4-sourced overview -- see GoogleAnalyticsTrafficReportService for which fields are real (GA4 rollups) vs placeholders pending the bot-crawler pipeline.

Traffic Overview

requires authentication

Example request:
const url = new URL(
    "https://backend-staging.subsig.com/api/organisations/6ff8f7f6-1eb3-3525-be4a-3932c805afed/ai-visibility/traffic"
);

const headers = {
    "Authorization": "Bearer {token}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "date_from": "architecto",
    "date_to": "architecto",
    "granularity": "architecto"
};

fetch(url, {
    method: "POST",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());

Request      

POST api/organisations/{organisation_uuid}/ai-visibility/traffic

Headers

Authorization        

Example: Bearer {token}

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

organisation_uuid   string     

The organisation UUID. Example: 6ff8f7f6-1eb3-3525-be4a-3932c805afed

Body Parameters

date_from   string  optional    

Optional start of the window (Y-m-d). Default: 30 days ago. Example: architecto

date_to   string  optional    

Optional end of the window (Y-m-d). Default: today. Example: architecto

granularity   string  optional    

Optional trend bucketing: daily, weekly, or monthly. Default: daily. Example: architecto

Traffic Pages

requires authentication

Example request:
const url = new URL(
    "https://backend-staging.subsig.com/api/organisations/6ff8f7f6-1eb3-3525-be4a-3932c805afed/ai-visibility/traffic/pages"
);

const headers = {
    "Authorization": "Bearer {token}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "date_from": "architecto",
    "date_to": "architecto",
    "providers": [
        "architecto"
    ],
    "sort_by": "architecto",
    "sort_direction": "architecto",
    "per_page": 16,
    "page": 16
};

fetch(url, {
    method: "POST",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());

Request      

POST api/organisations/{organisation_uuid}/ai-visibility/traffic/pages

Headers

Authorization        

Example: Bearer {token}

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

organisation_uuid   string     

The organisation UUID. Example: 6ff8f7f6-1eb3-3525-be4a-3932c805afed

Body Parameters

date_from   string  optional    

Optional start of the window (Y-m-d). Default: 30 days ago. Example: architecto

date_to   string  optional    

Optional end of the window (Y-m-d). Default: today. Example: architecto

providers   string[]  optional    

Optional provider ids to filter to.

sort_by   string  optional    

Optional: botVisits, retrievals, citationRate, topics, events, or sessionStarts. Default: sessionStarts. Example: architecto

sort_direction   string  optional    

Optional: asc or desc. Default: desc. Example: architecto

per_page   integer  optional    

Optional page size. Default: 20. Example: 16

page   integer  optional    

Optional page number. Default: 1. Example: 16

AI Visibility - Competitor

Competitor tab — brand list with visibility/SOV/position metrics and CRUD.

Competitor overview

requires authentication

Returns all tracked brands (own + competitors) with visibility, share-of-voice, and average-position metrics for the requested date window, plus change deltas vs the preceding window of equal length.

Example request:
const url = new URL(
    "https://backend-staging.subsig.com/api/organisations/660e8400-e29b-41d4-a716-446655440001/ai-visibility/competitors"
);

const headers = {
    "Authorization": "Bearer 1|abc123...",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "date_from": "architecto",
    "date_to": "architecto",
    "provider": "architecto",
    "page": 16,
    "per_page": 16,
    "search": "architecto"
};

fetch(url, {
    method: "POST",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());

Request      

POST api/organisations/{organisation_uuid}/ai-visibility/competitors

Headers

Authorization        

Example: Bearer 1|abc123...

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

organisation_uuid   string     

The organisation UUID. Example: 660e8400-e29b-41d4-a716-446655440001

Body Parameters

date_from   string  optional    

Optional start date (Y-m-d). Default: 30 days ago. Example: architecto

date_to   string  optional    

Optional end date (Y-m-d). Example: architecto

provider   string  optional    

Optional filter by provider (openai, perplexity, gemini, google_ai_overviews). Example: architecto

page   integer  optional    

Optional page number. Default: 1. Example: 16

per_page   integer  optional    

Optional items per page. Default: 10. Example: 16

search   string  optional    

Optional name search filter. Example: architecto

Add competitor

requires authentication

Example request:
const url = new URL(
    "https://backend-staging.subsig.com/api/organisations/6ff8f7f6-1eb3-3525-be4a-3932c805afed/ai-visibility/competitor-brands"
);

const headers = {
    "Authorization": "Bearer 1|abc123...",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "name": "Acme Corp",
    "website": "acme.com"
};

fetch(url, {
    method: "POST",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());

Request      

POST api/organisations/{organisation_uuid}/ai-visibility/competitor-brands

Headers

Authorization        

Example: Bearer 1|abc123...

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

organisation_uuid   string     

Example: 6ff8f7f6-1eb3-3525-be4a-3932c805afed

Body Parameters

name   string     

Competitor name. Example: Acme Corp

website   string     

Competitor domain. Example: acme.com

Update competitor

requires authentication

Example request:
const url = new URL(
    "https://backend-staging.subsig.com/api/organisations/6ff8f7f6-1eb3-3525-be4a-3932c805afed/ai-visibility/competitor-brands/16"
);

const headers = {
    "Authorization": "Bearer 1|abc123...",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "name": "architecto",
    "website": "architecto"
};

fetch(url, {
    method: "PATCH",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());

Request      

PATCH api/organisations/{organisation_uuid}/ai-visibility/competitor-brands/{brand}

Headers

Authorization        

Example: Bearer 1|abc123...

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

organisation_uuid   string     

Example: 6ff8f7f6-1eb3-3525-be4a-3932c805afed

brand   integer     

The brand ID. Example: 16

Body Parameters

name   string     

Example: architecto

website   string     

Example: architecto

Delete competitor

requires authentication

Example request:
const url = new URL(
    "https://backend-staging.subsig.com/api/organisations/6ff8f7f6-1eb3-3525-be4a-3932c805afed/ai-visibility/competitor-brands/16"
);

const headers = {
    "Authorization": "Bearer 1|abc123...",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

fetch(url, {
    method: "DELETE",
    headers,
}).then(response => response.json());

Request      

DELETE api/organisations/{organisation_uuid}/ai-visibility/competitor-brands/{brand}

Headers

Authorization        

Example: Bearer 1|abc123...

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

organisation_uuid   string     

Example: 6ff8f7f6-1eb3-3525-be4a-3932c805afed

brand   integer     

The brand ID. Example: 16

AI Visibility - Dashboard

Ticket 7 — the Visibility tab. Reads exclusively from ai_visibility_daily_metrics (Ticket 5), never live-joins execution tables — see AiVisibilityMetricsAggregateQuery.

Visibility Tab

requires authentication

Example request:
const url = new URL(
    "https://backend-staging.subsig.com/api/organisations/660e8400-e29b-41d4-a716-446655440001/ai-visibility/visibility"
);

const headers = {
    "Authorization": "Bearer {token}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "date_from": "2026-06-01",
    "date_to": "2026-06-30",
    "compare_from": "architecto",
    "compare_to": "architecto",
    "provider": "openai",
    "model": "architecto",
    "prompt_id": 16,
    "country": "architecto",
    "language": "architecto",
    "source": "architecto"
};

fetch(url, {
    method: "POST",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());

Request      

POST api/organisations/{organisation_uuid}/ai-visibility/visibility

Headers

Authorization        

Example: Bearer {token}

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

organisation_uuid   string     

The organisation UUID. Example: 660e8400-e29b-41d4-a716-446655440001

Body Parameters

date_from   string  optional    

Optional start of the current window (Y-m-d). Default: 30 days ago. Example: 2026-06-01

date_to   string  optional    

Optional end of the current window (Y-m-d). Example: 2026-06-30

compare_from   string  optional    

Optional start of the comparison window. Default: the preceding window of equal length. Example: architecto

compare_to   string  optional    

Optional end of the comparison window. Example: architecto

provider   string  optional    

Optional - filter by provider. Example: openai

model   string  optional    

Optional - filter by model. Example: architecto

prompt_id   integer  optional    

Optional - filter by prompt. Example: 16

country   string  optional    

Optional - filter by country. Example: architecto

language   string  optional    

Optional - filter by language. Example: architecto

source   string  optional    

Optional - active (default), brand_radar, or all. Example: architecto

Platforms Tab

requires authentication

Example request:
const url = new URL(
    "https://backend-staging.subsig.com/api/organisations/660e8400-e29b-41d4-a716-446655440001/ai-visibility/platforms"
);

const headers = {
    "Authorization": "Bearer {token}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "date_from": "architecto",
    "date_to": "architecto"
};

fetch(url, {
    method: "POST",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());

Request      

POST api/organisations/{organisation_uuid}/ai-visibility/platforms

Headers

Authorization        

Example: Bearer {token}

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

organisation_uuid   string     

The organisation UUID. Example: 660e8400-e29b-41d4-a716-446655440001

Body Parameters

date_from   string  optional    

Optional start of the current window (Y-m-d). Example: architecto

date_to   string  optional    

Optional end of the current window (Y-m-d). Example: architecto

Sentiment Tab

requires authentication

Example request:
const url = new URL(
    "https://backend-staging.subsig.com/api/organisations/660e8400-e29b-41d4-a716-446655440001/ai-visibility/sentiment"
);

const headers = {
    "Authorization": "Bearer {token}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "brand": 16,
    "per_page": 16
};

fetch(url, {
    method: "POST",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());

Request      

POST api/organisations/{organisation_uuid}/ai-visibility/sentiment

Headers

Authorization        

Example: Bearer {token}

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

organisation_uuid   string     

The organisation UUID. Example: 660e8400-e29b-41d4-a716-446655440001

Body Parameters

brand   integer  optional    

Optional - brand_id to scope to. Default: the organisation's own brand. Example: 16

per_page   integer  optional    

Optional - sentiment_sources page size. Default: 20. Example: 16

Export Sentiment Sources

requires authentication

Example request:
const url = new URL(
    "https://backend-staging.subsig.com/api/organisations/660e8400-e29b-41d4-a716-446655440001/ai-visibility/sentiment/sources/export"
);

const headers = {
    "Authorization": "Bearer {token}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

fetch(url, {
    method: "POST",
    headers,
}).then(response => response.json());

Request      

POST api/organisations/{organisation_uuid}/ai-visibility/sentiment/sources/export

Headers

Authorization        

Example: Bearer {token}

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

organisation_uuid   string     

The organisation UUID. Example: 660e8400-e29b-41d4-a716-446655440001

Citations Tab

requires authentication

Example request:
const url = new URL(
    "https://backend-staging.subsig.com/api/organisations/660e8400-e29b-41d4-a716-446655440001/ai-visibility/citations"
);

const headers = {
    "Authorization": "Bearer {token}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

fetch(url, {
    method: "POST",
    headers,
}).then(response => response.json());

Request      

POST api/organisations/{organisation_uuid}/ai-visibility/citations

Headers

Authorization        

Example: Bearer {token}

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

organisation_uuid   string     

The organisation UUID. Example: 660e8400-e29b-41d4-a716-446655440001

Citations — Domains

requires authentication

Example request:
const url = new URL(
    "https://backend-staging.subsig.com/api/organisations/660e8400-e29b-41d4-a716-446655440001/ai-visibility/citations/domains"
);

const headers = {
    "Authorization": "Bearer {token}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "per_page": 16
};

fetch(url, {
    method: "POST",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());

Request      

POST api/organisations/{organisation_uuid}/ai-visibility/citations/domains

Headers

Authorization        

Example: Bearer {token}

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

organisation_uuid   string     

The organisation UUID. Example: 660e8400-e29b-41d4-a716-446655440001

Body Parameters

per_page   integer  optional    

Optional - results per page. Default: 20. Example: 16

Citations — URLs

requires authentication

Example request:
const url = new URL(
    "https://backend-staging.subsig.com/api/organisations/660e8400-e29b-41d4-a716-446655440001/ai-visibility/citations/urls"
);

const headers = {
    "Authorization": "Bearer {token}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "per_page": 16
};

fetch(url, {
    method: "POST",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());

Request      

POST api/organisations/{organisation_uuid}/ai-visibility/citations/urls

Headers

Authorization        

Example: Bearer {token}

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

organisation_uuid   string     

The organisation UUID. Example: 660e8400-e29b-41d4-a716-446655440001

Body Parameters

per_page   integer  optional    

Optional - results per page. Default: 20. Example: 16

Export Citation URLs

requires authentication

Flat, one-row-per-citation-occurrence export matching the format competitor tools like Otterly.ai use -- not the on-screen URL rollup, which aggregates many occurrences of the same URL into one summary row and can't carry per-occurrence fields like Prompt/Date.

Example request:
const url = new URL(
    "https://backend-staging.subsig.com/api/organisations/660e8400-e29b-41d4-a716-446655440001/ai-visibility/citations/urls/export"
);

const headers = {
    "Authorization": "Bearer {token}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "domain": "architecto"
};

fetch(url, {
    method: "POST",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());

Request      

POST api/organisations/{organisation_uuid}/ai-visibility/citations/urls/export

Headers

Authorization        

Example: Bearer {token}

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

organisation_uuid   string     

The organisation UUID. Example: 660e8400-e29b-41d4-a716-446655440001

Body Parameters

domain   string  optional    

Optional - filter to a specific domain. Example: architecto

Export Citation Domains

requires authentication

Flat, one-row-per-citation-occurrence export matching the format competitor tools like Otterly.ai use -- not the on-screen domain rollup, which aggregates many occurrences of the same domain into one summary row and can't carry per-occurrence fields like Prompt/Date.

Example request:
const url = new URL(
    "https://backend-staging.subsig.com/api/organisations/660e8400-e29b-41d4-a716-446655440001/ai-visibility/citations/domains/export"
);

const headers = {
    "Authorization": "Bearer {token}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

fetch(url, {
    method: "POST",
    headers,
}).then(response => response.json());

Request      

POST api/organisations/{organisation_uuid}/ai-visibility/citations/domains/export

Headers

Authorization        

Example: Bearer {token}

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

organisation_uuid   string     

The organisation UUID. Example: 660e8400-e29b-41d4-a716-446655440001

Citations — Domain Detail

requires authentication

Example request:
const url = new URL(
    "https://backend-staging.subsig.com/api/organisations/6ff8f7f6-1eb3-3525-be4a-3932c805afed/ai-visibility/citations/domain-detail"
);

const headers = {
    "Authorization": "Bearer {token}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "domain": "reddit.com"
};

fetch(url, {
    method: "POST",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());

Request      

POST api/organisations/{organisation_uuid}/ai-visibility/citations/domain-detail

Headers

Authorization        

Example: Bearer {token}

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

organisation_uuid   string     

The organisation UUID. Example: 6ff8f7f6-1eb3-3525-be4a-3932c805afed

Body Parameters

domain   string     

The domain to retrieve detail for. Example: reddit.com

Citations — URLs for a Domain (paginated)

requires authentication

Example request:
const url = new URL(
    "https://backend-staging.subsig.com/api/organisations/6ff8f7f6-1eb3-3525-be4a-3932c805afed/ai-visibility/citations/domain-urls"
);

const headers = {
    "Authorization": "Bearer {token}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "domain": "architecto"
};

fetch(url, {
    method: "POST",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());

Request      

POST api/organisations/{organisation_uuid}/ai-visibility/citations/domain-urls

Headers

Authorization        

Example: Bearer {token}

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

organisation_uuid   string     

The organisation UUID. Example: 6ff8f7f6-1eb3-3525-be4a-3932c805afed

Body Parameters

domain   string     

The domain to list URLs for. Example: architecto

Citations — URL Detail

requires authentication

Example request:
const url = new URL(
    "https://backend-staging.subsig.com/api/organisations/6ff8f7f6-1eb3-3525-be4a-3932c805afed/ai-visibility/citations/url-detail"
);

const headers = {
    "Authorization": "Bearer {token}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "url": "http:\/\/www.bailey.biz\/quos-velit-et-fugiat-sunt-nihil-accusantium-harum.html"
};

fetch(url, {
    method: "POST",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());

Request      

POST api/organisations/{organisation_uuid}/ai-visibility/citations/url-detail

Headers

Authorization        

Example: Bearer {token}

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

organisation_uuid   string     

The organisation UUID. Example: 6ff8f7f6-1eb3-3525-be4a-3932c805afed

Body Parameters

url   string     

The URL to retrieve detail for. Example: http://www.bailey.biz/quos-velit-et-fugiat-sunt-nihil-accusantium-harum.html

Citations — AI Responses that cited a URL (paginated)

requires authentication

Example request:
const url = new URL(
    "https://backend-staging.subsig.com/api/organisations/6ff8f7f6-1eb3-3525-be4a-3932c805afed/ai-visibility/citations/url-responses"
);

const headers = {
    "Authorization": "Bearer {token}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "url": "http:\/\/www.bailey.biz\/quos-velit-et-fugiat-sunt-nihil-accusantium-harum.html"
};

fetch(url, {
    method: "POST",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());

Request      

POST api/organisations/{organisation_uuid}/ai-visibility/citations/url-responses

Headers

Authorization        

Example: Bearer {token}

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

organisation_uuid   string     

The organisation UUID. Example: 6ff8f7f6-1eb3-3525-be4a-3932c805afed

Body Parameters

url   string     

The URL whose citing executions to list. Example: http://www.bailey.biz/quos-velit-et-fugiat-sunt-nihil-accusantium-harum.html

Export AI Responses that cited a URL

requires authentication

One row per citation occurrence (a citation URL from a specific prompt/service/date), matching the flat format competitor tools like Otterly.ai export -- not one row per response, since a single response can cite several URLs.

Example request:
const url = new URL(
    "https://backend-staging.subsig.com/api/organisations/6ff8f7f6-1eb3-3525-be4a-3932c805afed/ai-visibility/citations/url-responses/export"
);

const headers = {
    "Authorization": "Bearer {token}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "url": "http:\/\/www.bailey.biz\/quos-velit-et-fugiat-sunt-nihil-accusantium-harum.html"
};

fetch(url, {
    method: "POST",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());

Request      

POST api/organisations/{organisation_uuid}/ai-visibility/citations/url-responses/export

Headers

Authorization        

Example: Bearer {token}

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

organisation_uuid   string     

The organisation UUID. Example: 6ff8f7f6-1eb3-3525-be4a-3932c805afed

Body Parameters

url   string     

The URL to export responses for. Example: http://www.bailey.biz/quos-velit-et-fugiat-sunt-nihil-accusantium-harum.html

AI Visibility - Prompt Detail

Ticket 7 — the prompt-detail scrollable page (overview/citations/responses) and the top-level execution detail view. Kept separate from AiVisibilityPromptController (already ~1700 lines, and owns a different, older payload shape via show()/executionShow()) — these are new endpoints with their own contract, built on the same Ticket 5 aggregate-table services as the rest of Ticket 7 rather than that controller's live-join helpers.

Prompt Overview

requires authentication

Example request:
const url = new URL(
    "https://backend-staging.subsig.com/api/organisations/660e8400-e29b-41d4-a716-446655440001/ai-visibility/prompts/1/overview"
);

const headers = {
    "Authorization": "Bearer {token}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());

Example response (401):

Show headers
cache-control: no-cache, private
content-type: application/json
vary: Origin
 

{
    "message": "Unauthenticated."
}
 

Request      

GET api/organisations/{organisation_uuid}/ai-visibility/prompts/{prompt}/overview

Headers

Authorization        

Example: Bearer {token}

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

organisation_uuid   string     

The organisation UUID. Example: 660e8400-e29b-41d4-a716-446655440001

prompt   integer     

The prompt ID. Example: 1

Prompt Filter Options

requires authentication

Distinct providers, models, countries and languages that have actually run for this prompt. Used to populate the filter dropdowns on the prompt detail page.

Example request:
const url = new URL(
    "https://backend-staging.subsig.com/api/organisations/660e8400-e29b-41d4-a716-446655440001/ai-visibility/prompts/1/filters"
);

const headers = {
    "Authorization": "Bearer {token}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());

Example response (401):

Show headers
cache-control: no-cache, private
content-type: application/json
vary: Origin
 

{
    "message": "Unauthenticated."
}
 

Request      

GET api/organisations/{organisation_uuid}/ai-visibility/prompts/{prompt}/filters

Headers

Authorization        

Example: Bearer {token}

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

organisation_uuid   string     

The organisation UUID. Example: 660e8400-e29b-41d4-a716-446655440001

prompt   integer     

The prompt ID. Example: 1

Prompt Citations

requires authentication

Same shape as POST /ai-visibility/citations, scoped to this prompt.

Example request:
const url = new URL(
    "https://backend-staging.subsig.com/api/organisations/660e8400-e29b-41d4-a716-446655440001/ai-visibility/prompts/1/citations"
);

const headers = {
    "Authorization": "Bearer {token}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

fetch(url, {
    method: "POST",
    headers,
}).then(response => response.json());

Request      

POST api/organisations/{organisation_uuid}/ai-visibility/prompts/{prompt}/citations

Headers

Authorization        

Example: Bearer {token}

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

organisation_uuid   string     

The organisation UUID. Example: 660e8400-e29b-41d4-a716-446655440001

prompt   integer     

The prompt ID. Example: 1

Prompt Citation Domains (paginated)

requires authentication

Same shape as POST /ai-visibility/citations/domains, scoped to this prompt.

Example request:
const url = new URL(
    "https://backend-staging.subsig.com/api/organisations/660e8400-e29b-41d4-a716-446655440001/ai-visibility/prompts/1/citations/domains"
);

const headers = {
    "Authorization": "Bearer {token}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "per_page": 16,
    "page": 16
};

fetch(url, {
    method: "POST",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());

Request      

POST api/organisations/{organisation_uuid}/ai-visibility/prompts/{prompt}/citations/domains

Headers

Authorization        

Example: Bearer {token}

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

organisation_uuid   string     

The organisation UUID. Example: 660e8400-e29b-41d4-a716-446655440001

prompt   integer     

The prompt ID. Example: 1

Body Parameters

per_page   integer  optional    

Optional - results per page. Default: 20. Example: 16

page   integer  optional    

Optional - page number. Default: 1. Example: 16

Prompt Citation URLs (paginated)

requires authentication

Same shape as POST /ai-visibility/citations/urls, scoped to this prompt.

Example request:
const url = new URL(
    "https://backend-staging.subsig.com/api/organisations/660e8400-e29b-41d4-a716-446655440001/ai-visibility/prompts/1/citations/urls"
);

const headers = {
    "Authorization": "Bearer {token}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "per_page": 16,
    "page": 16
};

fetch(url, {
    method: "POST",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());

Request      

POST api/organisations/{organisation_uuid}/ai-visibility/prompts/{prompt}/citations/urls

Headers

Authorization        

Example: Bearer {token}

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

organisation_uuid   string     

The organisation UUID. Example: 660e8400-e29b-41d4-a716-446655440001

prompt   integer     

The prompt ID. Example: 1

Body Parameters

per_page   integer  optional    

Optional - results per page. Default: 20. Example: 16

page   integer  optional    

Optional - page number. Default: 1. Example: 16

Export Prompt Citation URLs

requires authentication

Example request:
const url = new URL(
    "https://backend-staging.subsig.com/api/organisations/660e8400-e29b-41d4-a716-446655440001/ai-visibility/prompts/1/citations/export"
);

const headers = {
    "Authorization": "Bearer {token}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

fetch(url, {
    method: "POST",
    headers,
}).then(response => response.json());

Request      

POST api/organisations/{organisation_uuid}/ai-visibility/prompts/{prompt}/citations/export

Headers

Authorization        

Example: Bearer {token}

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

organisation_uuid   string     

The organisation UUID. Example: 660e8400-e29b-41d4-a716-446655440001

prompt   integer     

The prompt ID. Example: 1

GET api/organisations/{organisation_uuid}/ai-visibility/prompts/{prompt}/responses

requires authentication

Example request:
const url = new URL(
    "https://backend-staging.subsig.com/api/organisations/205ae76c-bd73-426b-a36a-8338353b9542/ai-visibility/prompts/architecto/responses"
);

const headers = {
    "Authorization": "Bearer 1|abc123...",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());

Example response (401):

Show headers
cache-control: no-cache, private
content-type: application/json
vary: Origin
 

{
    "message": "Unauthenticated."
}
 

Request      

GET api/organisations/{organisation_uuid}/ai-visibility/prompts/{prompt}/responses

Headers

Authorization        

Example: Bearer 1|abc123...

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

organisation_uuid   string     

Example: 205ae76c-bd73-426b-a36a-8338353b9542

prompt   string     

The prompt. Example: architecto

Prompt Response Detail

requires authentication

Full detail for a single execution — backs the "AI Response" modal on the prompt responses tab.

Example request:
const url = new URL(
    "https://backend-staging.subsig.com/api/organisations/660e8400-e29b-41d4-a716-446655440001/ai-visibility/prompts/1/responses/42"
);

const headers = {
    "Authorization": "Bearer {token}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());

Example response (200):


{
    "data": {
        "execution_id": 42,
        "prompt_title": "How does Notion help teams?",
        "provider": "openai",
        "model": "gpt-4o",
        "date": "2026-06-25 09:00:00",
        "detected_brands": [
            {
                "name": "Notion",
                "website": "notion.com"
            }
        ],
        "sentiment": "positive",
        "is_brand_mentioned": true,
        "response_content": "Notion helps teams...",
        "citations": [
            {
                "rank": 1,
                "url": "https://notion.so/blog",
                "title": "Notion Blog",
                "domain": "notion.so",
                "page_type": "references"
            }
        ]
    }
}
 

Request      

GET api/organisations/{organisation_uuid}/ai-visibility/prompts/{prompt}/responses/{execution}

Headers

Authorization        

Example: Bearer {token}

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

organisation_uuid   string     

The organisation UUID. Example: 660e8400-e29b-41d4-a716-446655440001

prompt   integer     

The prompt ID. Example: 1

execution   integer     

The execution ID. Example: 42

Full detail for a single radar (Google AI Overview) response.

requires authentication

Example request:
const url = new URL(
    "https://backend-staging.subsig.com/api/organisations/6ff8f7f6-1eb3-3525-be4a-3932c805afed/ai-visibility/prompts/16/radar-responses/16"
);

const headers = {
    "Authorization": "Bearer 1|abc123...",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());

Example response (401):

Show headers
cache-control: no-cache, private
content-type: application/json
vary: Origin
 

{
    "message": "Unauthenticated."
}
 

Request      

GET api/organisations/{organisation_uuid}/ai-visibility/prompts/{prompt}/radar-responses/{radarResponse}

Headers

Authorization        

Example: Bearer 1|abc123...

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

organisation_uuid   string     

Example: 6ff8f7f6-1eb3-3525-be4a-3932c805afed

prompt   integer     

Example: 16

radarResponse   integer     

The radar_response id. Example: 16

Export Prompt Responses

requires authentication

Example request:
const url = new URL(
    "https://backend-staging.subsig.com/api/organisations/660e8400-e29b-41d4-a716-446655440001/ai-visibility/prompts/1/responses/export"
);

const headers = {
    "Authorization": "Bearer {token}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

fetch(url, {
    method: "POST",
    headers,
}).then(response => response.json());

Request      

POST api/organisations/{organisation_uuid}/ai-visibility/prompts/{prompt}/responses/export

Headers

Authorization        

Example: Bearer {token}

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

organisation_uuid   string     

The organisation UUID. Example: 660e8400-e29b-41d4-a716-446655440001

prompt   integer     

The prompt ID. Example: 1

Execution Detail

requires authentication

Not scoped to a prompt and exempt from the dashboard cache — reads live, per Ticket 7's spec. Full detail (including inline citation segments) is available regardless of whether raw_response has since been pruned by retention (Ticket 8) — citation_segments is derived once at normalization time (see NormalizeAiVisibilityExecutionJob) and persisted alongside generated_answer, neither of which the prune command ever touches.

Example request:
const url = new URL(
    "https://backend-staging.subsig.com/api/organisations/660e8400-e29b-41d4-a716-446655440001/ai-visibility/executions/501"
);

const headers = {
    "Authorization": "Bearer {token}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());

Example response (401):

Show headers
cache-control: no-cache, private
content-type: application/json
vary: Origin
 

{
    "message": "Unauthenticated."
}
 

Request      

GET api/organisations/{organisation_uuid}/ai-visibility/executions/{execution}

Headers

Authorization        

Example: Bearer {token}

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

organisation_uuid   string     

The organisation UUID. Example: 660e8400-e29b-41d4-a716-446655440001

execution   integer     

The execution ID. Example: 501

AI Visibility - Prompts

APIs for managing AI Visibility prompts, their scheduled executions, and execution detail.

List Prompts

requires authentication

Example request:
const url = new URL(
    "https://backend-staging.subsig.com/api/organisations/660e8400-e29b-41d4-a716-446655440001/ai-visibility/prompts"
);

const params = {
    "topic_id": "16",
    "status": "active",
    "search": "pricing",
    "per_page": "20",
};
Object.keys(params)
    .forEach(key => url.searchParams.append(key, params[key]));

const headers = {
    "Authorization": "Bearer {token}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());

Example response (401):

Show headers
cache-control: no-cache, private
content-type: application/json
vary: Origin
 

{
    "message": "Unauthenticated."
}
 

Request      

GET api/organisations/{organisation_uuid}/ai-visibility/prompts

Headers

Authorization        

Example: Bearer {token}

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

organisation_uuid   string     

The organisation UUID. Example: 660e8400-e29b-41d4-a716-446655440001

Query Parameters

topic_id   integer  optional    

Optional - filter by topic. Example: 16

status   string  optional    

Optional - filter by status (active, paused, archived). Example: active

search   string  optional    

Optional - keyword search on prompt text. Example: pricing

per_page   integer  optional    

Optional - results per page. Default: 20. Example: 20

Create Prompt

requires authentication

Example request:
const url = new URL(
    "https://backend-staging.subsig.com/api/organisations/660e8400-e29b-41d4-a716-446655440001/ai-visibility/prompts"
);

const headers = {
    "Authorization": "Bearer {token}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "text": "How does our product compare to competitors?",
    "topic_id": 16,
    "country": "NG",
    "language": "en",
    "frequency": "weekly",
    "providers": [
        "openai",
        "perplexity"
    ],
    "skip_first_run": true
};

fetch(url, {
    method: "POST",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());

Example response (201):


{
    "data": {
        "id": 1,
        "text": "How does our product compare to competitors?",
        "topic_id": 16,
        "country": "NG",
        "language": "en",
        "frequency": "weekly",
        "status": "active",
        "providers": [
            "openai",
            "perplexity"
        ]
    }
}
 

Example response (403):


{
    "message": "AI Visibility is not enabled for this organisation."
}
 

Example response (422):


{
    "message": "Active prompt limit reached for this plan."
}
 

Request      

POST api/organisations/{organisation_uuid}/ai-visibility/prompts

Headers

Authorization        

Example: Bearer {token}

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

organisation_uuid   string     

The organisation UUID. Example: 660e8400-e29b-41d4-a716-446655440001

Body Parameters

text   string     

Prompt text. Max 700 characters. Example: How does our product compare to competitors?

topic_id   integer     

Topic ID. Every prompt must belong to a topic. Example: 16

country   string  optional    

Optional 2-letter country code. Default: US. Example: NG

language   string  optional    

Optional language code, max 10 chars. Default: en. Example: en

frequency   string     

One of daily, weekly, monthly. Example: weekly

providers   string[]     

Provider names, e.g. openai, perplexity.

skip_first_run   boolean  optional    

Example: true

Bulk Import Prompts

requires authentication

Accepts a flat list of prompt texts assigned to a single topic, split against the plan active-prompt limit: prompts that fit are created as active and dispatched on the priority queue; any overflow is created as paused. Case-insensitive exact duplicates are skipped.

Example request:
const url = new URL(
    "https://backend-staging.subsig.com/api/organisations/6ff8f7f6-1eb3-3525-be4a-3932c805afed/ai-visibility/prompts/bulk-import"
);

const headers = {
    "Authorization": "Bearer {token}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "topic_id": 16,
    "prompts": [
        "architecto"
    ],
    "activate": false
};

fetch(url, {
    method: "POST",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());

Request      

POST api/organisations/{organisation_uuid}/ai-visibility/prompts/bulk-import

Headers

Authorization        

Example: Bearer {token}

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

organisation_uuid   string     

The organisation UUID. Example: 6ff8f7f6-1eb3-3525-be4a-3932c805afed

Body Parameters

topic_id   integer     

Topic ID to assign all imported prompts to. Example: 16

prompts   string[]     

Array of prompt text strings. Max 500 items, each max 700 characters.

activate   boolean  optional    

Onboarding's CSV import uses activate=false to persist the overflow beyond the user's chosen top-3 as paused, without running them or eating into the plan's active-prompt limit. Defaults to true to preserve the existing dashboard behavior. Example: false

Get Organisation-Level Execution Status

requires authentication

Polling endpoint for the frontend to show a banner ("results are being recalculated") while any AI Visibility execution across the organisation is still in progress. Bounded by updated_at (not scheduled_at -- recurring prompts get scheduled_at pinned to midnight, so that column can't tell "just went pending/running" apart from "queued 10 hours ago") to a recent window rather than all-time, since any legitimate in-flight execution resolves (completes, fails, or gets reaped by ai-visibility:reap-stale-executions) well within it. Also counts radar (Google AI Overview) rows still in 'pending' status.

in_progress_count is the number of distinct prompts still in flight, not the number of rows -- a single prompt fans out to one execution row per provider (openai, perplexity, ...) plus its own radar row, so counting rows would overstate how many prompts are actually still running.

Always queried fresh (no caching layer, no-store response header) — every poll reflects the current DB state exactly.

Example request:
const url = new URL(
    "https://backend-staging.subsig.com/api/organisations/660e8400-e29b-41d4-a716-446655440001/ai-visibility/prompts/execution-status"
);

const headers = {
    "Authorization": "Bearer {token}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());

Example response (200):


{
    "data": {
        "in_progress": true,
        "in_progress_count": 2
    }
}
 

Request      

GET api/organisations/{organisation_uuid}/ai-visibility/prompts/execution-status

Headers

Authorization        

Example: Bearer {token}

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

organisation_uuid   string     

The organisation UUID. Example: 660e8400-e29b-41d4-a716-446655440001

Regions In Use

requires authentication

Distinct region/country codes actually configured on this organisation's own prompts -- NOT the full curated config('ai_visibility.supported_regions') list that OrganisationController::supportedRegions() returns (that one backs region pickers on creation forms, where every supported region is a valid choice). This backs the AI Visibility dashboard's region filter dropdowns (Platforms/Visibility/Sentiment/ Citations), which should only ever offer a region that at least one of this org's prompts could actually produce data for -- offering the full global list there let an org filter by a region with zero prompts and see an always-empty result with no indication why.

Scoped to active/paused prompts only, same default scope as index() -- an archived prompt's region isn't a live filtering concern.

Example request:
const url = new URL(
    "https://backend-staging.subsig.com/api/organisations/660e8400-e29b-41d4-a716-446655440001/ai-visibility/prompts/regions"
);

const headers = {
    "Authorization": "Bearer {token}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());

Example response (200):


{
    "data": [
        {
            "value": "US",
            "label": "United States"
        },
        {
            "value": "GB",
            "label": "United Kingdom"
        }
    ]
}
 

Request      

GET api/organisations/{organisation_uuid}/ai-visibility/prompts/regions

Headers

Authorization        

Example: Bearer {token}

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

organisation_uuid   string     

The organisation UUID. Example: 660e8400-e29b-41d4-a716-446655440001

Get Prompt

requires authentication

Example request:
const url = new URL(
    "https://backend-staging.subsig.com/api/organisations/660e8400-e29b-41d4-a716-446655440001/ai-visibility/prompts/1"
);

const headers = {
    "Authorization": "Bearer {token}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());

Example response (401):

Show headers
cache-control: no-cache, private
content-type: application/json
vary: Origin
 

{
    "message": "Unauthenticated."
}
 

Example response (404):


{
    "message": "Prompt not found."
}
 

Request      

GET api/organisations/{organisation_uuid}/ai-visibility/prompts/{prompt}

Headers

Authorization        

Example: Bearer {token}

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

organisation_uuid   string     

The organisation UUID. Example: 660e8400-e29b-41d4-a716-446655440001

prompt   integer     

The prompt ID. Example: 1

Update Prompt

requires authentication

Example request:
const url = new URL(
    "https://backend-staging.subsig.com/api/organisations/660e8400-e29b-41d4-a716-446655440001/ai-visibility/prompts/1"
);

const headers = {
    "Authorization": "Bearer {token}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "text": "How does our product compare to competitors?",
    "topic_id": 16,
    "country": "NG",
    "language": "en",
    "frequency": "weekly",
    "status": "active",
    "providers": [
        "openai",
        "perplexity"
    ]
};

fetch(url, {
    method: "PATCH",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());

Example response (404):


{
    "message": "Prompt not found."
}
 

Example response (422):


{
    "message": "This frequency is not allowed on the current plan."
}
 

Request      

PATCH api/organisations/{organisation_uuid}/ai-visibility/prompts/{prompt}

Headers

Authorization        

Example: Bearer {token}

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

organisation_uuid   string     

The organisation UUID. Example: 660e8400-e29b-41d4-a716-446655440001

prompt   integer     

The prompt ID. Example: 1

Body Parameters

text   string  optional    

Optional prompt text. Max 700 characters. Example: How does our product compare to competitors?

topic_id   integer  optional    

Optional topic ID. If provided, cannot be null - prompts must always belong to a topic. Example: 16

country   string  optional    

Optional 2-letter country code. Example: NG

language   string  optional    

Optional language code, max 10 chars. Example: en

frequency   string  optional    

Optional - one of daily, weekly, monthly. Example: weekly

status   string  optional    

Optional - one of active, paused, archived. Example: active

providers   string[]  optional    

Optional provider names, e.g. openai, perplexity.

Duplicate Prompt

requires authentication

Creates a paused copy of the prompt with the same topic, text, country, language, frequency and providers.

Example request:
const url = new URL(
    "https://backend-staging.subsig.com/api/organisations/660e8400-e29b-41d4-a716-446655440001/ai-visibility/prompts/1/duplicate"
);

const headers = {
    "Authorization": "Bearer {token}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

fetch(url, {
    method: "POST",
    headers,
}).then(response => response.json());

Example response (404):


{
    "message": "Prompt not found."
}
 

Example response (422):


{
    "message": "Active prompt limit reached for this plan."
}
 

Request      

POST api/organisations/{organisation_uuid}/ai-visibility/prompts/{prompt}/duplicate

Headers

Authorization        

Example: Bearer {token}

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

organisation_uuid   string     

The organisation UUID. Example: 660e8400-e29b-41d4-a716-446655440001

prompt   integer     

The prompt ID to duplicate. Example: 1

Delete Prompt

requires authentication

Example request:
const url = new URL(
    "https://backend-staging.subsig.com/api/organisations/660e8400-e29b-41d4-a716-446655440001/ai-visibility/prompts/1"
);

const headers = {
    "Authorization": "Bearer {token}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

fetch(url, {
    method: "DELETE",
    headers,
}).then(response => response.json());

Example response (204):

Empty response
 

Example response (404):


{
    "message": "Prompt not found."
}
 

Request      

DELETE api/organisations/{organisation_uuid}/ai-visibility/prompts/{prompt}

Headers

Authorization        

Example: Bearer {token}

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

organisation_uuid   string     

The organisation UUID. Example: 660e8400-e29b-41d4-a716-446655440001

prompt   integer     

The prompt ID. Example: 1

Run Prompt Now

requires authentication

Manually dispatches a fresh execution for this prompt across all of its allowed providers, independent of the scheduler.

Example request:
const url = new URL(
    "https://backend-staging.subsig.com/api/organisations/660e8400-e29b-41d4-a716-446655440001/ai-visibility/prompts/1/run"
);

const headers = {
    "Authorization": "Bearer {token}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

fetch(url, {
    method: "POST",
    headers,
}).then(response => response.json());

Example response (202):


{
    "data": {
        "prompt_id": 1,
        "execution_ids": [
            501,
            502
        ]
    }
}
 

Example response (404):


{
    "message": "Prompt not found."
}
 

Example response (422):


{
    "message": "No allowed and available provider could run this prompt."
}
 

Request      

POST api/organisations/{organisation_uuid}/ai-visibility/prompts/{prompt}/run

Headers

Authorization        

Example: Bearer {token}

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

organisation_uuid   string     

The organisation UUID. Example: 660e8400-e29b-41d4-a716-446655440001

prompt   integer     

The prompt ID. Example: 1

List Prompt Executions

requires authentication

"All Responses" list — one row per execution ("chat") run for this prompt, newest first.

Example request:
const url = new URL(
    "https://backend-staging.subsig.com/api/organisations/660e8400-e29b-41d4-a716-446655440001/ai-visibility/prompts/1/executions"
);

const params = {
    "provider": "openai",
    "status": "completed",
    "per_page": "20",
};
Object.keys(params)
    .forEach(key => url.searchParams.append(key, params[key]));

const headers = {
    "Authorization": "Bearer {token}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());

Example response (401):

Show headers
cache-control: no-cache, private
content-type: application/json
vary: Origin
 

{
    "message": "Unauthenticated."
}
 

Example response (404):


{
    "message": "Prompt not found."
}
 

Request      

GET api/organisations/{organisation_uuid}/ai-visibility/prompts/{prompt}/executions

Headers

Authorization        

Example: Bearer {token}

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

organisation_uuid   string     

The organisation UUID. Example: 660e8400-e29b-41d4-a716-446655440001

prompt   integer     

The prompt ID. Example: 1

Query Parameters

provider   string  optional    

Optional - filter by provider. Example: openai

status   string  optional    

Optional - filter by execution status. Example: completed

per_page   integer  optional    

Optional - results per page. Default: 20. Example: 20

Get Prompt Execution Detail

requires authentication

"Edit Responses" chat detail — full generated answer, citations, and per-brand mentions for a single execution.

Example request:
const url = new URL(
    "https://backend-staging.subsig.com/api/organisations/660e8400-e29b-41d4-a716-446655440001/ai-visibility/prompts/1/executions/501"
);

const headers = {
    "Authorization": "Bearer {token}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());

Example response (401):

Show headers
cache-control: no-cache, private
content-type: application/json
vary: Origin
 

{
    "message": "Unauthenticated."
}
 

Example response (404):


{
    "message": "Execution not found."
}
 

Request      

GET api/organisations/{organisation_uuid}/ai-visibility/prompts/{prompt}/executions/{execution}

Headers

Authorization        

Example: Bearer {token}

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

organisation_uuid   string     

The organisation UUID. Example: 660e8400-e29b-41d4-a716-446655440001

prompt   integer     

The prompt ID. Example: 1

execution   integer     

The execution ID. Example: 501

Get Prompt Execution Status

requires authentication

Polling endpoint for the frontend to show a loading skeleton while a prompt's latest run is still in progress. "In progress" covers both an execution that hasn't finished calling its provider yet, and one that has (status=completed) but whose citations/brand-mentions haven't been written yet by the normalization job that runs right after — status alone flips to completed before that data exists, so this checks processing_version too rather than reporting "ready" before there's anything real to show. Also folds in the org's latest radar (Google AI Overview) pull for this prompt, if radar is enabled on its plan — radar rows live in a separate table (ai_visibility_radar_responses) with their own pending/normalised/failed lifecycle, but ai_visibility_radar_responses.prompt_id is a real FK to this same prompt, so it belongs in this prompt's status too.

Always queried fresh (no caching layer, no-store response header) — every poll reflects the current DB state exactly.

Example request:
const url = new URL(
    "https://backend-staging.subsig.com/api/organisations/660e8400-e29b-41d4-a716-446655440001/ai-visibility/prompts/1/execution-status"
);

const headers = {
    "Authorization": "Bearer {token}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());

Example response (200):


{
    "data": {
        "status": "in_progress",
        "scheduled_at": "2026-08-14 12:23:00",
        "providers": [
            {
                "provider": "openai",
                "status": "in_progress"
            },
            {
                "provider": "perplexity",
                "status": "ready"
            },
            {
                "provider": "google_ai_overviews",
                "status": "ready"
            }
        ]
    }
}
 

Example response (404):


{
    "message": "Prompt not found."
}
 

Request      

GET api/organisations/{organisation_uuid}/ai-visibility/prompts/{prompt}/execution-status

Headers

Authorization        

Example: Bearer {token}

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

organisation_uuid   string     

The organisation UUID. Example: 660e8400-e29b-41d4-a716-446655440001

prompt   integer     

The prompt ID. Example: 1

AI Visibility - Suggestions

Prompt and topic suggestions generated by GenerateAiVisibilitySuggestionsJob. Suggestions start as completed and move to accepted or dismissed when the user acts on them.

List prompt suggestions

requires authentication

Returns up to 15 prompt suggestions with status completed for the organisation, ordered by source (radar first), then volume descending, then most recent.

Example request:
const url = new URL(
    "https://backend-staging.subsig.com/api/organisations/660e8400-e29b-41d4-a716-446655440001/ai-visibility/prompts/suggested"
);

const params = {
    "exclude_categories[0]": "branded",
};
Object.keys(params)
    .forEach(key => url.searchParams.append(key, params[key]));

const headers = {
    "Authorization": "Bearer {token}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());

Example response (200):


{
    "data": [
        {
            "id": 1,
            "text": "What is the best tool for notes and docs?",
            "category": "awareness",
            "source": "radar",
            "volume": 12000,
            "status": "completed",
            "generated_at": "2026-06-01 00:00:00"
        }
    ]
}
 

Request      

GET api/organisations/{organisation_uuid}/ai-visibility/prompts/suggested

Headers

Authorization        

Example: Bearer {token}

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

organisation_uuid   string     

The organisation UUID. Example: 660e8400-e29b-41d4-a716-446655440001

Query Parameters

exclude_categories   string[]  optional    

Optional categories to exclude.

Refetch suggestions

requires authentication

A separate, standalone generation path from GenerateAiVisibilitySuggestionsJob (the onboarding job) — deliberately not shared code, so this can never change that job's behavior. Two differences from onboarding generation: (1) previously dismissed suggestions are deleted first so a fresh batch can take their place — an upsert alone would otherwise leave a dismissed row's regenerated duplicate stuck as dismissed forever, since the upsert never touches status; completed/accepted suggestions are left untouched. (2) an LLM failure here is a real error, not silently swallowed into the generic fallback set — a user who clicks "refetch" wants real, freshly-generated suggestions, and substituting the generic fallback would look like a genuine refresh when it isn't one.

Rate-limited to once per 10 minutes per org: a successful call holds its lock for the full cooldown (not released in a finally) so a second click can't trigger another LLM call too soon; a failed call releases immediately since it never produced anything worth protecting. A request that lands inside an active cooldown isn't rejected — it gets back the same existing (still-valid) suggestions with cached: true, rather than a bare error.

Example request:
const url = new URL(
    "https://backend-staging.subsig.com/api/organisations/660e8400-e29b-41d4-a716-446655440001/ai-visibility/prompts/suggested/refetch"
);

const headers = {
    "Authorization": "Bearer {token}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

fetch(url, {
    method: "POST",
    headers,
}).then(response => response.json());

Example response (200):


{
    "data": {
        "topics": [
            {
                "id": 1,
                "name": "Feature Discovery",
                "description": "...",
                "source": "llm",
                "status": "completed",
                "generated_at": "2026-06-01 00:00:00"
            }
        ],
        "prompts": [
            {
                "id": 1,
                "text": "What is the best tool for notes and docs?",
                "category": "awareness",
                "source": "llm",
                "status": "completed",
                "generated_at": "2026-06-01 00:00:00"
            }
        ]
    }
}
 

Example response (200):


{
    "data": {
        "topics": [],
        "prompts": []
    },
    "cached": true
}
 

Example response (422):


{
    "message": "No own brand found for this organisation."
}
 

Example response (502):


{
    "message": "Failed to generate suggestions. Please try again."
}
 

Request      

POST api/organisations/{organisation_uuid}/ai-visibility/prompts/suggested/refetch

Headers

Authorization        

Example: Bearer {token}

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

organisation_uuid   string     

The organisation UUID. Example: 660e8400-e29b-41d4-a716-446655440001

Accept a prompt suggestion

requires authentication

Creates a live prompt from the suggestion, dispatches an immediate first run, and marks the suggestion as accepted. Returns the new prompt's ID and settings.

Example request:
const url = new URL(
    "https://backend-staging.subsig.com/api/organisations/660e8400-e29b-41d4-a716-446655440001/ai-visibility/prompts/suggested/1/accept"
);

const headers = {
    "Authorization": "Bearer {token}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "frequency": "daily",
    "providers": [
        "openai"
    ],
    "topic_id": 3,
    "country": "US",
    "language": "en",
    "skip_first_run": true
};

fetch(url, {
    method: "POST",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());

Example response (201):


{
    "data": {
        "prompt_id": 42,
        "text": "What is the best tool for notes and docs?",
        "topic_id": 3,
        "frequency": "daily",
        "providers": [
            "openai"
        ],
        "country": "US",
        "language": "en"
    }
}
 

Example response (422):


{
    "message": "Active prompt limit reached for this plan."
}
 

Request      

POST api/organisations/{organisation_uuid}/ai-visibility/prompts/suggested/{suggestion}/accept

Headers

Authorization        

Example: Bearer {token}

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

organisation_uuid   string     

The organisation UUID. Example: 660e8400-e29b-41d4-a716-446655440001

suggestion   integer     

The suggestion ID. Example: 1

Body Parameters

frequency   string     

Execution frequency. Example: daily

providers   string[]     

AI providers to enable.

topic_id   integer  optional    

Optional topic to attach the prompt to. Example: 3

country   string  optional    

Optional 2-letter country code. Example: US

language   string  optional    

Optional language code. Example: en

skip_first_run   boolean  optional    

Example: true

Dismiss a prompt suggestion

requires authentication

Marks the suggestion as dismissed. It will no longer appear in the suggestions list.

Example request:
const url = new URL(
    "https://backend-staging.subsig.com/api/organisations/660e8400-e29b-41d4-a716-446655440001/ai-visibility/prompts/suggested/1"
);

const headers = {
    "Authorization": "Bearer {token}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

fetch(url, {
    method: "DELETE",
    headers,
}).then(response => response.json());

Example response (204):

Empty response
 

Request      

DELETE api/organisations/{organisation_uuid}/ai-visibility/prompts/suggested/{suggestion}

Headers

Authorization        

Example: Bearer {token}

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

organisation_uuid   string     

The organisation UUID. Example: 660e8400-e29b-41d4-a716-446655440001

suggestion   integer     

The suggestion ID. Example: 1

Onboarding suggestions

requires authentication

Returns both topic and prompt suggestions for the onboarding wizard in a single call. Dispatch GenerateAiVisibilitySuggestionsJob for the org before calling this endpoint to ensure suggestions are populated.

Example request:
const url = new URL(
    "https://backend-staging.subsig.com/api/organisations/660e8400-e29b-41d4-a716-446655440001/ai-visibility/onboarding/suggestions"
);

const headers = {
    "Authorization": "Bearer {token}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());

Example response (200):


{
    "data": {
        "topics": [
            {
                "id": 1,
                "name": "Feature Discovery",
                "description": "...",
                "source": "llm",
                "status": "completed",
                "generated_at": "2026-06-01 00:00:00"
            }
        ],
        "prompts": [
            {
                "id": 1,
                "text": "What is the best tool for notes?",
                "category": "awareness",
                "source": "radar",
                "volume": 12000,
                "status": "completed",
                "generated_at": "2026-06-01 00:00:00"
            }
        ]
    }
}
 

Request      

GET api/organisations/{organisation_uuid}/ai-visibility/onboarding/suggestions

Headers

Authorization        

Example: Bearer {token}

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

organisation_uuid   string     

The organisation UUID. Example: 660e8400-e29b-41d4-a716-446655440001

Onboarding kickoff

requires authentication

Dispatches first-run executions on the priority queue for all active prompts the org accepted during onboarding (those created with skip_first_run=true). Call this from the BrandAnalysisProgress loading screen, after the user has committed to onboarding (step 4), so we don't waste compute on users who abandon after the suggestion step.

Example request:
const url = new URL(
    "https://backend-staging.subsig.com/api/organisations/6ff8f7f6-1eb3-3525-be4a-3932c805afed/ai-visibility/onboarding/kickoff"
);

const headers = {
    "Authorization": "Bearer {token}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

fetch(url, {
    method: "POST",
    headers,
}).then(response => response.json());

Example response (202):


{
    "data": {
        "dispatched": 5
    }
}
 

Request      

POST api/organisations/{organisation_uuid}/ai-visibility/onboarding/kickoff

Headers

Authorization        

Example: Bearer {token}

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

organisation_uuid   string     

The organisation UUID. Example: 6ff8f7f6-1eb3-3525-be4a-3932c805afed

Onboarding analysis status

requires authentication

Returns how many of the org's active prompt executions have completed and a live visibility percentage computed directly from execution_brands rows (no daily_metrics aggregation needed — available as soon as the first execution is normalised).

Poll this from BrandAnalysisProgress until ready is true.

Example request:
const url = new URL(
    "https://backend-staging.subsig.com/api/organisations/6ff8f7f6-1eb3-3525-be4a-3932c805afed/ai-visibility/onboarding/status"
);

const headers = {
    "Authorization": "Bearer {token}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());

Example response (200):


{
    "data": {
        "ready": true,
        "executions_total": 10,
        "executions_completed": 8,
        "visibility_pct": 45
    }
}
 

Request      

GET api/organisations/{organisation_uuid}/ai-visibility/onboarding/status

Headers

Authorization        

Example: Bearer {token}

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

organisation_uuid   string     

The organisation UUID. Example: 6ff8f7f6-1eb3-3525-be4a-3932c805afed

Onboarding improvement estimate

requires authentication

One-shot, synchronous LLM call — no queue, no polling. Call this once from the onboarding screen right when onboardingStatus() first reports has_own_brand_data (i.e. the real, aggregated visibility_pct is already showing), so the estimate stays grounded in the exact same number the dashboard will show after login. Cached for 30 days, so a repeat call (re-entering onboarding, a second tab) returns instantly without re-running the LLM. Returns potential_visibility_pct: null if there isn't enough own-brand data yet, or if the estimate genuinely can't be computed (LLM call failed, no headroom found).

Example request:
const url = new URL(
    "https://backend-staging.subsig.com/api/organisations/6ff8f7f6-1eb3-3525-be4a-3932c805afed/ai-visibility/onboarding/improvement"
);

const headers = {
    "Authorization": "Bearer {token}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

fetch(url, {
    method: "POST",
    headers,
}).then(response => response.json());

Example response (200):


{
    "data": {
        "potential_visibility_pct": 45
    }
}
 

Example response (200):


{
    "data": {
        "potential_visibility_pct": null
    }
}
 

Request      

POST api/organisations/{organisation_uuid}/ai-visibility/onboarding/improvement

Headers

Authorization        

Example: Bearer {token}

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

organisation_uuid   string     

The organisation UUID. Example: 6ff8f7f6-1eb3-3525-be4a-3932c805afed

List suggested competitors

requires authentication

Returns up to 10 pending competitor suggestions for the organisation, most recent first. Populated by SuggestAiVisibilityCompetitorsJob (dispatched at onboarding and daily thereafter via ai-visibility:suggest-competitors).

Example request:
const url = new URL(
    "https://backend-staging.subsig.com/api/organisations/660e8400-e29b-41d4-a716-446655440001/ai-visibility/competitors/suggested"
);

const headers = {
    "Authorization": "Bearer {token}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());

Example response (200):


{
    "data": [
        {
            "id": 1,
            "name": "Acme Corp",
            "website": "acme.com",
            "reason": "Direct competitor in the same market.",
            "generated_at": "2026-07-09 00:00:00"
        }
    ]
}
 

Request      

GET api/organisations/{organisation_uuid}/ai-visibility/competitors/suggested

Headers

Authorization        

Example: Bearer {token}

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

organisation_uuid   string     

The organisation UUID. Example: 660e8400-e29b-41d4-a716-446655440001

Accept a suggested competitor

requires authentication

Creates a real, tracked competitor brand from the suggestion (source=manual, same as one added directly via the Competitor tab), backfills its historical mention data across every completed execution so far, and marks the suggestion accepted.

Example request:
const url = new URL(
    "https://backend-staging.subsig.com/api/organisations/660e8400-e29b-41d4-a716-446655440001/ai-visibility/competitors/suggested/1/accept"
);

const headers = {
    "Authorization": "Bearer {token}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

fetch(url, {
    method: "POST",
    headers,
}).then(response => response.json());

Example response (201):


{
    "data": {
        "brand_id": 42,
        "name": "Acme Corp",
        "website": "acme.com"
    }
}
 

Example response (422):


{
    "message": "This suggestion has already been accepted."
}
 

Request      

POST api/organisations/{organisation_uuid}/ai-visibility/competitors/suggested/{suggestion}/accept

Headers

Authorization        

Example: Bearer {token}

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

organisation_uuid   string     

The organisation UUID. Example: 660e8400-e29b-41d4-a716-446655440001

suggestion   integer     

The competitor suggestion ID. Example: 1

Dismiss a suggested competitor

requires authentication

Marks the competitor suggestion as rejected. It will no longer appear in the suggestions list, and the name will never be suggested again for this organisation.

Example request:
const url = new URL(
    "https://backend-staging.subsig.com/api/organisations/660e8400-e29b-41d4-a716-446655440001/ai-visibility/competitors/suggested/1"
);

const headers = {
    "Authorization": "Bearer {token}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

fetch(url, {
    method: "DELETE",
    headers,
}).then(response => response.json());

Example response (204):

Empty response
 

Request      

DELETE api/organisations/{organisation_uuid}/ai-visibility/competitors/suggested/{suggestion}

Headers

Authorization        

Example: Bearer {token}

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

organisation_uuid   string     

The organisation UUID. Example: 660e8400-e29b-41d4-a716-446655440001

suggestion   integer     

The competitor suggestion ID. Example: 1

List topic suggestions

requires authentication

Returns up to 10 topic suggestions with status completed for the organisation, ordered by most recent first.

Example request:
const url = new URL(
    "https://backend-staging.subsig.com/api/organisations/660e8400-e29b-41d4-a716-446655440001/ai-visibility/topics/suggested"
);

const headers = {
    "Authorization": "Bearer {token}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());

Example response (200):


{
    "data": [
        {
            "id": 1,
            "name": "Feature Discovery",
            "description": "Questions about discovering features in productivity tools",
            "source": "llm",
            "status": "completed",
            "generated_at": "2026-06-01 00:00:00"
        }
    ]
}
 

Request      

GET api/organisations/{organisation_uuid}/ai-visibility/topics/suggested

Headers

Authorization        

Example: Bearer {token}

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

organisation_uuid   string     

The organisation UUID. Example: 660e8400-e29b-41d4-a716-446655440001

Accept a topic suggestion

requires authentication

Creates a real topic from the suggestion and marks it as accepted. Returns the new topic's ID and details.

Example request:
const url = new URL(
    "https://backend-staging.subsig.com/api/organisations/660e8400-e29b-41d4-a716-446655440001/ai-visibility/topics/suggested/1/accept"
);

const headers = {
    "Authorization": "Bearer {token}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

fetch(url, {
    method: "POST",
    headers,
}).then(response => response.json());

Example response (201):


{
    "data": {
        "topic_id": 5,
        "name": "Feature Discovery",
        "description": "Questions about discovering features in productivity tools"
    }
}
 

Example response (422):


{
    "message": "This suggestion has already been accepted."
}
 

Request      

POST api/organisations/{organisation_uuid}/ai-visibility/topics/suggested/{suggestion}/accept

Headers

Authorization        

Example: Bearer {token}

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

organisation_uuid   string     

The organisation UUID. Example: 660e8400-e29b-41d4-a716-446655440001

suggestion   integer     

The topic suggestion ID. Example: 1

Dismiss a topic suggestion

requires authentication

Marks the topic suggestion as dismissed. It will no longer appear in the suggestions list.

Example request:
const url = new URL(
    "https://backend-staging.subsig.com/api/organisations/660e8400-e29b-41d4-a716-446655440001/ai-visibility/topics/suggested/1"
);

const headers = {
    "Authorization": "Bearer {token}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

fetch(url, {
    method: "DELETE",
    headers,
}).then(response => response.json());

Example response (204):

Empty response
 

Request      

DELETE api/organisations/{organisation_uuid}/ai-visibility/topics/suggested/{suggestion}

Headers

Authorization        

Example: Bearer {token}

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

organisation_uuid   string     

The organisation UUID. Example: 660e8400-e29b-41d4-a716-446655440001

suggestion   integer     

The topic suggestion ID. Example: 1

AI Visibility - Topics

APIs for managing topics used to group AI Visibility prompts within an organisation.

List Topics

requires authentication

Example request:
const url = new URL(
    "https://backend-staging.subsig.com/api/organisations/660e8400-e29b-41d4-a716-446655440001/ai-visibility/topics"
);

const headers = {
    "Authorization": "Bearer {token}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());

Example response (200):


{
    "data": [
        {
            "id": 1,
            "name": "Competitor comparisons",
            "description": "Prompts comparing us against competitors",
            "created_at": "2026-06-05T10:00:00.000000Z",
            "updated_at": "2026-06-05T10:00:00.000000Z",
            "active_count": 3,
            "inactive_count": 1,
            "suggested_count": 0
        }
    ]
}
 

Request      

GET api/organisations/{organisation_uuid}/ai-visibility/topics

Headers

Authorization        

Example: Bearer {token}

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

organisation_uuid   string     

The organisation UUID. Example: 660e8400-e29b-41d4-a716-446655440001

Create Topic

requires authentication

Example request:
const url = new URL(
    "https://backend-staging.subsig.com/api/organisations/660e8400-e29b-41d4-a716-446655440001/ai-visibility/topics"
);

const headers = {
    "Authorization": "Bearer {token}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "name": "Competitor comparisons",
    "description": "Prompts comparing us against competitors"
};

fetch(url, {
    method: "POST",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());

Example response (201):


{
    "data": {
        "id": 1,
        "name": "Competitor comparisons",
        "description": "Prompts comparing us against competitors",
        "created_at": "2026-06-05T10:00:00.000000Z",
        "updated_at": "2026-06-05T10:00:00.000000Z"
    }
}
 

Request      

POST api/organisations/{organisation_uuid}/ai-visibility/topics

Headers

Authorization        

Example: Bearer {token}

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

organisation_uuid   string     

The organisation UUID. Example: 660e8400-e29b-41d4-a716-446655440001

Body Parameters

name   string     

Topic name. Example: Competitor comparisons

description   string  optional    

Optional description. Example: Prompts comparing us against competitors

Update Topic

requires authentication

Example request:
const url = new URL(
    "https://backend-staging.subsig.com/api/organisations/660e8400-e29b-41d4-a716-446655440001/ai-visibility/topics/1"
);

const headers = {
    "Authorization": "Bearer {token}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "name": "Competitor comparisons",
    "description": "Prompts comparing us against competitors"
};

fetch(url, {
    method: "PATCH",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());

Example response (200):


{
    "data": {
        "id": 1,
        "name": "Competitor comparisons",
        "description": "Prompts comparing us against competitors",
        "created_at": "2026-06-05T10:00:00.000000Z",
        "updated_at": "2026-06-05T10:00:00.000000Z"
    }
}
 

Example response (404):


{
    "message": "Topic not found."
}
 

Request      

PATCH api/organisations/{organisation_uuid}/ai-visibility/topics/{topic}

Headers

Authorization        

Example: Bearer {token}

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

organisation_uuid   string     

The organisation UUID. Example: 660e8400-e29b-41d4-a716-446655440001

topic   integer     

The topic ID. Example: 1

Body Parameters

name   string  optional    

Optional topic name. Example: Competitor comparisons

description   string  optional    

Optional description. Example: Prompts comparing us against competitors

Delete Topic

requires authentication

Example request:
const url = new URL(
    "https://backend-staging.subsig.com/api/organisations/660e8400-e29b-41d4-a716-446655440001/ai-visibility/topics/1"
);

const headers = {
    "Authorization": "Bearer {token}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

fetch(url, {
    method: "DELETE",
    headers,
}).then(response => response.json());

Example response (204):

Empty response
 

Example response (404):


{
    "message": "Topic not found."
}
 

Example response (422):


{
    "message": "Cannot delete a topic with active prompts assigned to it."
}
 

Request      

DELETE api/organisations/{organisation_uuid}/ai-visibility/topics/{topic}

Headers

Authorization        

Example: Bearer {token}

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

organisation_uuid   string     

The organisation UUID. Example: 660e8400-e29b-41d4-a716-446655440001

topic   integer     

The topic ID. Example: 1

API Keys

APIs for managing production API keys for the current organisation's billing group. A key is always generated from (and owned by) the group's billing-anchor organisation, but each key is independently scoped to whichever subset of the group's organisations (workspaces) was chosen when it was created -- a billing group can hold several keys, each covering a different subset, for different integrations.

List every production API key generated for this billing group.

requires authentication

Returns every non-revoked key generated from the billing-anchor organisation of whichever organisation was selected via X-Organisation-Id, each with the workspaces it's scoped to. Plaintext API keys are never returned from this endpoint.

Example request:
const url = new URL(
    "https://backend-staging.subsig.com/api/api-keys"
);

const headers = {
    "Authorization": "Bearer {token}",
    "X-Organisation-Id": "string required The organisation UUID.",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());

Example response (200):


{
    "data": [
        {
            "uuid": "550e8400-e29b-41d4-a716-446655440000",
            "environment": "live",
            "status": "active",
            "key": null,
            "masked_key": "sk_live_****************************Ab12",
            "last_four": "Ab12",
            "plain_text_available": false,
            "idempotent_replay": false,
            "rotation_expires_at": null,
            "expires_at": null,
            "revoked_at": null,
            "organisations": [
                {
                    "uuid": "660e8400-e29b-41d4-a716-446655440000",
                    "name": "Acme Inc",
                    "is_primary": true
                }
            ],
            "created_at": "2026-06-05T10:00:00.000000Z",
            "updated_at": "2026-06-05T10:00:00.000000Z"
        }
    ]
}
 

Request      

GET api/api-keys

Headers

Authorization        

Example: Bearer {token}

X-Organisation-Id        

Example: string required The organisation UUID.

Content-Type        

Example: application/json

Accept        

Example: application/json

Generate a production API key scoped to a chosen subset of this billing group's organisations. Plaintext is returned only on first creation.

requires authentication

Example request:
const url = new URL(
    "https://backend-staging.subsig.com/api/api-keys/generate"
);

const headers = {
    "Authorization": "Bearer {token}",
    "X-Organisation-Id": "string required The organisation UUID.",
    "Idempotency-Key": "string optional Safe retry key for this generation request.",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "name": "Claude Desktop MCP",
    "confirmed": true,
    "organisation_uuids": [
        "architecto"
    ]
};

fetch(url, {
    method: "POST",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());

Example response (201):


{
    "message": "API key generated successfully.",
    "data": {
        "uuid": "550e8400-e29b-41d4-a716-446655440000",
        "environment": "live",
        "status": "active",
        "key": "sk_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
        "masked_key": "sk_live_****************************Ab12",
        "last_four": "Ab12",
        "plain_text_available": true,
        "idempotent_replay": false,
        "organisations": [
            {
                "uuid": "660e8400-e29b-41d4-a716-446655440000",
                "name": "Acme Inc",
                "is_primary": true
            }
        ],
        "created_at": "2026-06-05T10:00:00.000000Z",
        "updated_at": "2026-06-05T10:00:00.000000Z"
    }
}
 

Example response (422, Workspace outside this billing group):


{
    "message": "One or more selected workspaces are not part of this account's billing group.",
    "error": {
        "code": "organisation_not_in_billing_scope"
    }
}
 

Request      

POST api/api-keys/generate

Headers

Authorization        

Example: Bearer {token}

X-Organisation-Id        

Example: string required The organisation UUID.

Idempotency-Key        

Example: string optional Safe retry key for this generation request.

Content-Type        

Example: application/json

Accept        

Example: application/json

Body Parameters

name   string  optional    

optional A label for this key, e.g. "Claude Desktop MCP". If omitted, a placeholder name derived from the key itself (e.g. "sk_live_eb0da992") is generated automatically. Example: Claude Desktop MCP

confirmed   boolean     

Must be true before generating the key. Example: true

organisation_uuids   string[]     

At least one workspace UUID (from this billing group) this key should be able to access. The billing-anchor organisation is selectable like any other workspace but is never added automatically -- the key only gets access to the workspace(s) explicitly listed here, and at least one is required.

Rotate one production API key. Plaintext is returned only on first rotation. The new key keeps the exact same workspace scope as the key it replaces.

requires authentication

Creates a new active production key immediately and keeps the previous key valid for 24 hours before it is auto-revoked.

Example request:
const url = new URL(
    "https://backend-staging.subsig.com/api/api-keys/bb5acf44-73b3-4cf7-a3d8-9eeed414dc5a/rotate"
);

const headers = {
    "Authorization": "Bearer {token}",
    "X-Organisation-Id": "string required The organisation UUID.",
    "Idempotency-Key": "string optional Safe retry key for this rotation request.",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

fetch(url, {
    method: "POST",
    headers,
}).then(response => response.json());

Example response (201):


{
    "message": "API key rotated successfully.",
    "data": {
        "current_key": {
            "uuid": "660e8400-e29b-41d4-a716-446655440000",
            "environment": "live",
            "status": "active",
            "key": "sk_live_yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy",
            "masked_key": "sk_live_****************************Cd34",
            "last_four": "Cd34",
            "plain_text_available": true,
            "idempotent_replay": false
        },
        "previous_key": {
            "uuid": "550e8400-e29b-41d4-a716-446655440000",
            "environment": "live",
            "status": "rotating",
            "key": null,
            "masked_key": "sk_live_****************************Ab12",
            "last_four": "Ab12",
            "plain_text_available": false,
            "idempotent_replay": false,
            "rotation_expires_at": "2026-06-06T10:00:00.000000Z"
        }
    }
}
 

Example response (409, Idempotency key conflict):


{
    "message": "This idempotency key was already used for a different API key action.",
    "error": {
        "code": "idempotency_key_conflict"
    }
}
 

Request      

POST api/api-keys/{apiKey_uuid}/rotate

Headers

Authorization        

Example: Bearer {token}

X-Organisation-Id        

Example: string required The organisation UUID.

Idempotency-Key        

Example: string optional Safe retry key for this rotation request.

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

apiKey_uuid   string     

Example: bb5acf44-73b3-4cf7-a3d8-9eeed414dc5a

Revoke one production API key immediately (and its still-rotating predecessor, if any).

requires authentication

The request must include the exact confirmation text REVOKE.

Example request:
const url = new URL(
    "https://backend-staging.subsig.com/api/api-keys/bb5acf44-73b3-4cf7-a3d8-9eeed414dc5a/revoke"
);

const headers = {
    "Authorization": "Bearer {token}",
    "X-Organisation-Id": "string required The organisation UUID.",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "confirmation": "REVOKE"
};

fetch(url, {
    method: "POST",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());

Example response (200):


{
    "message": "API key revoked successfully.",
    "data": {
        "revoked_count": 1
    }
}
 

Example response (422, Missing confirmation):


{
    "message": "The selected confirmation is invalid.",
    "errors": {
        "confirmation": [
            "The selected confirmation is invalid."
        ]
    }
}
 

Request      

POST api/api-keys/{apiKey_uuid}/revoke

Headers

Authorization        

Example: Bearer {token}

X-Organisation-Id        

Example: string required The organisation UUID.

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

apiKey_uuid   string     

Example: bb5acf44-73b3-4cf7-a3d8-9eeed414dc5a

Body Parameters

confirmation   string     

Must be exactly REVOKE. Example: REVOKE

Replace one production API key's workspace scope entirely -- adds any newly-listed workspace and drops any workspace no longer listed, in one call. Does not affect the key itself (its value, status, or expiry).

requires authentication

Example request:
const url = new URL(
    "https://backend-staging.subsig.com/api/api-keys/bb5acf44-73b3-4cf7-a3d8-9eeed414dc5a/workspaces"
);

const headers = {
    "Authorization": "Bearer {token}",
    "X-Organisation-Id": "string required The organisation UUID.",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "organisation_uuids": [
        "architecto"
    ]
};

fetch(url, {
    method: "PATCH",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());

Example response (200):


{
    "message": "API key workspaces updated successfully.",
    "data": {
        "uuid": "550e8400-e29b-41d4-a716-446655440000",
        "organisations": [
            {
                "uuid": "660e8400-e29b-41d4-a716-446655440000",
                "name": "Acme Inc",
                "is_primary": true
            },
            {
                "uuid": "770e8400-e29b-41d4-a716-446655440000",
                "name": "Acme Support",
                "is_primary": false
            }
        ]
    }
}
 

Example response (422, Workspace outside this billing group):


{
    "message": "One or more selected workspaces are not part of this account's billing group.",
    "error": {
        "code": "organisation_not_in_billing_scope"
    }
}
 

Request      

PATCH api/api-keys/{apiKey_uuid}/workspaces

Headers

Authorization        

Example: Bearer {token}

X-Organisation-Id        

Example: string required The organisation UUID.

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

apiKey_uuid   string     

Example: bb5acf44-73b3-4cf7-a3d8-9eeed414dc5a

Body Parameters

organisation_uuids   string[]     

At least one workspace UUID (from this billing group) this key should be scoped to. The billing-anchor organisation is selectable like any other workspace but is never added automatically, and the scope can never be emptied out entirely.

AppSumo

APIs for AppSumo lifetime-deal tier comparison and license redemption

Read-only preview so the FE can show "you're redeeming Tier 2 -- here's what you get" before the user commits. No writes.

Example request:
const url = new URL(
    "https://backend-staging.subsig.com/api/appsumo/licenses/validate"
);

const headers = {
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "license_key": "architecto"
};

fetch(url, {
    method: "POST",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());

Request      

POST api/appsumo/licenses/validate

Headers

Content-Type        

Example: application/json

Accept        

Example: application/json

Body Parameters

license_key   string     

Example: architecto

POST api/appsumo/licenses/redeem

Example request:
const url = new URL(
    "https://backend-staging.subsig.com/api/appsumo/licenses/redeem"
);

const headers = {
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "license_key": "architecto"
};

fetch(url, {
    method: "POST",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());

Request      

POST api/appsumo/licenses/redeem

Headers

Content-Type        

Example: application/json

Accept        

Example: application/json

Body Parameters

license_key   string     

Example: architecto

GET api/appsumo/licenses/current

Example request:
const url = new URL(
    "https://backend-staging.subsig.com/api/appsumo/licenses/current"
);

const headers = {
    "Content-Type": "application/json",
    "Accept": "application/json",
};

fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());

Example response (401):

Show headers
cache-control: no-cache, private
content-type: application/json
vary: Origin
 

{
    "message": "Unauthenticated."
}
 

Request      

GET api/appsumo/licenses/current

Headers

Content-Type        

Example: application/json

Accept        

Example: application/json

List AppSumo tiers

Unauthenticated tier comparison listing (name/price/reviews/mentions/credits per tier) for a marketing/comparison page -- unlike GET /subscription-plans, deliberately does not filter on is_active, since every AppSumo tier is seeded with is_active=false (see AppSumoPlanSeeder) precisely so it's excluded from that general, checkout-oriented listing. Ordered tier 1 -> 4 by config('billing.appsumo_tier_price_ids') key order, not by id.

Example request:
const url = new URL(
    "https://backend-staging.subsig.com/api/appsumo/plans"
);

const headers = {
    "Content-Type": "application/json",
    "Accept": "application/json",
};

fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());

Example response (200):


{
    "data": [
        {
            "tier": 1,
            "stripe_price_id": "price_internal_appsumo_tier1",
            "name": "AppSumo Lifetime - Tier 1",
            "description": "AppSumo lifetime deal, Tier 1 - 1,200 reviews, 1,000 mentions, 10 tracked prompts, 240 lifetime AI credits",
            "amount": 4900,
            "currency": "usd",
            "plan_threshold": {}
        }
    ]
}
 

Request      

GET api/appsumo/plans

Headers

Content-Type        

Example: application/json

Accept        

Example: application/json

Endpoints

Send a reset link to the given user.

Example request:
const url = new URL(
    "https://backend-staging.subsig.com/api/auth/forgot-password"
);

const headers = {
    "Content-Type": "application/json",
    "Accept": "application/json",
};

fetch(url, {
    method: "POST",
    headers,
}).then(response => response.json());

Request      

POST api/auth/forgot-password

Headers

Content-Type        

Example: application/json

Accept        

Example: application/json

Reset the user's password.

Example request:
const url = new URL(
    "https://backend-staging.subsig.com/api/auth/reset-password"
);

const headers = {
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "token": "architecto"
};

fetch(url, {
    method: "POST",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());

Request      

POST api/auth/reset-password

Headers

Content-Type        

Example: application/json

Accept        

Example: application/json

Body Parameters

token   string     

Example: architecto

password   string  optional    

Bulk Deactivate Brands (Temporary)

Disabled unless TEMP_BULK_BRAND_DEACTIVATE_ENABLED is set. When TEMP_BULK_BRAND_DEACTIVATE_TOKEN is set, the request must include a matching X-Temporary-Bulk-Token header.

Example request:
const url = new URL(
    "https://backend-staging.subsig.com/api/brands/deactivate-bulk-temporary"
);

const headers = {
    "X-Temporary-Bulk-Token": "{token}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "chunk_size": 1,
    "dry_run": true
};

fetch(url, {
    method: "POST",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());

Request      

POST api/brands/deactivate-bulk-temporary

Headers

X-Temporary-Bulk-Token        

Example: {token}

Content-Type        

Example: application/json

Accept        

Example: application/json

Body Parameters

chunk_size   integer  optional    

Must be at least 50. Must not be greater than 500. Example: 1

dry_run   boolean  optional    

Example: true

Send a test webhook (manual trigger).

Example request:
const url = new URL(
    "https://backend-staging.subsig.com/api/outbound-webhooks/architecto/test"
);

const headers = {
    "Content-Type": "application/json",
    "Accept": "application/json",
};

fetch(url, {
    method: "POST",
    headers,
}).then(response => response.json());

Request      

POST api/outbound-webhooks/{webhook}/test

Headers

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

webhook   string     

Example: architecto

List outbound webhooks for the current organisation.

Example request:
const url = new URL(
    "https://backend-staging.subsig.com/api/outbound-webhooks"
);

const headers = {
    "Content-Type": "application/json",
    "Accept": "application/json",
};

fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());

Example response (401):

Show headers
cache-control: no-cache, private
content-type: application/json
vary: Origin
 

{
    "message": "Unauthenticated."
}
 

Request      

GET api/outbound-webhooks

Headers

Content-Type        

Example: application/json

Accept        

Example: application/json

Create an outbound webhook.

Example request:
const url = new URL(
    "https://backend-staging.subsig.com/api/outbound-webhooks"
);

const headers = {
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "url": "http:\/\/www.bailey.biz\/quos-velit-et-fugiat-sunt-nihil-accusantium-harum.html",
    "headers": [
        "i"
    ],
    "auth_type": "none",
    "auth_token": "k",
    "secret": "h",
    "is_active": true
};

fetch(url, {
    method: "POST",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());

Request      

POST api/outbound-webhooks

Headers

Content-Type        

Example: application/json

Accept        

Example: application/json

Body Parameters

url   string     

Must be a valid URL. Must not be greater than 2048 characters. Example: http://www.bailey.biz/quos-velit-et-fugiat-sunt-nihil-accusantium-harum.html

headers   string[]  optional    

Must not be greater than 1024 characters.

auth_type   string     

Example: none

Must be one of:
  • none
  • bearer
  • custom
auth_token   string  optional    

Must not be greater than 2048 characters. Example: k

secret   string  optional    

Must not be greater than 2048 characters. Example: h

retry_config   object  optional    
is_active   boolean  optional    

Example: true

Show a single outbound webhook.

Example request:
const url = new URL(
    "https://backend-staging.subsig.com/api/outbound-webhooks/architecto"
);

const headers = {
    "Content-Type": "application/json",
    "Accept": "application/json",
};

fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());

Example response (401):

Show headers
cache-control: no-cache, private
content-type: application/json
vary: Origin
 

{
    "message": "Unauthenticated."
}
 

Request      

GET api/outbound-webhooks/{id}

Headers

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

id   string     

The ID of the outbound webhook. Example: architecto

Update an outbound webhook.

Example request:
const url = new URL(
    "https://backend-staging.subsig.com/api/outbound-webhooks/architecto"
);

const headers = {
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "url": "http:\/\/www.bailey.biz\/quos-velit-et-fugiat-sunt-nihil-accusantium-harum.html",
    "headers": [
        "i"
    ],
    "auth_type": "none",
    "auth_token": "k",
    "secret": "h",
    "is_active": false
};

fetch(url, {
    method: "PUT",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());

Request      

PUT api/outbound-webhooks/{id}

PATCH api/outbound-webhooks/{id}

Headers

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

id   string     

The ID of the outbound webhook. Example: architecto

Body Parameters

url   string  optional    

Must be a valid URL. Must not be greater than 2048 characters. Example: http://www.bailey.biz/quos-velit-et-fugiat-sunt-nihil-accusantium-harum.html

headers   string[]  optional    

Must not be greater than 1024 characters.

auth_type   string  optional    

Example: none

Must be one of:
  • none
  • bearer
  • custom
auth_token   string  optional    

Must not be greater than 2048 characters. Example: k

secret   string  optional    

Must not be greater than 2048 characters. Example: h

retry_config   object  optional    
is_active   boolean  optional    

Example: false

Delete an outbound webhook.

Example request:
const url = new URL(
    "https://backend-staging.subsig.com/api/outbound-webhooks/architecto"
);

const headers = {
    "Content-Type": "application/json",
    "Accept": "application/json",
};

fetch(url, {
    method: "DELETE",
    headers,
}).then(response => response.json());

Request      

DELETE api/outbound-webhooks/{id}

Headers

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

id   string     

The ID of the outbound webhook. Example: architecto

Lets the frontend resolve the org's own tracked brand_id on its own -- every agent endpoint requires it (see AgentRegistry::baseInputSchema()), but there's exactly one is_own_brand row per org and nothing today returns just that id without also computing a full competitor/metrics payload (AiVisibilityCompetitorController::index).

Example request:
const url = new URL(
    "https://backend-staging.subsig.com/api/organisations/205ae76c-bd73-426b-a36a-8338353b9542/agents/own-brand"
);

const headers = {
    "Content-Type": "application/json",
    "Accept": "application/json",
};

fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());

Example response (401):

Show headers
cache-control: no-cache, private
content-type: application/json
vary: Origin
 

{
    "message": "Unauthenticated."
}
 

Request      

GET api/organisations/{organisation_uuid}/agents/own-brand

Headers

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

organisation_uuid   string     

Example: 205ae76c-bd73-426b-a36a-8338353b9542

Populates the run form's "Try our AI Recommendations" chips with real, org-specific observations (see AgentSuggestedPromptGenerator) instead of the agent's static suggestedPrompts() copy. Meaningful for root_cause_analysis and competitive_benchmark -- other agents fall back to their registry-declared suggestedPrompts() unchanged since nothing generates dynamic ones for them yet.

Example request:
const url = new URL(
    "https://backend-staging.subsig.com/api/organisations/205ae76c-bd73-426b-a36a-8338353b9542/agents/architecto/suggested-prompts"
);

const headers = {
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "brand_id": 16,
    "timeframe": "90d",
    "data_source": "openai"
};

fetch(url, {
    method: "GET",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());

Example response (401):

Show headers
cache-control: no-cache, private
content-type: application/json
vary: Origin
 

{
    "message": "Unauthenticated."
}
 

Request      

GET api/organisations/{organisation_uuid}/agents/{key}/suggested-prompts

Headers

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

organisation_uuid   string     

Example: 205ae76c-bd73-426b-a36a-8338353b9542

key   string     

Example: architecto

Body Parameters

brand_id   integer     

Example: 16

timeframe   string  optional    

Example: 90d

Must be one of:
  • 7d
  • 30d
  • 90d
data_source   string  optional    

Shared across every agent's suggested-prompts endpoint -- the AI platform values (root_cause_analysis/prompts_discovery/ai_visibility_roadmap) plus the ai_visibility default, since neither generator below is told which agent's data_source shape to expect ahead of time. Example: openai

Must be one of:
  • openai
  • perplexity
  • gemini
  • ai_visibility

POST api/organisations/{organisation_uuid}/agents/{key}/runs

Example request:
const url = new URL(
    "https://backend-staging.subsig.com/api/organisations/205ae76c-bd73-426b-a36a-8338353b9542/agents/architecto/runs"
);

const headers = {
    "Content-Type": "application/json",
    "Accept": "application/json",
};

fetch(url, {
    method: "POST",
    headers,
}).then(response => response.json());

Request      

POST api/organisations/{organisation_uuid}/agents/{key}/runs

Headers

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

organisation_uuid   string     

Example: 205ae76c-bd73-426b-a36a-8338353b9542

key   string     

Example: architecto

Ticket 5's Recent Outputs listing endpoint -- never built alongside show()/download(), so that page has been stuck on hardcoded demo rows. Matches the mockup's columns exactly: Focus Topics, Agents, Status, Date created. Filterable by agent_type/status per Ticket 5's definition of done; paginated the same way AiVisibilityCompetitorController::index() is.

Example request:
const url = new URL(
    "https://backend-staging.subsig.com/api/organisations/205ae76c-bd73-426b-a36a-8338353b9542/agent-runs"
);

const headers = {
    "Content-Type": "application/json",
    "Accept": "application/json",
};

fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());

Example response (401):

Show headers
cache-control: no-cache, private
content-type: application/json
vary: Origin
 

{
    "message": "Unauthenticated."
}
 

Request      

GET api/organisations/{organisation_uuid}/agent-runs

Headers

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

organisation_uuid   string     

Example: 205ae76c-bd73-426b-a36a-8338353b9542

Pulled forward from Ticket 5's full listing/polling scope -- the Root Cause Analysis run page needs to poll run_plan step-by-step progress and read result once completed, and nothing else in Ticket 5 (agent listing, Recent Outputs table) is needed for that yet.

Example request:
const url = new URL(
    "https://backend-staging.subsig.com/api/organisations/205ae76c-bd73-426b-a36a-8338353b9542/agent-runs/6"
);

const headers = {
    "Content-Type": "application/json",
    "Accept": "application/json",
};

fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());

Example response (401):

Show headers
cache-control: no-cache, private
content-type: application/json
vary: Origin
 

{
    "message": "Unauthenticated."
}
 

Request      

GET api/organisations/{organisation_uuid}/agent-runs/{run_id}

Headers

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

organisation_uuid   string     

Example: 205ae76c-bd73-426b-a36a-8338353b9542

run_id   integer     

The ID of the run. Example: 6

Ticket 4/5's own definition of done requires this endpoint, but it was never actually built alongside RenderAgentReportPdfJob -- the PDF existed with nothing to fetch it through. Never returns a permanent URL, only a signed one generated on demand.

Backs both the frontend's Download button (open-and-forget) and Share button (copy the link to hand to someone without a Subsig login) -- one URL serves both, so the expiry has to be long enough for a genuinely shared link to still work hours later, not just a quick click. 24h, matching this feature's other "good enough, not permanent" TTLs (see AgentSuggestedPromptGenerator's suggestion cache).

Example request:
const url = new URL(
    "https://backend-staging.subsig.com/api/organisations/205ae76c-bd73-426b-a36a-8338353b9542/agent-runs/6/download"
);

const params = {
    "format": "docx",
};
Object.keys(params)
    .forEach(key => url.searchParams.append(key, params[key]));

const headers = {
    "Content-Type": "application/json",
    "Accept": "application/json",
};

fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());

Example response (401):

Show headers
cache-control: no-cache, private
content-type: application/json
vary: Origin
 

{
    "message": "Unauthenticated."
}
 

Request      

GET api/organisations/{organisation_uuid}/agent-runs/{run_id}/download

Headers

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

organisation_uuid   string     

Example: 205ae76c-bd73-426b-a36a-8338353b9542

run_id   integer     

The ID of the run. Example: 6

Query Parameters

format   string  optional    

Optional - 'pdf' (default) or 'docx'. Example: docx

Radar Usage

Returns this month's SerpApi spend for the organisation's passive brand radar, broken down against the plan budget cap (null when uncapped).

Example request:
const url = new URL(
    "https://backend-staging.subsig.com/api/organisations/6ff8f7f6-1eb3-3525-be4a-3932c805afed/ai-visibility/radar/usage"
);

const headers = {
    "Authorization": "Bearer {token}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());

Example response (401):

Show headers
cache-control: no-cache, private
content-type: application/json
vary: Origin
 

{
    "message": "Unauthenticated."
}
 

Request      

GET api/organisations/{organisation_uuid}/ai-visibility/radar/usage

Headers

Authorization        

Example: Bearer {token}

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

organisation_uuid   string     

The organisation UUID. Example: 6ff8f7f6-1eb3-3525-be4a-3932c805afed

Exchange the single-use `code` AppSumo redirects buyers with (after "Activate now") for a license_key + status. Unauthenticated -- a brand-new buyer has no subsig session yet.

AppSumo's own status field flips to "active" as soon as our webhook responds 200 to their activate event -- which happens independently of whether this specific person has ever been through our OAuth flow (AppSumoWebhookController always 200s that event). So it does NOT mean "this browser's user already has a subsig account" -- every first-time buyer would incorrectly see "active" before ever signing up. already_redeemed is the real signal: whether our own AppSumoLicense record has an organisation attached yet.

Frontend branches on already_redeemed for new-vs-returning, and on status === "deactivated" (still AppSumo's own billing-state field, legitimately authoritative for this one case) to block a cancelled/refunded license outright regardless of redemption state.

Example request:
const url = new URL(
    "https://backend-staging.subsig.com/api/appsumo/oauth/exchange"
);

const headers = {
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "code": "architecto"
};

fetch(url, {
    method: "POST",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());

Request      

POST api/appsumo/oauth/exchange

Headers

Content-Type        

Example: application/json

Accept        

Example: application/json

Body Parameters

code   string     

Example: architecto

POST api/webhook

Example request:
const url = new URL(
    "https://backend-staging.subsig.com/api/webhook"
);

const headers = {
    "Content-Type": "application/json",
    "Accept": "application/json",
};

fetch(url, {
    method: "POST",
    headers,
}).then(response => response.json());

Request      

POST api/webhook

Headers

Content-Type        

Example: application/json

Accept        

Example: application/json

GET api/v1/workspaces

Example request:
const url = new URL(
    "https://backend-staging.subsig.com/api/v1/workspaces"
);

const headers = {
    "Content-Type": "application/json",
    "Accept": "application/json",
};

fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());

Example response (401):

Show headers
cache-control: no-cache, private
content-type: application/json
x-request-id: 230a42d1-273f-49db-9e2b-7bc30ab9c025
vary: Origin
 

{
    "error": {
        "code": "unauthorized",
        "message": "Unauthorized.",
        "docs_url": "https://docs.subsig.com/errors/unauthorized",
        "request_id": "230a42d1-273f-49db-9e2b-7bc30ab9c025"
    }
}
 

Request      

GET api/v1/workspaces

Headers

Content-Type        

Example: application/json

Accept        

Example: application/json

GET api/v1/ai-visibility/topics

Example request:
const url = new URL(
    "https://backend-staging.subsig.com/api/v1/ai-visibility/topics"
);

const headers = {
    "Content-Type": "application/json",
    "Accept": "application/json",
};

fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());

Example response (401):

Show headers
cache-control: no-cache, private
content-type: application/json
x-request-id: 0e8d9117-2fd9-45a1-a933-469f26608487
vary: Origin
 

{
    "error": {
        "code": "unauthorized",
        "message": "Unauthorized.",
        "docs_url": "https://docs.subsig.com/errors/unauthorized",
        "request_id": "0e8d9117-2fd9-45a1-a933-469f26608487"
    }
}
 

Request      

GET api/v1/ai-visibility/topics

Headers

Content-Type        

Example: application/json

Accept        

Example: application/json

GET api/v1/ai-visibility/prompts

Example request:
const url = new URL(
    "https://backend-staging.subsig.com/api/v1/ai-visibility/prompts"
);

const headers = {
    "Content-Type": "application/json",
    "Accept": "application/json",
};

fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());

Example response (401):

Show headers
cache-control: no-cache, private
content-type: application/json
x-request-id: bad3a0be-5a89-43aa-bf4d-67742229db96
vary: Origin
 

{
    "error": {
        "code": "unauthorized",
        "message": "Unauthorized.",
        "docs_url": "https://docs.subsig.com/errors/unauthorized",
        "request_id": "bad3a0be-5a89-43aa-bf4d-67742229db96"
    }
}
 

Request      

GET api/v1/ai-visibility/prompts

Headers

Content-Type        

Example: application/json

Accept        

Example: application/json

GET api/v1/ai-visibility/prompts/{prompt}

Example request:
const url = new URL(
    "https://backend-staging.subsig.com/api/v1/ai-visibility/prompts/564"
);

const headers = {
    "Content-Type": "application/json",
    "Accept": "application/json",
};

fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());

Example response (401):

Show headers
cache-control: no-cache, private
content-type: application/json
x-request-id: 8611e2c8-ae7b-46cc-82fa-e3f3d54ab07d
vary: Origin
 

{
    "error": {
        "code": "unauthorized",
        "message": "Unauthorized.",
        "docs_url": "https://docs.subsig.com/errors/unauthorized",
        "request_id": "8611e2c8-ae7b-46cc-82fa-e3f3d54ab07d"
    }
}
 

Request      

GET api/v1/ai-visibility/prompts/{prompt}

Headers

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

prompt   string     

The prompt. Example: 564

Every brand tracked by AI Visibility (own brand + registered competitors), ranked, with visibility %/share-of-voice %/average position for each — the discovery step before digging into a specific brand via show() or into sentiment/citations for it. Built on the same AiVisibilityBrandRankService::brandMetrics()/AiVisibilityRanker::rankComposite() combination the session-auth AiVisibilityCompetitorController::index() (Competitor tab) already uses.

Example request:
const url = new URL(
    "https://backend-staging.subsig.com/api/v1/ai-visibility/brands"
);

const headers = {
    "Content-Type": "application/json",
    "Accept": "application/json",
};

fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());

Example response (401):

Show headers
cache-control: no-cache, private
content-type: application/json
x-request-id: 40edd226-b7ff-4aed-a7d1-1a7507403c9f
vary: Origin
 

{
    "error": {
        "code": "unauthorized",
        "message": "Unauthorized.",
        "docs_url": "https://docs.subsig.com/errors/unauthorized",
        "request_id": "40edd226-b7ff-4aed-a7d1-1a7507403c9f"
    }
}
 

Request      

GET api/v1/ai-visibility/brands

Headers

Content-Type        

Example: application/json

Accept        

Example: application/json

GET api/v1/ai-visibility/overview

Example request:
const url = new URL(
    "https://backend-staging.subsig.com/api/v1/ai-visibility/overview"
);

const headers = {
    "Content-Type": "application/json",
    "Accept": "application/json",
};

fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());

Example response (401):

Show headers
cache-control: no-cache, private
content-type: application/json
x-request-id: ab3d98b7-cdef-4320-9659-7430f33fdfd7
vary: Origin
 

{
    "error": {
        "code": "unauthorized",
        "message": "Unauthorized.",
        "docs_url": "https://docs.subsig.com/errors/unauthorized",
        "request_id": "ab3d98b7-cdef-4320-9659-7430f33fdfd7"
    }
}
 

Request      

GET api/v1/ai-visibility/overview

Headers

Content-Type        

Example: application/json

Accept        

Example: application/json

Detail for a single brand tracked by AI Visibility: own brand or a specific competitor, resolved by name against the SAME ai_visibility_brands-backed set index() draws its competitors list from. The 404 below says so explicitly rather than leaving the caller to guess why a name wasn't found.

Example request:
const url = new URL(
    "https://backend-staging.subsig.com/api/v1/ai-visibility/brand"
);

const headers = {
    "Content-Type": "application/json",
    "Accept": "application/json",
};

fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());

Example response (401):

Show headers
cache-control: no-cache, private
content-type: application/json
x-request-id: 096ad0b6-fccd-4dbf-b4a4-d1de13b84f20
vary: Origin
 

{
    "error": {
        "code": "unauthorized",
        "message": "Unauthorized.",
        "docs_url": "https://docs.subsig.com/errors/unauthorized",
        "request_id": "096ad0b6-fccd-4dbf-b4a4-d1de13b84f20"
    }
}
 

Request      

GET api/v1/ai-visibility/brand

Headers

Content-Type        

Example: application/json

Accept        

Example: application/json

GET api/v1/ai-visibility/platforms

Example request:
const url = new URL(
    "https://backend-staging.subsig.com/api/v1/ai-visibility/platforms"
);

const headers = {
    "Content-Type": "application/json",
    "Accept": "application/json",
};

fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());

Example response (401):

Show headers
cache-control: no-cache, private
content-type: application/json
x-request-id: cd19ac92-ee92-4a8e-8c50-0d9a091a3ca1
vary: Origin
 

{
    "error": {
        "code": "unauthorized",
        "message": "Unauthorized.",
        "docs_url": "https://docs.subsig.com/errors/unauthorized",
        "request_id": "cd19ac92-ee92-4a8e-8c50-0d9a091a3ca1"
    }
}
 

Request      

GET api/v1/ai-visibility/platforms

Headers

Content-Type        

Example: application/json

Accept        

Example: application/json

GET api/v1/ai-visibility/sentiment

Example request:
const url = new URL(
    "https://backend-staging.subsig.com/api/v1/ai-visibility/sentiment"
);

const headers = {
    "Content-Type": "application/json",
    "Accept": "application/json",
};

fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());

Example response (401):

Show headers
cache-control: no-cache, private
content-type: application/json
x-request-id: 27c8835c-12da-440d-a236-7fb3bb4009b1
vary: Origin
 

{
    "error": {
        "code": "unauthorized",
        "message": "Unauthorized.",
        "docs_url": "https://docs.subsig.com/errors/unauthorized",
        "request_id": "27c8835c-12da-440d-a236-7fb3bb4009b1"
    }
}
 

Request      

GET api/v1/ai-visibility/sentiment

Headers

Content-Type        

Example: application/json

Accept        

Example: application/json

GET api/v1/ai-visibility/citations

Example request:
const url = new URL(
    "https://backend-staging.subsig.com/api/v1/ai-visibility/citations"
);

const headers = {
    "Content-Type": "application/json",
    "Accept": "application/json",
};

fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());

Example response (401):

Show headers
cache-control: no-cache, private
content-type: application/json
x-request-id: 61a46285-b4b6-44a2-9978-d0229d14eb3c
vary: Origin
 

{
    "error": {
        "code": "unauthorized",
        "message": "Unauthorized.",
        "docs_url": "https://docs.subsig.com/errors/unauthorized",
        "request_id": "61a46285-b4b6-44a2-9978-d0229d14eb3c"
    }
}
 

Request      

GET api/v1/ai-visibility/citations

Headers

Content-Type        

Example: application/json

Accept        

Example: application/json

GET api/v1/ai-visibility/status

Example request:
const url = new URL(
    "https://backend-staging.subsig.com/api/v1/ai-visibility/status"
);

const headers = {
    "Content-Type": "application/json",
    "Accept": "application/json",
};

fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());

Example response (401):

Show headers
cache-control: no-cache, private
content-type: application/json
x-request-id: 505e799b-99dd-444b-a3c2-9bc91b3836e3
vary: Origin
 

{
    "error": {
        "code": "unauthorized",
        "message": "Unauthorized.",
        "docs_url": "https://docs.subsig.com/errors/unauthorized",
        "request_id": "505e799b-99dd-444b-a3c2-9bc91b3836e3"
    }
}
 

Request      

GET api/v1/ai-visibility/status

Headers

Content-Type        

Example: application/json

Accept        

Example: application/json

GET api/v1/agent-reports

Example request:
const url = new URL(
    "https://backend-staging.subsig.com/api/v1/agent-reports"
);

const headers = {
    "Content-Type": "application/json",
    "Accept": "application/json",
};

fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());

Example response (401):

Show headers
cache-control: no-cache, private
content-type: application/json
x-request-id: 9301ec08-32e8-4e59-b401-551c6f40f15e
vary: Origin
 

{
    "error": {
        "code": "unauthorized",
        "message": "Unauthorized.",
        "docs_url": "https://docs.subsig.com/errors/unauthorized",
        "request_id": "9301ec08-32e8-4e59-b401-551c6f40f15e"
    }
}
 

Request      

GET api/v1/agent-reports

Headers

Content-Type        

Example: application/json

Accept        

Example: application/json

GET api/v1/agent-reports/{run_id}

Example request:
const url = new URL(
    "https://backend-staging.subsig.com/api/v1/agent-reports/6"
);

const headers = {
    "Content-Type": "application/json",
    "Accept": "application/json",
};

fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());

Example response (401):

Show headers
cache-control: no-cache, private
content-type: application/json
x-request-id: 0a83ae38-983c-476a-8c89-2966e88aeb62
vary: Origin
 

{
    "error": {
        "code": "unauthorized",
        "message": "Unauthorized.",
        "docs_url": "https://docs.subsig.com/errors/unauthorized",
        "request_id": "0a83ae38-983c-476a-8c89-2966e88aeb62"
    }
}
 

Request      

GET api/v1/agent-reports/{run_id}

Headers

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

run_id   integer     

The ID of the run. Example: 6

External

Trusted external onboarding (API key): create a verified user and complete onboarding in one request.

Create User And Complete Claim Onboarding

Creates a user (or reuses an existing one), then either:

Example request:
const url = new URL(
    "https://backend-staging.subsig.com/api/external/claim-onboarding"
);

const headers = {
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "name": "John Doe",
    "email": "john@example.com",
    "organisation_name": "Acme Inc",
    "product_name": "Acme CRM",
    "product_website": "https:\/\/acme.com",
    "product_logo": "https:\/\/cdn.example.com\/logo.png"
};

fetch(url, {
    method: "POST",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());

Example response (201):


{
    "message": "Onboarding completed successfully.",
    "token": "1|abc123...",
    "magic_link": "https://subsig-frontend.vercel.app/login?qid=1%7Cabc123...&is_claim_profile=1&org_id=550e8400-e29b-41d4-a716-446655440000",
    "data": {
        "organisation_uuid": "550e8400-e29b-41d4-a716-446655440000",
        "product_uuid": "660e8400-e29b-41d4-a716-446655440001"
    }
}
 

Example response (401):


{
    "message": "Unauthorized."
}
 

Example response (422):


{
    "message": "Provided organisation does not match the existing claimed profile context.",
    "errors": {
        "organisation_name": [
            "Organisation does not match existing profile project."
        ]
    }
}
 

Request      

POST api/external/claim-onboarding

Headers

Content-Type        

Example: application/json

Accept        

Example: application/json

Body Parameters

name   string     

User full name. Example: John Doe

email   string     

User email address. Example: john@example.com

organisation_name   string     

Organisation name. Example: Acme Inc

product_name   string     

Product name. Example: Acme CRM

product_website   string     

Product website URL. Example: https://acme.com

product_logo   string  optional    

optional Product logo URL. Example: https://cdn.example.com/logo.png

Google Analytics Integration

Org-scoped via the X-Organisation-Id header (see ResolveOrganisation middleware), same pattern as the Slack integration -- no organisation_uuid path segment, $request->organisation is already resolved and access-checked by the time these methods run.

Connect Google Analytics Account

Exchanges an authorization code (obtained by the frontend's own OAuth flow) for tokens and creates or updates the connection. Supports multiple Google accounts per organisation -- a second call with a different account creates a second connection, not a collision.

Example request:
const url = new URL(
    "https://backend-staging.subsig.com/api/integrations/google-analytics/connect"
);

const headers = {
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "code": "4\/0Ab_...",
    "redirect_uri": "architecto"
};

fetch(url, {
    method: "POST",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());

Example response (201):


{
    "data": {
        "id": "...",
        "account_email": "user@example.com",
        "status": "connected",
        "connected_at": "...",
        "selected_property": null
    }
}
 

Request      

POST api/integrations/google-analytics/connect

Headers

Content-Type        

Example: application/json

Accept        

Example: application/json

Body Parameters

code   string     

The authorization code from Google. Example: 4/0Ab_...

redirect_uri   string     

Must exactly match the URI used to obtain the code. Example: architecto

List Google Analytics Connections

No properties included -- lazy-loaded separately per connection when its row is expanded.

Example request:
const url = new URL(
    "https://backend-staging.subsig.com/api/integrations/google-analytics/connections"
);

const headers = {
    "Content-Type": "application/json",
    "Accept": "application/json",
};

fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());

Example response (401):

Show headers
cache-control: no-cache, private
content-type: application/json
vary: Origin
 

{
    "message": "Unauthenticated."
}
 

Request      

GET api/integrations/google-analytics/connections

Headers

Content-Type        

Example: application/json

Accept        

Example: application/json

List GA4 Properties For A Connection

Fetched live from Google on first row-expand, briefly cached after (see GoogleAnalyticsService::listProperties()). Not eagerly included in the connections list.

Example request:
const url = new URL(
    "https://backend-staging.subsig.com/api/integrations/google-analytics/connections/architecto/properties"
);

const headers = {
    "Content-Type": "application/json",
    "Accept": "application/json",
};

fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());

Example response (200):


{
    "data": [
        {
            "id": "properties/123",
            "name": "Subsig - GA4",
            "domain": "subscribed.fyi",
            "sessions_per_month": 12400,
            "matches_site": true
        }
    ]
}
 

Request      

GET api/integrations/google-analytics/connections/{connection}/properties

Headers

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

connection   string     

The connection's uuid. Example: architecto

Select GA4 Property For A Connection

Validated against the connection's own real property list -- a customer can only select a property their own connected account actually has. Historical rows already collected under a previously selected property are never reassigned by changing this.

Example request:
const url = new URL(
    "https://backend-staging.subsig.com/api/integrations/google-analytics/connections/architecto/property"
);

const headers = {
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "property_id": "architecto"
};

fetch(url, {
    method: "PATCH",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());

Example response (200):


{
    "data": {
        "id": "...",
        "account_email": "...",
        "status": "connected",
        "connected_at": "...",
        "selected_property": {
            "id": "properties/123",
            "name": "Subsig - GA4"
        }
    }
}
 

Request      

PATCH api/integrations/google-analytics/connections/{connection}/property

Headers

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

connection   string     

The connection's uuid. Example: architecto

Body Parameters

property_id   string     

One of the ids returned by the properties endpoint. Example: architecto

Disconnect Google Analytics Account

Best-effort revoke on Google's side, then removes the connection regardless of whether the revoke call succeeds.

Example request:
const url = new URL(
    "https://backend-staging.subsig.com/api/integrations/google-analytics/connections/architecto"
);

const headers = {
    "Content-Type": "application/json",
    "Accept": "application/json",
};

fetch(url, {
    method: "DELETE",
    headers,
}).then(response => response.json());

Example response (204):

Empty response
 

Request      

DELETE api/integrations/google-analytics/connections/{connection}

Headers

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

connection   string     

The connection's uuid. Example: architecto

Home

Feeds the standalone Recommendations page, AI-Visibility-only. All the actual signal-gathering and recommendation logic lives in AiVisibilityRecommendationService, shared by nothing else, so this stays a thin request/response adapter.

GET api/organisations/{organisation_uuid}/agents/recommendations

Example request:
const url = new URL(
    "https://backend-staging.subsig.com/api/organisations/205ae76c-bd73-426b-a36a-8338353b9542/agents/recommendations"
);

const headers = {
    "Content-Type": "application/json",
    "Accept": "application/json",
};

fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());

Example response (401):

Show headers
cache-control: no-cache, private
content-type: application/json
vary: Origin
 

{
    "message": "Unauthenticated."
}
 

Request      

GET api/organisations/{organisation_uuid}/agents/recommendations

Headers

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

organisation_uuid   string     

Example: 205ae76c-bd73-426b-a36a-8338353b9542

Invites

APIs for managing invites

Validate Invite

Validate an invite token and return the associated email if the invite is valid. Also returns whether a user account already exists for the invite email, so the frontend can route to login vs signup. This endpoint is public and does not require authentication.

Example request:
const url = new URL(
    "https://backend-staging.subsig.com/api/invites/validate/abc123def456..."
);

const headers = {
    "Content-Type": "application/json",
    "Accept": "application/json",
};

fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());

Example response (200):


{
    "email": "user@example.com"
}
 

Example response (404, Invite not found):


{
    "message": "Invite not found."
}
 

Example response (410, Invite expired):


{
    "message": "This invite has expired."
}
 

Example response (410, Invite no longer valid):


{
    "message": "This invite is no longer valid."
}
 

Request      

GET api/invites/validate/{token}

Headers

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

token   string     

The invite token. Example: abc123def456...

Accept Invite (Existing User)

requires authentication

Accept an invitation as an already-registered, authenticated user. The authenticated user's email must match the invite email. The user will be added to the organisation or project specified in the invite.

Example request:
const url = new URL(
    "https://backend-staging.subsig.com/api/invites/accept-invite"
);

const headers = {
    "Authorization": "Bearer 1|abc123...",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "token": "eEtgjrcdtubjCu4817MfGiimvC2DQLBgaI7LpY1g5kdDMK5wJlQank7ZJ6PWurmb"
};

fetch(url, {
    method: "POST",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());

Example response (200):


{
    "message": "Invite accepted successfully.",
    "data": {
        "organisation": {
            "uuid": "550e8400-e29b-41d4-a716-446655440000",
            "name": "Acme Inc"
        }
    }
}
 

Example response (403, Email mismatch):


{
    "message": "This invite was sent to a different email address.",
    "errors": {
        "email": [
            "The invite email does not match your account email."
        ]
    }
}
 

Example response (404, Token not found):


{
    "message": "Invite not found.",
    "errors": {
        "token": [
            "The invite token is invalid or does not exist."
        ]
    }
}
 

Example response (410, Invite expired):


{
    "message": "Invite has expired.",
    "errors": {
        "token": [
            "This invite has expired. Please request a new invitation."
        ]
    }
}
 

Example response (410, Invite already accepted):


{
    "message": "Invite has already been accepted.",
    "errors": {
        "token": [
            "This invite has already been accepted."
        ]
    }
}
 

Example response (422, Already a member):


{
    "message": "You are already a member of this organisation.",
    "errors": {
        "organisation": [
            "You are already a member of this organisation."
        ]
    }
}
 

Request      

POST api/invites/accept-invite

Headers

Authorization        

Example: Bearer 1|abc123...

Content-Type        

Example: application/json

Accept        

Example: application/json

Body Parameters

token   string     

The invite token from the invitation link. Example: eEtgjrcdtubjCu4817MfGiimvC2DQLBgaI7LpY1g5kdDMK5wJlQank7ZJ6PWurmb

List Invites

requires authentication

Get invites based on context:

Example request:
const url = new URL(
    "https://backend-staging.subsig.com/api/invites"
);

const params = {
    "organisation_id": "550e8400-e29b-41d4-a716-446655440000",
    "project_id": "660e8400-e29b-41d4-a716-446655440001",
};
Object.keys(params)
    .forEach(key => url.searchParams.append(key, params[key]));

const headers = {
    "Authorization": "Bearer 1|abc123...",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());

Example response (200):


[
    {
        "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"
    }
]
 

Example response (403, No access to project):


{
    "message": "You do not have access to this project."
}
 

Example response (403, No access to organisation):


{
    "message": "You do not have access to this organisation."
}
 

Example response (404, Project not found):


{
    "message": "Project not found."
}
 

Example response (404, Organisation not found):


{
    "message": "Organisation not found."
}
 

Example response (422, No organisation context):


{
    "message": "No organisation context found.",
    "errors": {
        "organisation": [
            "Please select an organisation or set current organisation."
        ]
    }
}
 

Request      

GET api/invites

Headers

Authorization        

Example: Bearer 1|abc123...

Content-Type        

Example: application/json

Accept        

Example: application/json

Query Parameters

organisation_id   string  optional    

Optional. Organisation UUID to list invites for. If omitted, organisation is taken from X-Organisation-Id header or the user's current organisation. Example: 550e8400-e29b-41d4-a716-446655440000

project_id   string  optional    

Optional. Filter invites by project UUID. Example: 660e8400-e29b-41d4-a716-446655440001

Create Invite

requires authentication

Create a new invite for a user to join an organisation or project. For organisation invites, the organisation is determined from the provided organisation UUID.

Example request:
const url = new URL(
    "https://backend-staging.subsig.com/api/invites"
);

const headers = {
    "Authorization": "Bearer 1|abc123...",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "email": "user@example.com",
    "type": "organisation",
    "organisation_id": "550e8400-e29b-41d4-a716-446655440000",
    "project_id": "660e8400-e29b-41d4-a716-446655440001"
};

fetch(url, {
    method: "POST",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());

Example response (201):


{
    "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"
    }
}
 

Example response (403, Inviter not whitelisted):


{
    "message": "You are not permitted to send invites at this time."
}
 

Example response (422, No organisation context):


{
    "message": "No organisation context found.",
    "errors": {
        "organisation": [
            "Please select an organisation or set current organisation."
        ]
    }
}
 

Request      

POST api/invites

Headers

Authorization        

Example: Bearer 1|abc123...

Content-Type        

Example: application/json

Accept        

Example: application/json

Body Parameters

email   string     

The email address to send the invite to. Example: user@example.com

type   string     

The type of invite (organisation or project). Example: organisation

organisation_id   string     

The organisation UUID this invite belongs to. Example: 550e8400-e29b-41d4-a716-446655440000

project_id   string  optional    

The project UUID (required when type is project). Example: 660e8400-e29b-41d4-a716-446655440001

Delete Invite

requires authentication

Delete an invite. Only the inviter or organisation owners can delete invites.

Example request:
const url = new URL(
    "https://backend-staging.subsig.com/api/invites/1"
);

const headers = {
    "Authorization": "Bearer 1|abc123...",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

fetch(url, {
    method: "DELETE",
    headers,
}).then(response => response.json());

Example response (200):


{
    "message": "Invite deleted successfully."
}
 

Example response (403, Not authorized):


{
    "message": "You are not authorized to delete this invite."
}
 

Example response (404, Invite not found):


{
    "message": "Invite not found."
}
 

Example response (422, No organisation context):


{
    "message": "No organisation context found.",
    "errors": {
        "organisation": [
            "Please select an organisation or set current organisation."
        ]
    }
}
 

Request      

DELETE api/invites/{invite_id}

Headers

Authorization        

Example: Bearer 1|abc123...

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

invite_id   integer     

The invite ID. Example: 1

Links

APIs for managing project links

requires authentication

Get all links associated with a specific project.

Example request:
const url = new URL(
    "https://backend-staging.subsig.com/api/projects/660e8400-e29b-41d4-a716-446655440001/links"
);

const headers = {
    "Authorization": "Bearer 1|abc123...",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());

Example response (200):


{
    "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"
        }
    ]
}
 

Example response (403, No Access):


{
    "message": "You do not have access to this project."
}
 

Example response (404, Project Not Found):


{
    "message": "Project not found."
}
 

Onboarding

APIs for user onboarding flow

Complete Onboarding

requires authentication

Create an organisation and project in a single step during onboarding. The authenticated user becomes the organisation owner.

Example request:
const url = new URL(
    "https://backend-staging.subsig.com/api/onboarding"
);

const headers = {
    "Authorization": "Bearer 1|abc123...",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "organisation_name": "Acme Inc",
    "product_name": "Acme CRM",
    "product_website": "https:\/\/acme.com",
    "product_logo": "https:\/\/cdn.brandfetch.io\/acme.com\/fallback\/lettermark\/icon?c=BRANDFETCH_CLIENT_ID",
    "threshold_consumption_date": "last_14_days. Allowed: last_14_days, last_3_months, last_6_months, last_12_months, last_2_years, last_3_years, all_time"
};

fetch(url, {
    method: "POST",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());

Example response (201):


{
    "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",
            "threshold_consumption_date": "2026-05-11"
        },
        "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"
        }
    }
}
 

Example response (422, Validation error):


{
    "message": "The organisation name field is required.",
    "errors": {
        "organisation_name": [
            "The organisation name field is required."
        ]
    }
}
 

Request      

POST api/onboarding

Headers

Authorization        

Example: Bearer 1|abc123...

Content-Type        

Example: application/json

Accept        

Example: application/json

Body Parameters

organisation_name   string     

The organisation/company name. Example: Acme Inc

product_name   string     

The product name. Example: Acme CRM

product_website   string  optional    

The product website URL. Example: https://acme.com

product_logo   string  optional    

The product logo URL. Example: https://cdn.brandfetch.io/acme.com/fallback/lettermark/icon?c=BRANDFETCH_CLIENT_ID

threshold_consumption_date   string  optional    

optional Threshold consumption preset for the organisation. Example: last_14_days. Allowed: last_14_days, last_3_months, last_6_months, last_12_months, last_2_years, last_3_years, all_time

Update Onboarding

requires authentication

Update the current organisation and project for the authenticated user.

Example request:
const url = new URL(
    "https://backend-staging.subsig.com/api/onboarding"
);

const headers = {
    "Authorization": "Bearer 1|abc123...",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "organisation_name": "Acme Inc",
    "product_name": "Acme CRM",
    "product_website": "https:\/\/acme.com",
    "product_logo": "https:\/\/cdn.brandfetch.io\/acme.com\/fallback\/lettermark\/icon?c=BRANDFETCH_CLIENT_ID",
    "threshold_consumption_date": "last_6_months"
};

fetch(url, {
    method: "PUT",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());

Example response (200):


{
    "message": "Onboarding updated successfully.",
    "data": {
        "organisation": {
            "uuid": "550e8400-e29b-41d4-a716-446655440000",
            "name": "Acme Inc",
            "website": "https://acme.com",
            "threshold_consumption_date": "2026-02-25"
        },
        "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"
        }
    }
}
 

Example response (404, No organisation or project found):


{
    "message": "No organisation or project found for the current user."
}
 

Example response (422, Validation error):


{
    "message": "The organisation name field is required.",
    "errors": {
        "organisation_name": [
            "The organisation name field is required."
        ]
    }
}
 

Request      

PUT api/onboarding

Headers

Authorization        

Example: Bearer 1|abc123...

Content-Type        

Example: application/json

Accept        

Example: application/json

Body Parameters

organisation_name   string     

The organisation/company name. Example: Acme Inc

product_name   string     

The product name. Example: Acme CRM

product_website   string  optional    

The product website URL. Example: https://acme.com

product_logo   string  optional    

The product logo URL. Example: https://cdn.brandfetch.io/acme.com/fallback/lettermark/icon?c=BRANDFETCH_CLIENT_ID

threshold_consumption_date   string  optional    

optional Threshold consumption preset; recalculates stored date when provided. Example: last_6_months

Organisations

APIs for managing organisations

Supported Regions

Curated list of region/country codes usable for an organisation's region (and, by fallback, a prompt's country -- see AiVisibilityPromptController::store()). Backs the frontend's region/country picker so it never offers a value the backend would then reject. See config('ai_visibility.supported_regions')'s docblock for why this list isn't simply "every ISO 3166-1 alpha-2 code" or restricted to any one provider's own narrower support.

Example request:
const url = new URL(
    "https://backend-staging.subsig.com/api/organisations/supported-regions"
);

const headers = {
    "Content-Type": "application/json",
    "Accept": "application/json",
};

fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());

Example response (200):


{
    "data": [
        {
            "value": "US",
            "label": "United States"
        },
        {
            "value": "GB",
            "label": "United Kingdom"
        }
    ]
}
 

Request      

GET api/organisations/supported-regions

Headers

Content-Type        

Example: application/json

Accept        

Example: application/json

List Organisations

requires authentication

Get all organisations the authenticated user belongs to.

Example request:
const url = new URL(
    "https://backend-staging.subsig.com/api/organisations"
);

const headers = {
    "Authorization": "Bearer 1|abc123...",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());

Example response (200):


{
    "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"
        }
    ]
}
 

Request      

GET api/organisations

Headers

Authorization        

Example: Bearer 1|abc123...

Content-Type        

Example: application/json

Accept        

Example: application/json

Create Organisation

requires authentication

Create a new organisation. The authenticated user becomes the admin.

Example request:
const url = new URL(
    "https://backend-staging.subsig.com/api/organisations"
);

const headers = {
    "Authorization": "Bearer 1|abc123...",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "name": "Acme Inc",
    "website": "https:\/\/acme.com",
    "region": "architecto",
    "language": "ngzmiy"
};

fetch(url, {
    method: "POST",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());

Example response (201):


{
    "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"
    }
}
 

Request      

POST api/organisations

Headers

Authorization        

Example: Bearer 1|abc123...

Content-Type        

Example: application/json

Accept        

Example: application/json

Body Parameters

name   string     

The organisation name. Example: Acme Inc

website   string  optional    

optional The organisation website URL. Example: https://acme.com

region   string  optional    

Example: architecto

language   string  optional    

Must not be greater than 10 characters. Example: ngzmiy

Get Organisation

requires authentication

Get details of a specific organisation.

Example request:
const url = new URL(
    "https://backend-staging.subsig.com/api/organisations/205ae76c-bd73-426b-a36a-8338353b9542"
);

const headers = {
    "Authorization": "Bearer 1|abc123...",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());

Example response (200):


{
    "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"
    }
}
 

Example response (403, No access):


{
    "message": "You do not have access to this organisation."
}
 

Request      

GET api/organisations/{uuid}

Headers

Authorization        

Example: Bearer 1|abc123...

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

uuid   string     

Example: 205ae76c-bd73-426b-a36a-8338353b9542

organisation   string     

The organisation UUID. Example: 550e8400-e29b-41d4-a716-446655440000

Update Organisation

requires authentication

Update an organisation's details. Requires admin role.

Example request:
const url = new URL(
    "https://backend-staging.subsig.com/api/organisations/205ae76c-bd73-426b-a36a-8338353b9542"
);

const headers = {
    "Authorization": "Bearer 1|abc123...",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "name": "Acme Corp",
    "website": "https:\/\/acme.com",
    "region": "architecto",
    "language": "ngzmiy"
};

fetch(url, {
    method: "PUT",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());

Example response (200):


{
    "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"
    }
}
 

Example response (403, Not admin):


{
    "message": "You must be an organisation admin to perform this action."
}
 

Request      

PUT api/organisations/{uuid}

PATCH api/organisations/{uuid}

Headers

Authorization        

Example: Bearer 1|abc123...

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

uuid   string     

Example: 205ae76c-bd73-426b-a36a-8338353b9542

organisation   string     

The organisation UUID. Example: 550e8400-e29b-41d4-a716-446655440000

Body Parameters

name   string     

The organisation name. Example: Acme Corp

website   string  optional    

optional The organisation website URL. Example: https://acme.com

region   string  optional    

Example: architecto

language   string  optional    

Must not be greater than 10 characters. Example: ngzmiy

Delete Organisation

requires authentication

Delete an organisation and its projects (cascaded). Requires organisation owner role. Cannot delete the authenticated user's current (primary) workspace; switch first.

Example request:
const url = new URL(
    "https://backend-staging.subsig.com/api/organisations/205ae76c-bd73-426b-a36a-8338353b9542"
);

const headers = {
    "Authorization": "Bearer 1|abc123...",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

fetch(url, {
    method: "DELETE",
    headers,
}).then(response => response.json());

Example response (200):


{
    "message": "Organisation deleted successfully."
}
 

Example response (403, No access):


{
    "message": "You do not have access to this organisation."
}
 

Example response (403, Not owner):


{
    "message": "You must be an organisation admin to perform this action."
}
 

Example response (422, Current workspace):


{
    "message": "You cannot delete your current workspace. Switch to another organisation first."
}
 

Request      

DELETE api/organisations/{uuid}

Headers

Authorization        

Example: Bearer 1|abc123...

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

uuid   string     

Example: 205ae76c-bd73-426b-a36a-8338353b9542

organisation   string     

The organisation UUID. Example: 550e8400-e29b-41d4-a716-446655440000

Switch Current Organisation

requires authentication

Set the user's current organisation for subsequent requests.

Example request:
const url = new URL(
    "https://backend-staging.subsig.com/api/organisations/205ae76c-bd73-426b-a36a-8338353b9542/switch"
);

const headers = {
    "Authorization": "Bearer 1|abc123...",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

fetch(url, {
    method: "POST",
    headers,
}).then(response => response.json());

Example response (200):


{
    "message": "Switched to organisation successfully.",
    "data": {
        "uuid": "550e8400-e29b-41d4-a716-446655440000",
        "name": "Acme Inc"
    }
}
 

Example response (403, No access):


{
    "message": "You do not have access to this organisation."
}
 

Request      

POST api/organisations/{organisation_uuid}/switch

Headers

Authorization        

Example: Bearer 1|abc123...

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

organisation_uuid   string     

Example: 205ae76c-bd73-426b-a36a-8338353b9542

organisation   string     

The organisation UUID. Example: 550e8400-e29b-41d4-a716-446655440000

Ownership Transfer

Hands an entire organisation (and every other organisation its current owner also owns -- they move as one bundle) over to a new owner. Modeled as a third InviteType on the existing Invite table rather than a parallel system, since it already has everything a token-based offer/accept flow needs.

Accept Ownership Transfer

Example request:
const url = new URL(
    "https://backend-staging.subsig.com/api/ownership-transfers/accept"
);

const headers = {
    "Authorization": "Bearer {token}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "token": "architecto"
};

fetch(url, {
    method: "POST",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());

Request      

POST api/ownership-transfers/accept

Headers

Authorization        

Example: Bearer {token}

Content-Type        

Example: application/json

Accept        

Example: application/json

Body Parameters

token   string     

The ownership transfer token. Example: architecto

Initiate Ownership Transfer

Example request:
const url = new URL(
    "https://backend-staging.subsig.com/api/ownership-transfers"
);

const headers = {
    "Authorization": "Bearer {token}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "email": "gbailey@example.net",
    "organisation_id": "architecto"
};

fetch(url, {
    method: "POST",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());

Request      

POST api/ownership-transfers

Headers

Authorization        

Example: Bearer {token}

Content-Type        

Example: application/json

Accept        

Example: application/json

Body Parameters

email   string     

The new owner's email address. Example: gbailey@example.net

organisation_id   string     

The organisation UUID to transfer (its whole owned bundle moves together). Example: architecto

Platforms

APIs for managing platforms

List All Platforms

requires authentication

Get all platforms.

Example request:
const url = new URL(
    "https://backend-staging.subsig.com/api/platforms"
);

const headers = {
    "Authorization": "Bearer 1|abc123...",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());

Example response (200):


[
    {
        "uuid": "550e8400-e29b-41d4-a716-446655440000",
        "name": "G2",
        "created_at": "2026-01-08T10:00:00.000000Z",
        "updated_at": "2026-01-08T10:00:00.000000Z"
    }
]
 

Request      

GET api/platforms

Headers

Authorization        

Example: Bearer 1|abc123...

Content-Type        

Example: application/json

Accept        

Example: application/json

Create Platform

requires authentication

Create a new platform.

Example request:
const url = new URL(
    "https://backend-staging.subsig.com/api/platforms"
);

const headers = {
    "Authorization": "Bearer 1|abc123...",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "name": "G2"
};

fetch(url, {
    method: "POST",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());

Example response (201):


{
    "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"
    }
}
 

Example response (422, Validation Error):


{
    "message": "The name has already been taken.",
    "errors": {
        "name": [
            "The name has already been taken."
        ]
    }
}
 

Request      

POST api/platforms

Headers

Authorization        

Example: Bearer 1|abc123...

Content-Type        

Example: application/json

Accept        

Example: application/json

Body Parameters

name   string     

The platform name. Example: G2

Update Platform

requires authentication

Update a platform's details.

Example request:
const url = new URL(
    "https://backend-staging.subsig.com/api/platforms/550e8400-e29b-41d4-a716-446655440000"
);

const headers = {
    "Authorization": "Bearer 1|abc123...",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "name": "Capterra"
};

fetch(url, {
    method: "PATCH",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());

Example response (200):


{
    "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"
    }
}
 

Example response (404, Not Found):


{
    "message": "Platform not found."
}
 

Example response (422, Validation Error):


{
    "message": "The name has already been taken.",
    "errors": {
        "name": [
            "The name has already been taken."
        ]
    }
}
 

Request      

PATCH api/platforms/{uuid}

Headers

Authorization        

Example: Bearer 1|abc123...

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

uuid   string     

The platform UUID. Example: 550e8400-e29b-41d4-a716-446655440000

Body Parameters

name   string     

The platform name. Example: Capterra

Delete Platform

requires authentication

Delete a platform.

Example request:
const url = new URL(
    "https://backend-staging.subsig.com/api/platforms/550e8400-e29b-41d4-a716-446655440000"
);

const headers = {
    "Authorization": "Bearer 1|abc123...",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

fetch(url, {
    method: "DELETE",
    headers,
}).then(response => response.json());

Example response (200):


{
    "message": "Platform deleted successfully."
}
 

Example response (404, Not Found):


{
    "message": "Platform not found."
}
 

Request      

DELETE api/platforms/{uuid}

Headers

Authorization        

Example: Bearer 1|abc123...

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

uuid   string     

The platform UUID. Example: 550e8400-e29b-41d4-a716-446655440000

Profiles

APIs for managing product claims and product profile data.

The Profiles API group covers two related workflows:

  1. Claim Profiles - Submit and manage ownership claims for products
  2. Product Profiles - Edit and sync detailed product information

Claim Profiles

Claim profiles allow users to claim ownership of products listed in the system.

Once a claim is submitted, it goes through a review process. Approved claims grant access to edit the product's profile data.

Claim Statuses:

Staging Environment:

In staging, use test products from Curiosity (names ending with _test):

Configure via CURIOSITY_TEST_PRODUCT_IDS and CURIOSITY_RESTRICT_TO_TEST_PRODUCTS environment variables.

requires authentication

Search for products in the Curiosity database to claim.

Example request:
const url = new URL(
    "https://backend-staging.subsig.com/api/products/search"
);

const params = {
    "q": "slack",
    "limit": "10",
};
Object.keys(params)
    .forEach(key => url.searchParams.append(key, params[key]));

const headers = {
    "Authorization": "Bearer 1|abc123...",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());

Example response (200):


{
    "data": {
        "products": [
            {
                "id": 123,
                "name": "Slack",
                "url": "https://slack.com",
                "logo_path": "products/slack-logo.png"
            }
        ]
    }
}
 

List All Claim Profiles

requires authentication

Get all claim profiles across all organisations. Intended for admin panel usage.

Example request:
const url = new URL(
    "https://backend-staging.subsig.com/api/claim-profiles"
);

const headers = {
    "Authorization": "Bearer 1|abc123...",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());

Example response (200):


[
    {
        "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"
    }
]
 

Request      

GET api/claim-profiles

Headers

Authorization        

Example: Bearer 1|abc123...

Content-Type        

Example: application/json

Accept        

Example: application/json

Create Claim Profile

requires authentication

Create a new claim profile for a project. The organisation is automatically derived from the authenticated user's current organisation context.

Example request:
const url = new URL(
    "https://backend-staging.subsig.com/api/claim-profiles"
);

const headers = {
    "Authorization": "Bearer 1|abc123...",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "project_id": "660e8400-e29b-41d4-a716-446655440001",
    "scraper_product_id": 123,
    "product_name": "Slack",
    "product_url": "https:\/\/slack.com",
    "business_email": "john@company.com",
    "job_title": "Product Manager",
    "business_phone": "+1234567890"
};

fetch(url, {
    method: "POST",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());

Example response (201, Success):


{
    "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"
    }
}
 

Example response (403, No organisation context):


{
    "message": "This action is unauthorized."
}
 

Example response (422, Validation Error):


{
    "message": "The project is required. (and 1 more error)",
    "errors": {
        "project_id": [
            "The project is required."
        ],
        "business_email": [
            "Please use a business email address."
        ]
    }
}
 

Request      

POST api/claim-profiles

Headers

Authorization        

Example: Bearer 1|abc123...

Content-Type        

Example: application/json

Accept        

Example: application/json

Body Parameters

project_id   string     

The project UUID. Example: 660e8400-e29b-41d4-a716-446655440001

scraper_product_id   integer     

The ID of the product from Curiosity. Example: 123

product_name   string     

The name of the product being claimed. Example: Slack

product_url   string  optional    

The URL of the product. Example: https://slack.com

business_email   string     

The business email address. Example: john@company.com

job_title   string     

The job title. Example: Product Manager

business_phone   string  optional    

The business phone number (optional). Example: +1234567890

Product Profiles

Product profiles contain detailed information about claimed products, synced bidirectionally with the Curiosity database.

Product profiles are created automatically when a claim is approved. They include:

Sync Operations:

Staging Environment:

Sync operations are restricted to test products. Configure via:

Get Product Profile

requires authentication

Get the product profile for a specific project. Returns all profile data including content, SEO meta, categories, and sync status.

Example request:
const url = new URL(
    "https://backend-staging.subsig.com/api/projects/660e8400-e29b-41d4-a716-446655440001/profile"
);

const headers = {
    "Authorization": "Bearer 1|abc123...",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());

Example response (200):


{
    "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": "<p>Where work happens</p>",
        "overview": "<p>Slack is a messaging platform...</p>",
        "pricing": "<p>Free, Pro $7.25/user, Business+ $12.50/user</p>",
        "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": "<p>To cancel your subscription...</p>",
        "cancellation_content_summary": "<p>Cancel anytime from settings</p>",
        "book_demo_url": "https://slack.com/demo",
        "pricing_url": "https://slack.com/pricing",
        "pros_cons": "Pros: Easy to use\nCons: Can be expensive",
        "analysis": "<p>Detailed analysis of Slack...</p>",
        "faq": "Q: How much does it cost?\nA: Free tier available",
        "alternatives_text": "<p>Consider Microsoft Teams or Discord...</p>",
        "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"
    }
}
 

Example response (404):


{
    "message": "Project not found."
}
 

Example response (404):


{
    "message": "This project does not have a product profile yet."
}
 

Request      

GET api/projects/{project_uuid}/profile

Headers

Authorization        

Example: Bearer 1|abc123...

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

project_uuid   string     

The project UUID. Example: 660e8400-e29b-41d4-a716-446655440001

Update Product Profile

requires authentication

Submit a change request for the product profile. Changes are stored as a pending request for admin review. Once approved, changes will be applied and synced to Curiosity. Fields like subtitle, overview, pricing, analysis, and alternatives_text support HTML/richtext.

Example request:
const url = new URL(
    "https://backend-staging.subsig.com/api/projects/660e8400-e29b-41d4-a716-446655440001/profile"
);

const headers = {
    "Authorization": "Bearer 1|abc123...",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "subtitle": "<p>Where work happens<\/p>",
    "overview": "<p>Slack is a messaging platform...<\/p>",
    "pricing": "<p>Free, Pro $7.25\/user<\/p>",
    "parent_category_id": 5,
    "review_platforms": [],
    "videos": [
        "architecto"
    ],
    "categories": [
        []
    ],
    "segments": [
        []
    ],
    "search_fields": [],
    "competitors": [
        []
    ],
    "deal": "architecto",
    "deals_meta_title": "Best Slack Deals 2026",
    "deals_meta_description": "architecto",
    "cancellation_content": "architecto",
    "cancellation_content_summary": "architecto",
    "book_demo_url": "https:\/\/slack.com\/demo",
    "pricing_url": "https:\/\/slack.com\/pricing",
    "pros_cons": "architecto",
    "analysis": "architecto",
    "faq": "architecto",
    "alternatives_text": "architecto",
    "pricing_range": "$99-$999\/mo",
    "is_ai_powered": true,
    "meta": []
};

fetch(url, {
    method: "PUT",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());

Example response (200):


{
    "message": "Change request submitted for review.",
    "data": {
        "change_request_uuid": "880e8400-e29b-41d4-a716-446655440003",
        "status": "pending"
    }
}
 

Example response (404):


{
    "message": "This project does not have a product profile yet."
}
 

Request      

PUT api/projects/{project_uuid}/profile

Headers

Authorization        

Example: Bearer 1|abc123...

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

project_uuid   string     

The project UUID. Example: 660e8400-e29b-41d4-a716-446655440001

Body Parameters

subtitle   string  optional    

The product tagline (HTML supported). Example: <p>Where work happens</p>

overview   string  optional    

The product description (HTML supported). Example: <p>Slack is a messaging platform...</p>

pricing   string  optional    

Pricing information (HTML supported). Example: <p>Free, Pro $7.25/user</p>

parent_category_id   integer  optional    

The parent category ID from Curiosity. Example: 5

review_platforms   object  optional    

Review platform URLs and data.

g2   object  optional    
url   string  optional    

G2 review page URL. Example: https://g2.com/products/slack

enabled   boolean  optional    

Whether G2 is enabled. Example: true

capterra   object  optional    
url   string  optional    

Capterra review page URL. Example: https://capterra.com/p/123/slack

enabled   boolean  optional    

Whether Capterra is enabled. Example: true

videos   string[]  optional    

List of video URLs.

categories   object[]  optional    

List of category objects with id and name.

segments   object[]  optional    

List of segment objects with id and name.

search_fields   object  optional    

Search field categorization by type (built_for, platform, pricing_model arrays).

competitors   object[]  optional    

List of competitor products with id, name, url, logo_path.

deal   string  optional    

Deal content/description. Example: architecto

deals_meta_title   string  optional    

SEO meta title for deals page. Example: Best Slack Deals 2026

deals_meta_description   string  optional    

SEO meta description for deals page. Example: architecto

cancellation_content   string  optional    

How to cancel subscription content (HTML supported). Example: architecto

cancellation_content_summary   string  optional    

Summary of cancellation info (HTML supported). Example: architecto

book_demo_url   string  optional    

URL to book a demo. Example: https://slack.com/demo

pricing_url   string  optional    

URL to pricing page. Example: https://slack.com/pricing

pros_cons   string  optional    

Product pros and cons. Example: architecto

analysis   string  optional    

Detailed product analysis (HTML supported). Example: architecto

faq   string  optional    

Frequently asked questions. Example: architecto

alternatives_text   string  optional    

Description of alternatives (HTML supported). Example: architecto

pricing_range   string  optional    

Price tier display. Example: $99-$999/mo

is_ai_powered   boolean  optional    

Whether product uses AI technology. Example: true

meta   object  optional    

SEO meta tags for various pages.

main_page   object  optional    
title   string  optional    

Main page meta title. Example: Slack - Where Work Happens

description   string  optional    

Main page meta description. Example: Eius et animi quos velit et.

deals   object  optional    
title   string  optional    

Deals page meta title. Example: architecto

description   string  optional    

Deals page meta description. Example: Eius et animi quos velit et.

cancellation   object  optional    
title   string  optional    

Cancellation page meta title. Example: architecto

description   string  optional    

Cancellation page meta description. Example: Eius et animi quos velit et.

requires authentication

Upload a new logo image for the product profile. The logo is stored locally and will be synced to Curiosity when you push changes.

Get Sync Status

requires authentication

Get the current sync status of a project's product profile.

Example request:
const url = new URL(
    "https://backend-staging.subsig.com/api/projects/660e8400-e29b-41d4-a716-446655440001/profile/status"
);

const headers = {
    "Authorization": "Bearer 1|abc123...",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());

Example response (200):


{
    "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
        }
    }
}
 

Example response (200):


{
    "data": {
        "has_profile": false,
        "has_curiosity_link": true,
        "sync_status": null,
        "synced_at": null,
        "local_changes_at": null,
        "last_change_request": null
    }
}
 

Request      

GET api/projects/{project_uuid}/profile/status

Headers

Authorization        

Example: Bearer 1|abc123...

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

project_uuid   string     

The project UUID. Example: 660e8400-e29b-41d4-a716-446655440001

Catalog

Reference data for product categorization and filtering.

These endpoints provide access to taxonomy data from Curiosity used when editing product profiles.

Available Data:

List Parent Categories

requires authentication

Get all top-level parent categories for product classification. Parent categories represent broad product domains.

Example request:
const url = new URL(
    "https://backend-staging.subsig.com/api/catalog/parent-categories"
);

const headers = {
    "Authorization": "Bearer 1|abc123...",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());

Example response (200):


{
    "data": [
        {
            "id": 1,
            "name": "Analytics"
        },
        {
            "id": 2,
            "name": "Communication"
        },
        {
            "id": 3,
            "name": "Marketing"
        },
        {
            "id": 4,
            "name": "Project Management"
        },
        {
            "id": 5,
            "name": "Sales"
        }
    ]
}
 

Request      

GET api/catalog/parent-categories

Headers

Authorization        

Example: Bearer 1|abc123...

Content-Type        

Example: application/json

Accept        

Example: application/json

List Categories

requires authentication

Get all detailed categories for product classification. Categories are more specific than parent categories and can be assigned to products.

Example request:
const url = new URL(
    "https://backend-staging.subsig.com/api/catalog/categories"
);

const headers = {
    "Authorization": "Bearer 1|abc123...",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());

Example response (200):


{
    "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"
        }
    ]
}
 

Request      

GET api/catalog/categories

Headers

Authorization        

Example: Bearer 1|abc123...

Content-Type        

Example: application/json

Accept        

Example: application/json

List Segments

requires authentication

Get all market segments for product targeting. Segments define the target audience or market size for products.

Example request:
const url = new URL(
    "https://backend-staging.subsig.com/api/catalog/segments"
);

const headers = {
    "Authorization": "Bearer 1|abc123...",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());

Example response (200):


{
    "data": [
        {
            "id": 1,
            "name": "Enterprise"
        },
        {
            "id": 2,
            "name": "Mid-Market"
        },
        {
            "id": 3,
            "name": "SMB"
        },
        {
            "id": 4,
            "name": "Startup"
        },
        {
            "id": 5,
            "name": "Freelancer"
        }
    ]
}
 

Request      

GET api/catalog/segments

Headers

Authorization        

Example: Bearer 1|abc123...

Content-Type        

Example: application/json

Accept        

Example: application/json

List Search Fields

requires authentication

Get all search field options grouped by type. Search fields are structured attributes used for filtering and discovery.

Field Types:

Example request:
const url = new URL(
    "https://backend-staging.subsig.com/api/catalog/search-fields"
);

const headers = {
    "Authorization": "Bearer 1|abc123...",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());

Example response (200):


{
    "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"
            }
        ]
    }
}
 

Request      

GET api/catalog/search-fields

Headers

Authorization        

Example: Bearer 1|abc123...

Content-Type        

Example: application/json

Accept        

Example: application/json

Projects

APIs for managing projects within organisations

List Projects

requires authentication

Get all projects in an organisation that the user has access to.

Example request:
const url = new URL(
    "https://backend-staging.subsig.com/api/organisations/205ae76c-bd73-426b-a36a-8338353b9542/projects"
);

const headers = {
    "Authorization": "Bearer 1|abc123...",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());

Example response (200):


{
    "data": [
        {
            "uuid": "660e8400-e29b-41d4-a716-446655440001",
            "product_name": "Acme App",
            "product_website": "https://acme.com",
            "created_at": "2025-12-10T10:00:00.000000Z",
            "updated_at": "2025-12-10T10:00:00.000000Z"
        }
    ]
}
 

Request      

GET api/organisations/{organisation_uuid}/projects

Headers

Authorization        

Example: Bearer 1|abc123...

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

organisation_uuid   string     

Example: 205ae76c-bd73-426b-a36a-8338353b9542

organisation   string     

The organisation UUID. Example: 550e8400-e29b-41d4-a716-446655440000

Create Project

requires authentication

Create a new project in an organisation. Requires organisation admin role.

Example request:
const url = new URL(
    "https://backend-staging.subsig.com/api/organisations/205ae76c-bd73-426b-a36a-8338353b9542/projects"
);

const headers = {
    "Authorization": "Bearer 1|abc123...",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "product_name": "Acme App",
    "product_website": "https:\/\/acme.com",
    "product_logo": "https:\/\/cdn.brandfetch.io\/acme.com\/fallback\/lettermark\/icon?c=BRANDFETCH_CLIENT_ID"
};

fetch(url, {
    method: "POST",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());

Example response (201):


{
    "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",
        "is_competitor": false,
        "created_at": "2025-12-10T10:00:00.000000Z",
        "updated_at": "2025-12-10T10:00:00.000000Z"
    }
}
 

Request      

POST api/organisations/{organisation_uuid}/projects

Headers

Authorization        

Example: Bearer 1|abc123...

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

organisation_uuid   string     

Example: 205ae76c-bd73-426b-a36a-8338353b9542

organisation   string     

The organisation UUID. Example: 550e8400-e29b-41d4-a716-446655440000

Body Parameters

product_name   string     

The product name. Example: Acme App

product_website   string  optional    

The product website URL. Example: https://acme.com

product_logo   string  optional    

The product logo URL. Example: https://cdn.brandfetch.io/acme.com/fallback/lettermark/icon?c=BRANDFETCH_CLIENT_ID

Get Project

requires authentication

Get details of a specific project.

Example request:
const url = new URL(
    "https://backend-staging.subsig.com/api/organisations/205ae76c-bd73-426b-a36a-8338353b9542/projects/cbe3b865-507c-442a-aeab-0e6b948c10e0"
);

const headers = {
    "Authorization": "Bearer 1|abc123...",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());

Example response (200):


{
    "data": {
        "uuid": "660e8400-e29b-41d4-a716-446655440001",
        "product_name": "Acme App",
        "product_website": "https://acme.com",
        "created_at": "2025-12-10T10:00:00.000000Z",
        "updated_at": "2025-12-10T10:00:00.000000Z"
    }
}
 

Request      

GET api/organisations/{organisation_uuid}/projects/{uuid}

Headers

Authorization        

Example: Bearer 1|abc123...

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

organisation_uuid   string     

Example: 205ae76c-bd73-426b-a36a-8338353b9542

uuid   string     

Example: cbe3b865-507c-442a-aeab-0e6b948c10e0

organisation   string     

The organisation UUID. Example: 550e8400-e29b-41d4-a716-446655440000

project   string     

The project UUID. Example: 660e8400-e29b-41d4-a716-446655440001

Update Project

requires authentication

Update a project's details. Requires organisation admin role.

Example request:
const url = new URL(
    "https://backend-staging.subsig.com/api/organisations/205ae76c-bd73-426b-a36a-8338353b9542/projects/cbe3b865-507c-442a-aeab-0e6b948c10e0"
);

const headers = {
    "Authorization": "Bearer 1|abc123...",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "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"
};

fetch(url, {
    method: "PUT",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());

Example response (200):


{
    "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",
        "is_competitor": false,
        "created_at": "2025-12-10T10:00:00.000000Z",
        "updated_at": "2025-12-10T10:00:00.000000Z"
    }
}
 

Request      

PUT api/organisations/{organisation_uuid}/projects/{uuid}

PATCH api/organisations/{organisation_uuid}/projects/{uuid}

Headers

Authorization        

Example: Bearer 1|abc123...

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

organisation_uuid   string     

Example: 205ae76c-bd73-426b-a36a-8338353b9542

uuid   string     

Example: cbe3b865-507c-442a-aeab-0e6b948c10e0

organisation   string     

The organisation UUID. Example: 550e8400-e29b-41d4-a716-446655440000

project   string     

The project UUID. Example: 660e8400-e29b-41d4-a716-446655440001

Body Parameters

product_name   string     

The product name. Example: Acme App Pro

product_website   string  optional    

The product website URL. Example: https://acme.com

product_logo   string  optional    

The product logo URL. Example: https://cdn.brandfetch.io/acme.com/fallback/lettermark/icon?c=BRANDFETCH_CLIENT_ID

Delete Project

requires authentication

Delete a project. Requires organisation admin role.

Example request:
const url = new URL(
    "https://backend-staging.subsig.com/api/organisations/205ae76c-bd73-426b-a36a-8338353b9542/projects/cbe3b865-507c-442a-aeab-0e6b948c10e0"
);

const headers = {
    "Authorization": "Bearer 1|abc123...",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

fetch(url, {
    method: "DELETE",
    headers,
}).then(response => response.json());

Example response (200):


{
    "message": "Project deleted successfully."
}
 

Request      

DELETE api/organisations/{organisation_uuid}/projects/{uuid}

Headers

Authorization        

Example: Bearer 1|abc123...

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

organisation_uuid   string     

Example: 205ae76c-bd73-426b-a36a-8338353b9542

uuid   string     

Example: cbe3b865-507c-442a-aeab-0e6b948c10e0

organisation   string     

The organisation UUID. Example: 550e8400-e29b-41d4-a716-446655440000

project   string     

The project UUID. Example: 660e8400-e29b-41d4-a716-446655440001

Slack Integration

APIs for connecting and managing Slack workspace integration

Slack OAuth Callback

Handles the OAuth callback from Slack after user authorization. Exchanges the authorization code for an access token. This endpoint is unauthenticated - uses cached state for auth context.

Example request:
const url = new URL(
    "https://backend-staging.subsig.com/api/integrations/slack/callback"
);

const params = {
    "code": "123456789.abcdef",
    "state": "abc123...",
    "error": "access_denied",
};
Object.keys(params)
    .forEach(key => url.searchParams.append(key, params[key]));

const headers = {
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "code": "architecto",
    "state": "ngzmiyvdljnikhwaykcmyuwpwlvqwrsitcpscqldzsnrwtujwvlxjklqppwqbewt",
    "error": "architecto"
};

fetch(url, {
    method: "GET",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());

Example response (302, Success):


Redirects to /dashboard/notification/overview?slack_connected=true&workspace={name}
 

Example response (302, Error):


Redirects to /dashboard/notification/overview?slack_error={error_code}
 

Example response (302):

Show headers
cache-control: no-cache, private
location: https://subsig-frontend.vercel.app/dashboard/notification/overview?slack_error=architecto
content-type: text/html; charset=utf-8
vary: Origin
 

<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8" />
        <meta http-equiv="refresh" content="0;url='https://subsig-frontend.vercel.app/dashboard/notification/overview?slack_error=architecto'" />

        <title>Redirecting to https://subsig-frontend.vercel.app/dashboard/notification/overview?slack_error=architecto</title>
    </head>
    <body>
        Redirecting to <a href="https://subsig-frontend.vercel.app/dashboard/notification/overview?slack_error=architecto">https://subsig-frontend.vercel.app/dashboard/notification/overview?slack_error=architecto</a>.
    </body>
</html>
 

Request      

GET api/integrations/slack/callback

Headers

Content-Type        

Example: application/json

Accept        

Example: application/json

Query Parameters

code   string  optional    

The authorization code from Slack. Example: 123456789.abcdef

state   string  optional    

The state parameter for CSRF protection. Example: abc123...

error   string  optional    

OAuth error if user denied access. Example: access_denied

Body Parameters

code   string  optional    

This field is required when error is not present. Example: architecto

state   string     

Must be 64 characters. Example: ngzmiyvdljnikhwaykcmyuwpwlvqwrsitcpscqldzsnrwtujwvlxjklqppwqbewt

error   string  optional    

Example: architecto

Initiate Slack OAuth

requires authentication

Starts the OAuth flow to connect a Slack workspace. Redirects to Slack's authorization page.

Example request:
const url = new URL(
    "https://backend-staging.subsig.com/api/integrations/slack/connect"
);

const headers = {
    "Authorization": "Bearer 1|abc123...",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());

Example response (302, Redirect to Slack):


Redirects to Slack OAuth page
 

Example response (400, Not configured):


{
    "error": "configuration",
    "message": "Slack integration is not configured."
}
 

Example response (401):

Show headers
cache-control: no-cache, private
content-type: application/json
vary: Origin
 

{
    "message": "Unauthenticated."
}
 

Example response (403, No organisation):


{
    "message": "Organisation context required."
}
 

Request      

GET api/integrations/slack/connect

Headers

Authorization        

Example: Bearer 1|abc123...

Content-Type        

Example: application/json

Accept        

Example: application/json

Get Slack Connection Status

requires authentication

Returns the current Slack connection status for the organisation.

Example request:
const url = new URL(
    "https://backend-staging.subsig.com/api/integrations/slack/status"
);

const headers = {
    "Authorization": "Bearer 1|abc123...",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());

Example response (200, Connected):


{
    "connected": true,
    "team_id": "T123456789",
    "team_name": "My Workspace",
    "scopes": "chat:write,channels:read",
    "connected_at": "2025-01-01T12:00:00Z"
}
 

Example response (200, Not connected):


{
    "connected": false
}
 

Example response (200, Invalid token):


{
    "connected": false,
    "error": "token_invalid",
    "message": "Slack connection needs to be re-authorized."
}
 

Example response (403, No organisation):


{
    "message": "Organisation context required."
}
 

Request      

GET api/integrations/slack/status

Headers

Authorization        

Example: Bearer 1|abc123...

Content-Type        

Example: application/json

Accept        

Example: application/json

Disconnect Slack

requires authentication

Removes the Slack workspace connection for the organisation.

Example request:
const url = new URL(
    "https://backend-staging.subsig.com/api/integrations/slack/disconnect"
);

const headers = {
    "Authorization": "Bearer 1|abc123...",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

fetch(url, {
    method: "POST",
    headers,
}).then(response => response.json());

Example response (200, Success):


{
    "success": true,
    "message": "Slack connection removed."
}
 

Example response (200, Not connected):


{
    "success": true,
    "message": "No Slack connection found."
}
 

Example response (403, No organisation):


{
    "message": "Organisation context required."
}
 

Request      

POST api/integrations/slack/disconnect

Headers

Authorization        

Example: Bearer 1|abc123...

Content-Type        

Example: application/json

Accept        

Example: application/json

List Slack Channels

requires authentication

Fetches the list of channels from the connected Slack workspace.

Example request:
const url = new URL(
    "https://backend-staging.subsig.com/api/integrations/slack/channels"
);

const headers = {
    "Authorization": "Bearer 1|abc123...",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());

Example response (200, Success):


{
    "ok": true,
    "channels": [
        {
            "id": "C123456789",
            "name": "general",
            "is_member": true
        },
        {
            "id": "C987654321",
            "name": "random",
            "is_member": false
        }
    ]
}
 

Example response (401, Not connected):


{
    "ok": false,
    "error": "not_connected",
    "message": "Not connected to Slack."
}
 

Example response (401, Invalid token):


{
    "ok": false,
    "error": "token_invalid",
    "message": "Slack connection needs to be re-authorized."
}
 

Example response (403, No organisation):


{
    "message": "Organisation context required."
}
 

Request      

GET api/integrations/slack/channels

Headers

Authorization        

Example: Bearer 1|abc123...

Content-Type        

Example: application/json

Accept        

Example: application/json

Send a message to Slack

requires authentication

Accepts a payload in either Reviews format (data = array of reviews) or Mentions format (data = object with posts and/or comments) and posts a Block Kit message to the given Slack channel via the notification dispatcher.

Example request:
const url = new URL(
    "https://backend-staging.subsig.com/api/integrations/slack/send"
);

const headers = {
    "Authorization": "Bearer 1|abc123...",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "channel_id": "b",
    "status": "architecto",
    "process_id": "architecto",
    "created_at": "architecto",
    "data": []
};

fetch(url, {
    method: "POST",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());

Example response (200, Success):


{
    "ok": true,
    "message": "Message sent."
}
 

Example response (400, Slack API error):


{
    "ok": false,
    "error": "...",
    "message": "..."
}
 

Example response (401, Not connected):


{
    "ok": false,
    "error": "not_connected",
    "message": "Not connected to Slack."
}
 

Example response (422, Invalid payload):


{
    "message": "Invalid payload: data must be reviews array or mentions object."
}
 

Example response (500, Server error):


{
    "message": "Failed to send message."
}
 

Request      

POST api/integrations/slack/send

Headers

Authorization        

Example: Bearer 1|abc123...

Content-Type        

Example: application/json

Accept        

Example: application/json

Body Parameters

channel_id   string     

Must not be greater than 32 characters. Example: b

status   string     

Example: architecto

process_id   string     

Example: architecto

created_at   string     

Example: architecto

data   object     

Subscriptions

APIs for managing subscriptions

List Subscription Plans

requires authentication

Get all available subscription plans.

Example request:
const url = new URL(
    "https://backend-staging.subsig.com/api/subscription-plans"
);

const headers = {
    "Authorization": "Bearer 1|abc123...",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());

Example response (200):


{
    "data": [
        {
            "id": 1,
            "stripe_price_id": "price_internal_free",
            "name": "Free",
            "description": "Post-trial free tier",
            "amount": 0,
            "currency": "usd",
            "interval": "month",
            "plan_threshold": {}
        }
    ]
}
 

Request      

GET api/subscription-plans

Headers

Authorization        

Example: Bearer 1|abc123...

Content-Type        

Example: application/json

Accept        

Example: application/json

Get Threshold Usage

requires authentication

Returns plan soft limits in threshold and limits.*_soft. Usage is all-time across organisations owned by the billing user. Per-platform breakdowns are uncapped; they may sum above the top-level totals.

Example request:
const url = new URL(
    "https://backend-staging.subsig.com/api/subscriptions/threshold-usage"
);

const headers = {
    "Authorization": "Bearer 1|abc123...",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());

Example response (200):


{
    "data": {
        "threshold": {
            "competitor_brands_limit": 3,
            "history_months": 6,
            "refresh_interval": "daily",
            "users_limit": null,
            "workspaces_limit": null,
            "data_export_type": null,
            "ai_visibility_prompts_limit": 20
        },
        "limits": {
            "ai_visibility_prompts_soft": 20
        },
        "usage": {
            "mentions": 1000,
            "mentions_per_platform": {
                "reddit": 200
            },
            "competitor_brands": 0,
            "ai_visibility_prompts": 7,
            "workspaces": 1,
            "users": 1
        },
        "usage_period": {
            "scope": "all_time",
            "start": "2025-01-01",
            "end": "2026-04-02"
        },
        "trial_days_left": 3
    }
}
 

Example response (403, No organisation access):


{
    "message": "You do not have access to this organisation."
}
 

Request      

GET api/subscriptions/threshold-usage

Headers

Authorization        

Example: Bearer 1|abc123...

Content-Type        

Example: application/json

Accept        

Example: application/json

POST api/subscriptions/checkout

Example request:
const url = new URL(
    "https://backend-staging.subsig.com/api/subscriptions/checkout"
);

const headers = {
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "plan_id": 16,
    "success_url": "http:\/\/bailey.com\/",
    "cancel_url": "http:\/\/rempel.com\/sunt-nihil-accusantium-harum-mollitia",
    "addons": {
        "ai_visibility_prompts": {
            "price": 23,
            "quantity": 64
        }
    }
};

fetch(url, {
    method: "POST",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());

Request      

POST api/subscriptions/checkout

Headers

Content-Type        

Example: application/json

Accept        

Example: application/json

Body Parameters

plan_id   integer     

The id of an existing record in the subscription_plans table. Example: 16

success_url   string     

Must be a valid URL. Example: http://bailey.com/

cancel_url   string     

Must be a valid URL. Example: http://rempel.com/sunt-nihil-accusantium-harum-mollitia

addons   object  optional    
ai_visibility_prompts   object  optional    
price   integer  optional    

This field is required when addons.ai_visibility_prompts is present. Must be at least 1. Example: 23

quantity   integer  optional    

This field is required when addons.ai_visibility_prompts is present. Must be at least 1. Example: 64

Update Subscription Add-ons

requires authentication

Update add-ons on the organisation's current active subscription. Supports optional proration_date to control Stripe proration timing.

Example request:
const url = new URL(
    "https://backend-staging.subsig.com/api/subscriptions/addons"
);

const headers = {
    "Authorization": "Bearer 1|abc123...",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "addons": {
        "ai_visibility_prompts": {
            "price": 14000,
            "quantity": 1000
        }
    },
    "proration_date": 1715000000
};

fetch(url, {
    method: "POST",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());

Example response (200):


{
    "data": {
        "id": 1,
        "stripe_subscription_id": "sub_1234567890",
        "status": "active",
        "addons": [
            {
                "id": 10,
                "product_id": "prod_ai_visibility_prompts_123",
                "stripe_price_id": "price_inline_ai_visibility_prompts_1",
                "price": 2000,
                "ai_visibility_prompts_quantity": 1,
                "status": "active"
            }
        ]
    }
}
 

Example response (403, Not owner or no organisation access):


{
    "message": "You must be an organisation owner to manage subscriptions."
}
 

Example response (404, No active subscription):


{
    "message": "No active subscription found for this organisation."
}
 

Example response (422, Validation error):


{
    "message": "The given data was invalid."
}
 

Request      

POST api/subscriptions/addons

Headers

Authorization        

Example: Bearer 1|abc123...

Content-Type        

Example: application/json

Accept        

Example: application/json

Body Parameters

addons   object     

Add-ons payload to update.

ai_visibility_prompts   object  optional    

Optional AI Visibility Prompts add-on payload.

price   integer  optional    

Required with addons.ai_visibility_prompts. Total amount in cents. Example: 14000

quantity   integer  optional    

Required with addons.ai_visibility_prompts. Quantity to provision. Example: 1000

proration_date   integer  optional    

Optional Unix timestamp used by Stripe for proration. Example: 1715000000

Preview Add-on Proration

requires authentication

Preview Stripe prorated invoice impact for add-on changes without applying the subscription update.

Example request:
const url = new URL(
    "https://backend-staging.subsig.com/api/subscriptions/addons/preview-proration"
);

const headers = {
    "Authorization": "Bearer 1|abc123...",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "addons": {
        "ai_visibility_prompts": {
            "price": 14000,
            "quantity": 1000
        }
    },
    "proration_date": 1715000000
};

fetch(url, {
    method: "POST",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());

Example response (200):


{
    "data": {
        "amount_due": 2400,
        "subtotal": 2400,
        "total": 2400,
        "currency": "usd",
        "proration_lines": [
            {
                "id": "il_proration_123",
                "amount": 2400,
                "currency": "usd",
                "description": "Proration adjustment"
            }
        ]
    }
}
 

Example response (403, Not owner or no organisation access):


{
    "message": "You must be an organisation owner to manage subscriptions."
}
 

Example response (404, No active subscription):


{
    "message": "No active subscription found for this organisation."
}
 

Example response (422, Validation error):


{
    "message": "The given data was invalid."
}
 

Request      

POST api/subscriptions/addons/preview-proration

Headers

Authorization        

Example: Bearer 1|abc123...

Content-Type        

Example: application/json

Accept        

Example: application/json

Body Parameters

addons   object     

Add-ons payload to preview.

ai_visibility_prompts   object  optional    

Optional AI Visibility Prompts add-on payload.

price   integer  optional    

Required with addons.ai_visibility_prompts. Total amount in cents. Example: 14000

quantity   integer  optional    

Required with addons.ai_visibility_prompts. Quantity to provision. Example: 1000

proration_date   integer  optional    

Optional Unix timestamp used by Stripe for proration preview. Example: 1715000000

AI Platforms Pricing

requires authentication

Cents-per-prompt rate for each AI platform add-on, keyed in the frontend's own provider vocabulary (GetMoreAiPlatformsModal.tsx multiplies this by the org's current AI Visibility Prompts quantity to render its preview) -- this is now also the exact table repriceAiPlatformsAddons() uses server-side to compute the real charge in updateAddons(), so the preview and the actual charge can never drift out of sync.

Example request:
const url = new URL(
    "https://backend-staging.subsig.com/api/subscriptions/ai-platforms/pricing"
);

const headers = {
    "Authorization": "Bearer 1|abc123...",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());

Example response (200):


{
    "data": {
        "openai": 0,
        "perplexity": 0,
        "google_ai_overviews": 0,
        "bing_copilot": 0,
        "google_ai_mode": 43,
        "gemini": 161,
        "claude": 142,
        "deepseek": 12,
        "grok": 120
    }
}
 

Request      

GET api/subscriptions/ai-platforms/pricing

Headers

Authorization        

Example: Bearer 1|abc123...

Content-Type        

Example: application/json

Accept        

Example: application/json

Get Current Subscription

requires authentication

Get the current subscription for the organisation.

Example request:
const url = new URL(
    "https://backend-staging.subsig.com/api/subscriptions/current"
);

const headers = {
    "Authorization": "Bearer 1|abc123...",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());

Example response (200):


{
    "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"
            ]
        }
    }
}
 

Example response (403, No organisation access):


{
    "message": "You do not have access to this organisation."
}
 

Example response (404, No subscription):


{
    "message": "No subscription found for this organisation."
}
 

Request      

GET api/subscriptions/current

Headers

Authorization        

Example: Bearer 1|abc123...

Content-Type        

Example: application/json

Accept        

Example: application/json

Refresh subscription from Stripe

requires authentication

Fetch the organisation's current subscription from Stripe and sync to the database. Use after the user returns from the billing portal so subscription and threshold data are up to date.

Example request:
const url = new URL(
    "https://backend-staging.subsig.com/api/subscriptions/refresh"
);

const headers = {
    "Authorization": "Bearer 1|abc123...",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

fetch(url, {
    method: "POST",
    headers,
}).then(response => response.json());

Example response (200):


{
  "data": {
    "id": 1,
    "stripe_subscription_id": "sub_xxx",
    "status": "active",
    "current_period_start": "2025-12-01T00:00:00.000000Z",
    "current_period_end": "2026-01-01T00:00:00.000000Z",
    "canceled_at": null,
    "ends_at": null,
    "plan": { "id": 1, "stripe_price_id": "price_xxx", "name": "Pro - Monthly", ... }
  }
}
 

Example response (403, Not owner):


{
    "message": "You must be an organisation owner to manage subscriptions."
}
 

Example response (404, No subscription):


{
    "message": "No active subscription found for this organisation."
}
 

Request      

POST api/subscriptions/refresh

Headers

Authorization        

Example: Bearer 1|abc123...

Content-Type        

Example: application/json

Accept        

Example: application/json

Get Billing Portal URL

requires authentication

Create a Stripe Billing Portal session for the organisation's customer. The portal allows customers to manage their subscription, update payment methods, view invoices, and cancel their subscription.

Example request:
const url = new URL(
    "https://backend-staging.subsig.com/api/subscriptions/billing-portal"
);

const headers = {
    "Authorization": "Bearer 1|abc123...",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "return_url": "https:\/\/app.example.com\/settings\/billing"
};

fetch(url, {
    method: "POST",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());

Example response (200):


{
    "portal_url": "https://billing.stripe.com/p/session/..."
}
 

Example response (403, Not owner):


{
    "message": "You must be an organisation owner to manage subscriptions."
}
 

Example response (404, No subscription):


{
    "message": "No active subscription found for this organisation."
}
 

Request      

POST api/subscriptions/billing-portal

Headers

Authorization        

Example: Bearer 1|abc123...

Content-Type        

Example: application/json

Accept        

Example: application/json

Body Parameters

return_url   string     

The URL to redirect to when the customer is done. Example: https://app.example.com/settings/billing

Utilities

Validate URL Reachability

requires authentication

Validates URL format and checks if the URL is reachable.

Example request:
const url = new URL(
    "https://backend-staging.subsig.com/api/validate-url"
);

const headers = {
    "Authorization": "Bearer 1|abc123...",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "url": "https:\/\/example.com"
};

fetch(url, {
    method: "POST",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());

Example response (200):


{
    "valid": true,
    "status_code": 200,
    "message": "URL is valid and reachable"
}
 

Example response (422):


{
    "valid": false,
    "message": "Invalid URL format"
}
 

Request      

POST api/validate-url

Headers

Authorization        

Example: Bearer 1|abc123...

Content-Type        

Example: application/json

Accept        

Example: application/json

Body Parameters

url   string     

URL to validate. Example: https://example.com

Webhook Tools

Utility endpoints for webhook signature testing.

Generate webhook signature

requires authentication

Generate X-Scraper-Signature using HMAC SHA256 from the exact raw_body string you send. Use this for Swagger testing before calling scrape webhook endpoints.

Example request:
const url = new URL(
    "https://backend-staging.subsig.com/api/tools/webhook-signatures"
);

const headers = {
    "Authorization": "Bearer 1|abc123...",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "target": "social",
    "raw_body": "{\"status\":\"SUCCESS\",\"process_id\":76445,\"created_at\":\"2026-01-19 14:23:11\",\"link_url\":\"https:\/\/www.trustpilot.com\/review\/example.com\",\"data\":[]}"
};

fetch(url, {
    method: "POST",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());

Example response (200):


{
    "signature": "8e1b1b0d7c6c1b3b8c9a..."
}
 

Example response (422):


{
    "message": "The given data was invalid.",
    "errors": {
        "target": [
            "The selected target is invalid."
        ]
    }
}
 

Request      

POST api/tools/webhook-signatures

Headers

Authorization        

Example: Bearer 1|abc123...

Content-Type        

Example: application/json

Accept        

Example: application/json

Body Parameters

target   string     

Which webhook secret to use. Allowed: reviews, social, universal_keywords. Example: social

raw_body   string     

Exact raw JSON string to sign. Example: {"status":"SUCCESS","process_id":76445,"created_at":"2026-01-19 14:23:11","link_url":"https://www.trustpilot.com/review/example.com","data":[]}