MENU navbar-image

Introduction

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

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

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

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

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-api.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-api.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-api.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-api.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-api.subsig.com/api/auth/google"
);

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

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

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

Authenticate with LinkedIn

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

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

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

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

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

Change Password

requires authentication

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

Example request:
const url = new URL(
    "https://backend-api.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-api.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-api.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-api.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-api.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-api.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-api.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-api.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-api.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-api.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-api.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-api.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-api.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 - Chats

The org-wide "Chats" tab -- every AI response (execution or radar pull) across every prompt, with brand-mention/citation/web-search summary stats and a filterable, paginated, exportable list. Deliberately separate from AiVisibilityPromptAnalyticsController's responses()/ exportResponses(): those are hard-scoped to one prompt and gap-fill against that single prompt's own check cadence (AiVisibilityResponseGapFill) -- a concept that doesn't generalize across an org's many prompts with different frequencies. This controller queries the raw execution/radar rows directly with no gap-fill, and duplicates the small amount of row-mapping/CSV logic it needs rather than pulling in that per-prompt machinery.

Chats — List

requires authentication

Paginated, org-wide list of every AI response (execution or radar pull), newest first.

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

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

let body = {
    "providers": [
        "architecto"
    ],
    "date_from": "architecto",
    "date_to": "architecto",
    "topic_ids": [
        16
    ],
    "brand_ids": [
        16
    ],
    "sources": [
        "architecto"
    ],
    "search": "architecto",
    "page": 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/chats

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

providers   string[]  optional    

Optional AI model/provider filter.

date_from   string  optional    

Optional start date. Example: architecto

date_to   string  optional    

Optional end date. Example: architecto

topic_ids   integer[]  optional    

Optional topic filter.

brand_ids   integer[]  optional    

Optional "All brands" mention filter.

sources   string[]  optional    

Optional "All sources" citation-domain filter.

search   string  optional    

Optional free-text search over the response text. Example: architecto

page   integer  optional    

Optional. Default: 1. Example: 16

per_page   integer  optional    

Optional. Default: 20. Example: 16

Chats — Overview

requires authentication

The 4 header cards (Total chats, Brand mentioned, Answers with web search, Average citations), each with a period-over-period change against the filters' comparison window. brand_ids narrows the cards the same way it narrows the table below (top filter bar). Deliberately still ignores sources/search -- those remain table-only refinements.

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

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/chats/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

Chats — Export

requires authentication

CSV of the full filtered set (not just the current page).

Example request:
const url = new URL(
    "https://backend-api.subsig.com/api/organisations/660e8400-e29b-41d4-a716-446655440001/ai-visibility/chats/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/chats/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

Chats — Filters

requires authentication

Distinct citation domains ever seen across the org's chats (execution + radar), for the "All sources" dropdown. Not date-scoped -- a filter's option list is the full universe of values, not just what the current date range happens to show.

Example request:
const url = new URL(
    "https://backend-api.subsig.com/api/organisations/660e8400-e29b-41d4-a716-446655440001/ai-visibility/chats/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/chats/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

Chats — Detail

requires authentication

A single chat, keyed by the same composite id the list/export rows carry ("execution-{id}" or "radar-{id}") -- there's no single backend table a chat lives in, so the id itself says which one to look up.

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

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/chats/{chat}

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

chat   string     

The composite chat id from a list row. Example: execution-42

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-api.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

Either website or social_profiles is required (a competitor with no website can still be tracked via its social profiles, same as the own brand's onboarding modes) -- see CitationClassifier for how social_profiles feeds 'competitor' citation classification.

Example request:
const url = new URL(
    "https://backend-api.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",
    "social_profiles": [
        {
            "platform": "instagram",
            "url": "https:\/\/instagram.com\/acme"
        }
    ],
    "location_scope": "architecto",
    "location_ids": [
        16
    ],
    "track_globally": false
};

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  optional    

Competitor domain. Example: acme.com

social_profiles   object[]  optional    
platform   string  optional    

This field is required when social_profiles is present. Must not be greater than 50 characters. Example: n

url   string  optional    

This field is required when social_profiles is present. Must be a valid URL. Must not be greater than 500 characters. Example: http://crooks.biz/et-fugiat-sunt-nihil-accusantium

location_scope   string  optional    

Optional, franchise orgs only: global (default), all_locations, or chosen. Inferred from location_ids when omitted. Example: architecto

location_ids   integer[]  optional    

The specific locations to track at, when scope is chosen (or inferred chosen).

track_globally   boolean  optional    

Independent of location_scope -- whether this competitor is compared on Global (no-city) prompts too. Default true. Example: false

Update competitor

requires authentication

Either website or social_profiles is required, same rule as Add Competitor -- editing a competitor from website-tracked to social-tracked (or vice versa) is a supported mode switch, not just a field edit.

Example request:
const url = new URL(
    "https://backend-api.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",
    "social_profiles": [
        {
            "platform": "instagram",
            "url": "https:\/\/instagram.com\/acme"
        }
    ],
    "location_scope": "architecto",
    "location_ids": [
        16
    ],
    "track_globally": false
};

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  optional    

Required without social_profiles. Example: architecto

social_profiles   object[]  optional    

Required without website.

platform   string  optional    

This field is required when social_profiles is present. Must not be greater than 50 characters. Example: n

url   string  optional    

This field is required when social_profiles is present. Must be a valid URL. Must not be greater than 500 characters. Example: http://crooks.biz/et-fugiat-sunt-nihil-accusantium

location_scope   string  optional    

Optional, franchise orgs only: global (default), all_locations, or chosen. Inferred from location_ids when omitted. Example: architecto

location_ids   integer[]  optional    

The specific locations to track at, when scope is chosen (or inferred chosen).

track_globally   boolean  optional    

Independent of location_scope -- whether this competitor is compared on Global (no-city) prompts too. Default true. Example: false

Delete competitor

requires authentication

Example request:
const url = new URL(
    "https://backend-api.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

Override a competitor's store match

requires authentication

Lets an org admin correct a wrong auto-match (or manually set/clear one) for one of the organisation's own locations. Always sets is_manual_override, so MatchAiVisibilityCompetitorStoreJob never revisits this (brand, location) pair again -- see its own docblock. Upserts, same shape as the job's own persist() method, since an admin may set a match before the job has ever run for this pair.

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

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

let body = {
    "status": "matched",
    "matched_place_id": "architecto",
    "matched_name": "architecto",
    "matched_address": "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}/locations/{location}/store-match

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 competitor brand ID. Example: 16

location   integer     

The organisation's own location ID. Example: 16

Body Parameters

status   string     

matched or not_found. Example: matched

matched_place_id   string  optional    

Required when status is matched. Example: architecto

matched_name   string  optional    

Required when status is matched. Example: architecto

matched_address   string  optional    

Optional. Example: architecto

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-api.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",
    "prompt_type": "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

prompt_type   string  optional    

Optional - branded or non_branded, computed live from each prompt's text against the org's tracked brand names (see AiVisibilityPromptBrandedClassifier). Omit for no prompt-type filter. Example: architecto

Platforms Tab

requires authentication

Example request:
const url = new URL(
    "https://backend-api.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-api.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-api.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-api.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 Overview

requires authentication

Cards, retrievals trend, top domains and the domain-type breakdown for the Domains main screen — the domain-scoped counterpart of index()'s combined payload, so that screen isn't pulling URL-scoped fields it never renders.

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

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-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

Citations — URLs Overview

requires authentication

URL-scoped counterpart of domainsOverview() above, for the URLs main screen.

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

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/urls-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

Citations — Domains

requires authentication

Example request:
const url = new URL(
    "https://backend-api.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-api.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-api.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-api.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-api.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-api.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-api.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-api.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-api.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 - Fanout Queries

The org-wide "Fanout Query" tab -- every sub-query an AI engine ran behind the scenes to answer a tracked prompt, across both the active LLM pipeline (ai_visibility_fanout_queries) and the radar/google_ai_overviews pipeline (ai_visibility_radar_fanout_queries). See AiVisibilityFanoutQueryAggregateQuery's class doc for exactly which providers ever populate either table and why.

Fanout Queries: List

requires authentication

Paginated, searchable, org-wide list of distinct query fanouts, most-occurring first. Pass topic_id to page through just one topic's group of queries (the "Group by Topics" UI expands one topic at a time and lazy-loads its own page via this same endpoint) -- the topic list itself (with in-scope prompt counts) is served by topics() below.

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

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

let body = {
    "providers": [
        "architecto"
    ],
    "date_from": "architecto",
    "date_to": "architecto",
    "search": "architecto",
    "type": "architecto",
    "topic_id": 16,
    "page": 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/fanout

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

providers   string[]  optional    

Optional AI model filter.

date_from   string  optional    

Optional start date. Example: architecto

date_to   string  optional    

Optional end date. Example: architecto

search   string  optional    

Optional free-text search over the query text. Example: architecto

type   string  optional    

Optional "search" or "question" filter. Example: architecto

topic_id   integer  optional    

Optional -- narrows to one topic's queries. Example: 16

page   integer  optional    

Optional. Default: 1. Example: 16

per_page   integer  optional    

Optional. Default: 20. Example: 16

Fanout Queries: Overview

requires authentication

The 4 header cards, each with a period-over-period change against the filters' comparison window.

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

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

let body = {
    "providers": [
        "architecto"
    ],
    "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/fanout/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

Body Parameters

providers   string[]  optional    

Optional AI model filter.

date_from   string  optional    

Optional start date. Example: architecto

date_to   string  optional    

Optional end date. Example: architecto

Fanout Queries: Topic Groups

requires authentication

Powers the "Group by Topics" accordion -- only topics that actually have a fanout query in the current filtered scope, each with its distinct-prompt count (the "N Prompts" badge). Distinct from GET /ai-visibility/topics (every topic, unscoped prompt-status counts): that endpoint doesn't know about date/provider filters or fanout data at all.

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

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/fanout/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

Fanout Queries: Brand Mentioned

requires authentication

Donut-chart-shaped brand breakdown: each tracked brand's share of the executions/radar pulls behind the in-scope fanout queries that mentioned it. Every slice sums to 100%, same invariant as Visibility's share-of-voice donut.

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

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/fanout/brand-mentioned

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

Fanout Queries: Common Terms

requires authentication

Most frequent 2-3 word phrases across every fanout query's text in scope.

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

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/fanout/common-terms

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

Fanout Queries: Export

requires authentication

CSV of the full filtered, grouped query list (not just the current page).

Example request:
const url = new URL(
    "https://backend-api.subsig.com/api/organisations/660e8400-e29b-41d4-a716-446655440001/ai-visibility/fanout/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/fanout/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

AI Visibility - Locations

Settings > Account and Workspaces "Locations" list -- unlike onboarding's Step 1 (where every location is collected client-side and submitted in one batch because the organisation doesn't exist yet), the organisation already exists here, so each location is a real, immediate call.

Visibility by location

requires authentication

One visibility/SOV/rank row per location for the Home "Locations" card -- not a raw location list (see workspaceSummary() for that). Empty for an org with no locations.

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

const params = {
    "date_from": "architecto",
    "date_to": "architecto",
};
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 (401):

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

{
    "message": "Unauthenticated."
}
 

Request      

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

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

Query 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). Default: today. Example: architecto

Add a location

requires authentication

The address is resolved client-side first via the existing Google Places lookup endpoints (GooglePlacesLookupController, unchanged) -- this endpoint only persists the already-resolved fields, it never calls the Google Places API itself.

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

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

let body = {
    "name": "Times Square",
    "country_code": "US",
    "locality": "New York, United States",
    "formatted_address": "architecto",
    "google_place_id": "architecto",
    "outlet_page_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/locations

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

name   string     

Display name for the location. Example: Times Square

country_code   string     

ISO 3166-1 alpha-2. Example: US

locality   string     

", " from the resolved Place. Example: New York, United States

formatted_address   string  optional    

Full street address from the resolved Place. Example: architecto

google_place_id   string  optional    

The resolved Place's id. Example: architecto

outlet_page_url   string  optional    

Optional outlet page URL, editable regardless of what the Place's own website_uri says. Example: http://www.bailey.biz/quos-velit-et-fugiat-sunt-nihil-accusantium-harum.html

Workspace locations summary

requires authentication

Settings' "Locations" card and the multi-location gate (useWorkspaceLocations): the own brand's workspace type, every location with its tracked prompt/competitor counts, and the org-wide Global (no-location) counts. A single workspace always returns an empty locations array, regardless of how many rows exist (there shouldn't be any).

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

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}/locations

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

Add a location (frontend contract)

requires authentication

Same persistence path as store(), just the field names the dashboard's "Add location" modal actually sends (address/city/place_id/outlet_url instead of formatted_address/locality/google_place_id/outlet_page_url).

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

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

let body = {
    "name": "architecto",
    "country_code": "architecto",
    "city": "architecto",
    "address": "architecto",
    "place_id": "architecto",
    "outlet_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}/locations

Headers

Authorization        

Example: Bearer 1|abc123...

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

name   string     

Example: architecto

country_code   string     

ISO 3166-1 alpha-2. Example: architecto

city   string     

Example: architecto

address   string  optional    

Full formatted address. Example: architecto

place_id   string  optional    

Example: architecto

outlet_url   string  optional    

Example: http://www.bailey.biz/quos-velit-et-fugiat-sunt-nihil-accusantium-harum.html

Edit a location (frontend contract)

requires authentication

No equivalent existed before this -- Phase 1 only ever shipped create/list. Same field rename as storeForFrontend().

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

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

let body = {
    "name": "b",
    "country_code": "ng",
    "city": "z",
    "address": "m",
    "place_id": "i",
    "outlet_url": "http:\/\/www.okon.com\/accusantium-harum-mollitia-modi-deserunt-aut-ab"
};

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

Request      

PATCH api/organisations/{organisation_uuid}/locations/{location}

Headers

Authorization        

Example: Bearer 1|abc123...

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

organisation_uuid   string     

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

location   integer     

The location ID. Example: 16

Body Parameters

name   string     

Must not be greater than 255 characters. Example: b

country_code   string     

Must be 2 characters. Example: ng

city   string     

Must not be greater than 150 characters. Example: z

address   string  optional    

Must not be greater than 500 characters. Example: m

place_id   string  optional    

Must not be greater than 255 characters. Example: i

outlet_url   string  optional    

Must be a valid URL. Must not be greater than 500 characters. Example: http://www.okon.com/accusantium-harum-mollitia-modi-deserunt-aut-ab

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-api.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-api.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-api.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-api.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-api.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-api.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-api.subsig.com/api/organisations/2958b10e-6a80-4903-868f-79b048b5e0c4/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: 2958b10e-6a80-4903-868f-79b048b5e0c4

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-api.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-api.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-api.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-api.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-api.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-api.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,
    "tag_ids": [
        16
    ],
    "country": "NG",
    "language": "en",
    "frequency": "weekly",
    "providers": [
        "openai",
        "perplexity"
    ],
    "skip_first_run": true,
    "location_scope": "architecto",
    "location_ids": [
        16
    ]
};

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

tag_ids   integer[]  optional    
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

location_scope   string  optional    

Franchise workspaces only -- a single-location org just never sends these and every prompt stays 'global', identical to today. Same enum/validation shape as AiVisibilitySuggestionsController::accept()'s location override. Example: architecto

location_ids   integer[]  optional    

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-api.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,
    "topics": [
        "g"
    ],
    "tags": [
        [
            "z"
        ]
    ],
    "locations": [
        {
            "scope": "architecto",
            "location_ids": [
                16
            ]
        }
    ]
};

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

locations   object[]  optional    

Optional, parallel to prompts by index -- franchise onboarding only. A prompt with no entry at its index (or this key omitted entirely) stays 'global', unchanged from before this parameter existed. Kept as its own array rather than nesting into prompts.* so a plain string array (every existing/dashboard caller) stays valid.

scope   string  optional    

This field is required when locations is present. Example: architecto

location_ids   integer[]  optional    
topics   string[]  optional    

Must not be greater than 255 characters.

tags   string[][]  optional    

Must not be greater than 255 characters.

Bulk Delete Prompts

requires authentication

Deletes multiple tracked prompts at once, purging each one's historical visibility/ sentiment/citation contribution the same way a single delete does (see AiVisibilityPromptDataPurger). An ID that doesn't belong to this organisation is silently skipped rather than failing the whole batch -- same "unowned resource is invisible, not an error" posture as findPromptOrFail()'s 404 on the single-delete endpoint, just non-fatal here since a bulk selection can't assume every ID is still valid by the time it's submitted.

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

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

let body = {
    "prompt_ids": [
        16
    ]
};

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

Request      

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

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

prompt_ids   integer[]     

Array of prompt IDs to delete. Max 500 items.

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.

reprocessing is a second, independent signal folded into the same in_progress flag: true while a brand/competitor/workspace rename's ReprocessAiVisibilityOrganisationJob fan-out is still working through this org's historical executions/radar responses (see AiVisibilityReprocessingTracker). Surfaced separately from in_progress_count since it isn't a count of prompts in flight -- the frontend banner shows different copy for it.

Example request:
const url = new URL(
    "https://backend-api.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,
        "reprocessing": false,
        "in_progress_prompt_ids": [
            "101",
            "104"
        ]
    }
}
 

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-api.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-api.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-api.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,
    "tag_ids": [
        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

tag_ids   integer[]  optional    
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, tags, text, country, language, frequency and providers.

Example request:
const url = new URL(
    "https://backend-api.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-api.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-api.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-api.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-api.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-api.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",
                "location_id": null
            },
            {
                "provider": "perplexity",
                "status": "ready",
                "location_id": null
            },
            {
                "provider": "google_ai_overviews",
                "status": "ready",
                "location_id": null
            }
        ]
    }
}
 

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 - Saved Views

Named filter-state snapshots for the AI Visibility Overview page (date range/providers/brands/ topics/tags/regions/prompt_type) -- "Weekly review", "US market", etc. Shared across the organisation: any member can save, see, and delete a view, same as tags/topics. There is no persisted "Default" row -- that's the frontend's own reset state, never saved here.

List Saved Views

requires authentication

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

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": "Weekly review",
            "filters": {
                "date_preset": "last_30_days",
                "providers": [],
                "brand_ids": [],
                "topic_ids": [],
                "tag_ids": [],
                "regions": [],
                "prompt_type": null
            },
            "created_at": "2026-10-08T10:00:00.000000Z",
            "updated_at": "2026-10-08T10:00:00.000000Z"
        }
    ]
}
 

Request      

GET api/organisations/{organisation_uuid}/ai-visibility/saved-views

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

Save Current View

requires authentication

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

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

let body = {
    "name": "Weekly review",
    "filters": []
};

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

Example response (201):


{
    "data": {
        "id": 1,
        "name": "Weekly review",
        "filters": {
            "date_preset": "last_30_days",
            "providers": [],
            "brand_ids": [],
            "topic_ids": [],
            "tag_ids": [],
            "regions": [],
            "prompt_type": null
        },
        "created_at": "2026-10-08T10:00:00.000000Z",
        "updated_at": "2026-10-08T10:00:00.000000Z"
    }
}
 

Example response (422):


{
    "message": "The name field is required. (and 1 more error)",
    "errors": {
        "name": [
            "\"Weekly review\" already exists. Pick another name or use the existing view."
        ]
    }
}
 

Request      

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

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     

Example: Weekly review

filters   object     

Whatever the Overview page's filter bar currently holds -- stored and returned verbatim.

Delete Saved View

requires authentication

Owner-only -- unlike creating a view (Owner or Member), deleting one is restricted to organisation owners, regardless of who created it. See OrganisationPolicy::deleteAiVisibilitySavedView().

Example request:
const url = new URL(
    "https://backend-api.subsig.com/api/organisations/660e8400-e29b-41d4-a716-446655440001/ai-visibility/saved-views/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 (403):


{
    "message": "You must be an organisation owner to delete a saved view."
}
 

Example response (404):


{
    "message": "Saved view not found."
}
 

Request      

DELETE api/organisations/{organisation_uuid}/ai-visibility/saved-views/{saved_view}

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

saved_view   integer     

The saved view 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 every prompt suggestion with status completed for the organisation, ordered by source (radar first), then volume descending, then most recent. This backs the full "Suggested" tab (search + accept/dismiss over the whole set), so it is not capped the way the onboarding preview endpoints are -- meta.total always equals count(data).

Example request:
const url = new URL(
    "https://backend-api.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"
        }
    ],
    "meta": {
        "total": 1
    }
}
 

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-api.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-api.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": false,
    "location_scope": "architecto",
    "location_ids": [
        16
    ]
};

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: false

location_scope   string  optional    

Lets the user override the suggestion's pre-tagged default scope before accepting -- falls back to $row->location_scope/$row->location_id below when omitted. Example: architecto

location_ids   integer[]  optional    

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-api.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-api.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": "...",
                "category": "feature",
                "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-api.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-api.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-api.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-api.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-api.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",
};

let body = {
    "location_scope": "architecto",
    "location_ids": [
        16
    ]
};

fetch(url, {
    method: "POST",
    headers,
    body: JSON.stringify(body),
}).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

Body Parameters

location_scope   string  optional    

Example: architecto

location_ids   integer[]  optional    

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-api.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-api.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",
            "category": "feature",
            "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-api.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-api.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 - Tags

APIs for managing tags used to group AI Visibility prompts within an organisation. Unlike ai_visibility_topics (one nullable topic per prompt), a prompt can carry several tags -- see ai_visibility_prompt_tags.

List Tags

requires authentication

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

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": "Comparison",
            "color": "blue",
            "created_at": "2026-06-05T10:00:00.000000Z",
            "updated_at": "2026-06-05T10:00:00.000000Z",
            "prompts_connected": 14
        }
    ]
}
 

Request      

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

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 Tag

requires authentication

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

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

let body = {
    "name": "Comparison",
    "color": "blue"
};

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

Example response (201):


{
    "data": {
        "id": 1,
        "name": "Comparison",
        "color": "blue",
        "created_at": "2026-06-05T10:00:00.000000Z",
        "updated_at": "2026-06-05T10:00:00.000000Z"
    }
}
 

Example response (422):


{
    "message": "The name field is required. (and 1 more error)",
    "errors": {
        "name": [
            "\"Comparison\" already exists. Pick another name or use the existing tag."
        ]
    }
}
 

Request      

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

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     

Tag name. Example: Comparison

color   string  optional    

Optional swatch -- one of neutral, blue, emerald, amber, violet, rose. Defaults to neutral. Example: blue

Update Tag

requires authentication

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

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

let body = {
    "name": "Comparison",
    "color": "blue"
};

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

Example response (200):


{
    "data": {
        "id": 1,
        "name": "Comparison",
        "color": "blue",
        "created_at": "2026-06-05T10:00:00.000000Z",
        "updated_at": "2026-06-05T10:00:00.000000Z"
    }
}
 

Example response (404):


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

Request      

PATCH api/organisations/{organisation_uuid}/ai-visibility/tags/{tag}

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

tag   integer     

The tag ID. Example: 1

Body Parameters

name   string  optional    

Optional tag name. Example: Comparison

color   string  optional    

Optional swatch -- one of neutral, blue, emerald, amber, violet, rose. Example: blue

Delete Tag

requires authentication

Removes the tag from every prompt it's attached to (ai_visibility_prompt_tags cascades) -- the prompts themselves, and their execution history/results, are untouched. Unlike AiVisibilityTopicController::destroy(), this is never blocked by active prompts: a tag is a loose label, not the single required grouping a topic is.

Example request:
const url = new URL(
    "https://backend-api.subsig.com/api/organisations/660e8400-e29b-41d4-a716-446655440001/ai-visibility/tags/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": "Tag not found."
}
 

Request      

DELETE api/organisations/{organisation_uuid}/ai-visibility/tags/{tag}

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

tag   integer     

The tag 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-api.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
        },
        {
            "id": null,
            "name": "Unassigned",
            "description": null,
            "created_at": null,
            "updated_at": null,
            "active_count": 2,
            "inactive_count": 0,
            "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-api.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-api.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-api.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

AI Visibility — Addon Access

GET /organisations/{organisation_uuid}/ai-visibility/addon-access

requires authentication

Example request:
const url = new URL(
    "https://backend-api.subsig.com/api/organisations/2958b10e-6a80-4903-868f-79b048b5e0c4/ai-visibility/addon-access"
);

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):


{
    "data": {
        "enabled_addon_kinds": [
            "grok"
        ],
        "purchased_addon_kinds": [
            "grok",
            "gemini"
        ],
        "owned_addon_kinds": []
    }
}
 

Request      

GET api/organisations/{organisation_uuid}/ai-visibility/addon-access

Headers

Authorization        

Example: Bearer 1|abc123...

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

organisation_uuid   string     

Example: 2958b10e-6a80-4903-868f-79b048b5e0c4

POST /organisations/{organisation_uuid}/ai-visibility/addon-access

requires authentication

Example request:
const url = new URL(
    "https://backend-api.subsig.com/api/organisations/2958b10e-6a80-4903-868f-79b048b5e0c4/ai-visibility/addon-access"
);

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

let body = {
    "kind": "architecto",
    "enabled": false
};

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

Example response (200, success):


{
    "data": {
        "enabled_addon_kinds": [
            "grok"
        ],
        "purchased_addon_kinds": [
            "grok",
            "gemini"
        ],
        "owned_addon_kinds": []
    }
}
 

Request      

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

Headers

Authorization        

Example: Bearer 1|abc123...

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

organisation_uuid   string     

Example: 2958b10e-6a80-4903-868f-79b048b5e0c4

Body Parameters

kind   string     

One of grok, deepseek, anthropic, gemini, google_ai_mode. Example: architecto

enabled   boolean     

Example: false

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-api.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-api.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-api.subsig.com/api/api-keys/36010381-2db5-4553-8459-ec8e089eb331/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: 36010381-2db5-4553-8459-ec8e089eb331

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-api.subsig.com/api/api-keys/36010381-2db5-4553-8459-ec8e089eb331/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: 36010381-2db5-4553-8459-ec8e089eb331

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-api.subsig.com/api/api-keys/36010381-2db5-4553-8459-ec8e089eb331/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: 36010381-2db5-4553-8459-ec8e089eb331

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-api.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-api.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-api.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-api.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-api.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-api.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-api.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": false
};

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: false

Send a test webhook (manual trigger).

Example request:
const url = new URL(
    "https://backend-api.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-api.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-api.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": "bearer",
    "auth_token": "k",
    "secret": "h",
    "is_active": false
};

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: bearer

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

Show a single outbound webhook.

Example request:
const url = new URL(
    "https://backend-api.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-api.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": "bearer",
    "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: bearer

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-api.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-api.subsig.com/api/organisations/2958b10e-6a80-4903-868f-79b048b5e0c4/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: 2958b10e-6a80-4903-868f-79b048b5e0c4

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-api.subsig.com/api/organisations/2958b10e-6a80-4903-868f-79b048b5e0c4/agents/architecto/suggested-prompts"
);

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

let body = {
    "brand_id": 16,
    "timeframe": "7d",
    "data_source": [
        "google_ai_mode"
    ]
};

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: 2958b10e-6a80-4903-868f-79b048b5e0c4

key   string     

Example: architecto

Body Parameters

brand_id   integer     

Example: 16

timeframe   string  optional    

Example: 7d

Must be one of:
  • 7d
  • 30d
  • 90d
data_source   string[]  optional    
Must be one of:
  • openai
  • perplexity
  • gemini
  • grok
  • deepseek
  • anthropic
  • google_ai_overviews
  • bing_copilot
  • google_ai_mode
  • ai_visibility

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

Example request:
const url = new URL(
    "https://backend-api.subsig.com/api/organisations/2958b10e-6a80-4903-868f-79b048b5e0c4/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: 2958b10e-6a80-4903-868f-79b048b5e0c4

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-api.subsig.com/api/organisations/2958b10e-6a80-4903-868f-79b048b5e0c4/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: 2958b10e-6a80-4903-868f-79b048b5e0c4

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-api.subsig.com/api/organisations/2958b10e-6a80-4903-868f-79b048b5e0c4/agent-runs/17"
);

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: 2958b10e-6a80-4903-868f-79b048b5e0c4

run_id   integer     

The ID of the run. Example: 17

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-api.subsig.com/api/organisations/2958b10e-6a80-4903-868f-79b048b5e0c4/agent-runs/17/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: 2958b10e-6a80-4903-868f-79b048b5e0c4

run_id   integer     

The ID of the run. Example: 17

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-api.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-api.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-api.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-api.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: 59236191-44ef-4ab7-b2c7-acae8c37d895
vary: Origin
 

{
    "error": {
        "code": "unauthorized",
        "message": "Unauthorized.",
        "docs_url": "https://docs.subsig.com/errors/unauthorized",
        "request_id": "59236191-44ef-4ab7-b2c7-acae8c37d895"
    }
}
 

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-api.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: 32d32f0d-b949-47a7-b4ee-706454e30a96
vary: Origin
 

{
    "error": {
        "code": "unauthorized",
        "message": "Unauthorized.",
        "docs_url": "https://docs.subsig.com/errors/unauthorized",
        "request_id": "32d32f0d-b949-47a7-b4ee-706454e30a96"
    }
}
 

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-api.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: fb5734a9-b59c-444f-9624-0e1ce910644b
vary: Origin
 

{
    "error": {
        "code": "unauthorized",
        "message": "Unauthorized.",
        "docs_url": "https://docs.subsig.com/errors/unauthorized",
        "request_id": "fb5734a9-b59c-444f-9624-0e1ce910644b"
    }
}
 

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-api.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: 9603b0bc-dbc0-4841-8b72-6ea70e36af5c
vary: Origin
 

{
    "error": {
        "code": "unauthorized",
        "message": "Unauthorized.",
        "docs_url": "https://docs.subsig.com/errors/unauthorized",
        "request_id": "9603b0bc-dbc0-4841-8b72-6ea70e36af5c"
    }
}
 

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-api.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: 93ef386b-6c04-42c5-a1d6-7308b6e284a8
vary: Origin
 

{
    "error": {
        "code": "unauthorized",
        "message": "Unauthorized.",
        "docs_url": "https://docs.subsig.com/errors/unauthorized",
        "request_id": "93ef386b-6c04-42c5-a1d6-7308b6e284a8"
    }
}
 

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-api.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: 7da1cf55-1913-4403-a15c-f6d2c6f74430
vary: Origin
 

{
    "error": {
        "code": "unauthorized",
        "message": "Unauthorized.",
        "docs_url": "https://docs.subsig.com/errors/unauthorized",
        "request_id": "7da1cf55-1913-4403-a15c-f6d2c6f74430"
    }
}
 

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-api.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: 58bd3bf8-f29f-4ad3-8e11-fd98fadcc9cb
vary: Origin
 

{
    "error": {
        "code": "unauthorized",
        "message": "Unauthorized.",
        "docs_url": "https://docs.subsig.com/errors/unauthorized",
        "request_id": "58bd3bf8-f29f-4ad3-8e11-fd98fadcc9cb"
    }
}
 

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-api.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: 926b8bec-4859-4ab6-86cf-73df513401e9
vary: Origin
 

{
    "error": {
        "code": "unauthorized",
        "message": "Unauthorized.",
        "docs_url": "https://docs.subsig.com/errors/unauthorized",
        "request_id": "926b8bec-4859-4ab6-86cf-73df513401e9"
    }
}
 

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-api.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: 67e50fff-e461-44b8-9668-556678c03ebf
vary: Origin
 

{
    "error": {
        "code": "unauthorized",
        "message": "Unauthorized.",
        "docs_url": "https://docs.subsig.com/errors/unauthorized",
        "request_id": "67e50fff-e461-44b8-9668-556678c03ebf"
    }
}
 

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-api.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: 865aa301-3553-4b57-a479-282b6c97e039
vary: Origin
 

{
    "error": {
        "code": "unauthorized",
        "message": "Unauthorized.",
        "docs_url": "https://docs.subsig.com/errors/unauthorized",
        "request_id": "865aa301-3553-4b57-a479-282b6c97e039"
    }
}
 

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-api.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: cd967501-0453-4af9-b139-734fc11d49df
vary: Origin
 

{
    "error": {
        "code": "unauthorized",
        "message": "Unauthorized.",
        "docs_url": "https://docs.subsig.com/errors/unauthorized",
        "request_id": "cd967501-0453-4af9-b139-734fc11d49df"
    }
}
 

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-api.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: 8fd5e405-1d4d-4d4f-8684-b488fc96ef54
vary: Origin
 

{
    "error": {
        "code": "unauthorized",
        "message": "Unauthorized.",
        "docs_url": "https://docs.subsig.com/errors/unauthorized",
        "request_id": "8fd5e405-1d4d-4d4f-8684-b488fc96ef54"
    }
}
 

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-api.subsig.com/api/v1/agent-reports/17"
);

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: 14cea592-93af-4fa1-9aa2-b895aa01179c
vary: Origin
 

{
    "error": {
        "code": "unauthorized",
        "message": "Unauthorized.",
        "docs_url": "https://docs.subsig.com/errors/unauthorized",
        "request_id": "14cea592-93af-4fa1-9aa2-b895aa01179c"
    }
}
 

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: 17

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-api.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-api.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-api.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-api.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-api.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-api.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-api.subsig.com/api/organisations/2958b10e-6a80-4903-868f-79b048b5e0c4/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: 2958b10e-6a80-4903-868f-79b048b5e0c4

The Recommendations page's top stat-card row (High priority / Open recommendations / Completed this month / AI readiness score). Pools Off-Page's Engage + Editorial cards and On-Page's cards (Technical isn't included: its checks are a pass/fail audit, not Done/Decline task-state cards -- see recommendationsForTab()'s 'technical' branch). Always a full, uncapped pull (limit: PHP_INT_MAX) since every card needs counting, not just the first page a tab would normally show.

Example request:
const url = new URL(
    "https://backend-api.subsig.com/api/organisations/2958b10e-6a80-4903-868f-79b048b5e0c4/agents/recommendations/summary"
);

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/summary

Headers

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

organisation_uuid   string     

Example: 2958b10e-6a80-4903-868f-79b048b5e0c4

Off-Page/On-Page/Technical recommendation tabs -- see docs/zerorank-style-recommendations-plan.md. Separate from recommendations() above: that endpoint serves the existing flat trend-insight list (visibility drop, content-gap, etc), this one serves the new tabbed, task-tracked cards. 'off_page' (Phase 2) and 'on_page' (Phase 3) are built; 'technical' is reserved by the route's tab constraint but returns 501 until Phase 4 lands, rather than a misleading empty list.

Example request:
const url = new URL(
    "https://backend-api.subsig.com/api/organisations/2958b10e-6a80-4903-868f-79b048b5e0c4/agents/recommendations/off_page|on_page|technical"
);

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

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

Example response (404):

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

{
    "message": "The route api/organisations/2958b10e-6a80-4903-868f-79b048b5e0c4/agents/recommendations/off_page|on_page|technical could not be found."
}
 

Request      

GET api/organisations/{organisation_uuid}/agents/recommendations/{tab}

Headers

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

organisation_uuid   string     

Example: 2958b10e-6a80-4903-868f-79b048b5e0c4

tab   string     

Example: off_page|on_page|technical

ZeroRank's per-domain "Sources" table, shown when a card in the Off-Page sidebar is selected. Same cache/501 shape as recommendationsForTab() above.

Example request:
const url = new URL(
    "https://backend-api.subsig.com/api/organisations/2958b10e-6a80-4903-868f-79b048b5e0c4/agents/recommendations/off_page|on_page|technical/sources"
);

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

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

Example response (404):

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

{
    "message": "The route api/organisations/2958b10e-6a80-4903-868f-79b048b5e0c4/agents/recommendations/off_page|on_page|technical/sources could not be found."
}
 

Request      

GET api/organisations/{organisation_uuid}/agents/recommendations/{tab}/sources

Headers

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

organisation_uuid   string     

Example: 2958b10e-6a80-4903-868f-79b048b5e0c4

tab   string     

Example: off_page|on_page|technical

One domain's actionable cards (thread-specific, plus a subreddit-specific one for reddit.com) -- see AiVisibilityOffPageRecommendationService::cardsForDomain(). Same cache/501 shape as recommendationSources() above.

Example request:
const url = new URL(
    "https://backend-api.subsig.com/api/organisations/2958b10e-6a80-4903-868f-79b048b5e0c4/agents/recommendations/off_page|on_page|technical/action-cards"
);

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

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

Example response (404):

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

{
    "message": "The route api/organisations/2958b10e-6a80-4903-868f-79b048b5e0c4/agents/recommendations/off_page|on_page|technical/action-cards could not be found."
}
 

Request      

GET api/organisations/{organisation_uuid}/agents/recommendations/{tab}/action-cards

Headers

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

organisation_uuid   string     

Example: 2958b10e-6a80-4903-868f-79b048b5e0c4

tab   string     

Example: off_page|on_page|technical

ZeroRank's "Top Subreddits" panel -- only ever non-empty for a reddit.com domain. Same cache/501 shape as recommendationSources() above.

Example request:
const url = new URL(
    "https://backend-api.subsig.com/api/organisations/2958b10e-6a80-4903-868f-79b048b5e0c4/agents/recommendations/off_page|on_page|technical/subreddits"
);

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

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

Example response (404):

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

{
    "message": "The route api/organisations/2958b10e-6a80-4903-868f-79b048b5e0c4/agents/recommendations/off_page|on_page|technical/subreddits could not be found."
}
 

Request      

GET api/organisations/{organisation_uuid}/agents/recommendations/{tab}/subreddits

Headers

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

organisation_uuid   string     

Example: 2958b10e-6a80-4903-868f-79b048b5e0c4

tab   string     

Example: off_page|on_page|technical

"Who AI recommends from X" stacked bar -- own-brand vs named competitors vs "Others", for one Off-Page domain. Off-Page only (On-Page/Technical have no equivalent chart in the design), same cache/501 shape as recommendationSubreddits() above.

Example request:
const url = new URL(
    "https://backend-api.subsig.com/api/organisations/2958b10e-6a80-4903-868f-79b048b5e0c4/agents/recommendations/off_page|on_page|technical/who-recommends"
);

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

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

Example response (404):

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

{
    "message": "The route api/organisations/2958b10e-6a80-4903-868f-79b048b5e0c4/agents/recommendations/off_page|on_page|technical/who-recommends could not be found."
}
 

Request      

GET api/organisations/{organisation_uuid}/agents/recommendations/{tab}/who-recommends

Headers

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

organisation_uuid   string     

Example: 2958b10e-6a80-4903-868f-79b048b5e0c4

tab   string     

Example: off_page|on_page|technical

On-Page's "Phrases, themes and domains" panel -- bundles all three into one response since the frontend always needs them together for the selected content type. Off-Page/Technical have no equivalent, same 501 shape as the other tab-scoped endpoints.

Example request:
const url = new URL(
    "https://backend-api.subsig.com/api/organisations/2958b10e-6a80-4903-868f-79b048b5e0c4/agents/recommendations/off_page|on_page|technical/content-insights"
);

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

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

Example response (404):

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

{
    "message": "The route api/organisations/2958b10e-6a80-4903-868f-79b048b5e0c4/agents/recommendations/off_page|on_page|technical/content-insights could not be found."
}
 

Request      

GET api/organisations/{organisation_uuid}/agents/recommendations/{tab}/content-insights

Headers

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

organisation_uuid   string     

Example: 2958b10e-6a80-4903-868f-79b048b5e0c4

tab   string     

Example: off_page|on_page|technical

Technical tab's "Re-check" button -- forces a fresh site audit, bypassing the 24h cache.

Write-shaped (an on-demand, potentially slow re-crawl of the org's own site via PageSpeed Insights + several Http::get() calls), so requires manage access like updateRecommendation TaskStatus() below, not just view access.

Example request:
const url = new URL(
    "https://backend-api.subsig.com/api/organisations/2958b10e-6a80-4903-868f-79b048b5e0c4/agents/recommendations/off_page|on_page|technical/recheck"
);

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/recommendations/{tab}/recheck

Headers

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

organisation_uuid   string     

Example: 2958b10e-6a80-4903-868f-79b048b5e0c4

tab   string     

Example: off_page|on_page|technical

Marks one Off-Page/On-Page/Technical recommendation card Done/Declined/Todo -- see AiVisibilityRecommendationTaskStateService's docblock for why "todo" deletes rather than stores a row. Write action (changes state other users with access to this org would also see), so this requires manage access, not just view access.

Example request:
const url = new URL(
    "https://backend-api.subsig.com/api/organisations/2958b10e-6a80-4903-868f-79b048b5e0c4/agents/recommendations/off_page|on_page|technical/status"
);

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

let body = {
    "item_key": "b",
    "status": "architecto"
};

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

Request      

PATCH api/organisations/{organisation_uuid}/agents/recommendations/{tab}/status

Headers

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

organisation_uuid   string     

Example: 2958b10e-6a80-4903-868f-79b048b5e0c4

tab   string     

Example: off_page|on_page|technical

Body Parameters

item_key   string     

Must not be greater than 255 characters. Example: b

status   string     

Example: architecto

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-api.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-api.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-api.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-api.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-api.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-api.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-api.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",
    "identity_source": "website",
    "product_website": "https:\/\/acme.com",
    "product_logo": "https:\/\/cdn.brandfetch.io\/acme.com\/fallback\/lettermark\/icon?c=BRANDFETCH_CLIENT_ID",
    "social_profiles": [
        {
            "platform": "instagram",
            "url": "https:\/\/instagram.com\/acme"
        }
    ],
    "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

identity_source   string  optional    

How the brand was identified: website (default) or social. Example: website

product_website   string  optional    

The product website URL. Required (nullable) when identity_source is website; must be omitted when social. 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

social_profiles   object[]  optional    

Required when identity_source is social; must be omitted otherwise.

platform   string     

Free-form platform name, e.g. instagram, linkedin, x, facebook. Example: instagram

url   string     

The profile URL. Example: https://instagram.com/acme

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-api.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",
    "identity_source": "website",
    "product_website": "https:\/\/acme.com",
    "product_logo": "https:\/\/cdn.brandfetch.io\/acme.com\/fallback\/lettermark\/icon?c=BRANDFETCH_CLIENT_ID",
    "social_profiles": [
        {
            "platform": "instagram",
            "url": "https:\/\/instagram.com\/acme"
        }
    ],
    "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

identity_source   string  optional    

How the brand was identified: website (default) or social. Example: website

product_website   string  optional    

The product website URL. Required (nullable) when identity_source is website; must be omitted when social. 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

social_profiles   object[]  optional    

Required when identity_source is social; must be omitted otherwise.

platform   string     

Free-form platform name, e.g. instagram, linkedin, x, facebook. Example: instagram

url   string     

The profile URL. Example: https://instagram.com/acme

threshold_consumption_date   string  optional    

optional Threshold consumption preset; recalculates stored date when provided. Example: last_6_months

requires authentication

Returns candidate Places for a picker UI, given a free-text query (e.g. a business name).

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

const params = {
    "query": "Aura Bangkok Clinic Asok",
    "region": "TH",
};
Object.keys(params)
    .forEach(key => url.searchParams.append(key, params[key]));

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

let body = {
    "query": "b",
    "region": "ng"
};

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

Example response (200):


{
    "data": [
        {
            "place_id": "ChIJ...",
            "name": "Aura Bangkok Clinic Asok",
            "formatted_address": "Exchange Tower, Bangkok, Thailand",
            "types": [
                "medical_clinic"
            ]
        }
    ]
}
 

Resolve a pasted Google Maps/Place link

requires authentication

For the "paste a link" flow (same input as Instagram/LinkedIn/etc.) -- detects a Google Maps/Place URL, resolves it to a real business via Text Search + Place Details, and returns the same shape as show() for the frontend to autofill Brand Name / switch to Website mode.

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

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

let body = {
    "url": "https:\/\/www.google.com\/maps\/place\/Aura+Bangkok+Clinic\/@13.7,100.5,17z"
};

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

Example response (200):


{
    "data": {
        "place_id": "ChIJ...",
        "name": "Aura Bangkok Clinic Asok",
        "primary_type": "medical_clinic",
        "types": [
            "medical_clinic"
        ],
        "formatted_address": "Exchange Tower, Bangkok, Thailand",
        "website_uri": "https://aurabangkokclinic.com",
        "photo_names": []
    }
}
 

Example response (404):


{
    "message": "Could not resolve a business from that link."
}
 

Request      

POST api/onboarding/google-places/resolve-url

Headers

Authorization        

Example: Bearer 1|abc123...

Content-Type        

Example: application/json

Accept        

Example: application/json

Body Parameters

url   string     

The pasted URL. Example: https://www.google.com/maps/place/Aura+Bangkok+Clinic/@13.7,100.5,17z

Address autocomplete for the "Add location" modal

requires authentication

Search-as-you-type predictions for a location's physical address, scoped to one country. Distinct from search() above (which finds a whole business listing) -- this is purely an address/place-name typeahead for onboarding's multi-location "Add location" flow.

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

const params = {
    "input": "350 5th Ave",
    "country": "US",
    "session_token": "3fa4159e-...",
};
Object.keys(params)
    .forEach(key => url.searchParams.append(key, params[key]));

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

let body = {
    "input": "b",
    "country": "ng",
    "session_token": "z"
};

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

Example response (200):


{
    "data": [
        {
            "place_id": "ChIJ...",
            "primary_text": "350 5th Ave",
            "secondary_text": "New York, NY, USA"
        }
    ]
}
 

Request      

GET api/onboarding/google-places/autocomplete

Headers

Authorization        

Example: Bearer 1|abc123...

Content-Type        

Example: application/json

Accept        

Example: application/json

Query Parameters

input   string     

The partial address/place text typed so far. Example: 350 5th Ave

country   string     

ISO 3166-1 alpha-2 country code to scope results to. Example: US

session_token   string     

Caller-generated token shared across this typeahead session and the one details() call that follows it (Google's session-based billing). Example: 3fa4159e-...

Body Parameters

input   string     

Must not be greater than 200 characters. Example: b

country   string     

Must be 2 characters. Example: ng

session_token   string     

Must not be greater than 100 characters. Example: z

Address details for a picked autocomplete suggestion

requires authentication

Resolves one chosen autocomplete prediction to its full address/coordinates, for the onboarding "Add location" modal. Must be called with the SAME session_token as the autocomplete() call that produced $placeId.

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

const params = {
    "place_id": "ChIJ...",
    "session_token": "3fa4159e-...",
};
Object.keys(params)
    .forEach(key => url.searchParams.append(key, params[key]));

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

let body = {
    "place_id": "b",
    "session_token": "n"
};

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

Example response (200):


{
    "data": {
        "place_id": "ChIJ...",
        "formatted_address": "350 5th Ave, New York, NY 10118, USA",
        "city": "New York",
        "region": "New York",
        "country_code": "US",
        "lat": 40.7484,
        "lng": -73.9857
    }
}
 

Request      

GET api/onboarding/google-places/details

Headers

Authorization        

Example: Bearer 1|abc123...

Content-Type        

Example: application/json

Accept        

Example: application/json

Query Parameters

place_id   string     

The Place ID from the autocomplete results. Example: ChIJ...

session_token   string     

The same session token used for the preceding autocomplete() call. Example: 3fa4159e-...

Body Parameters

place_id   string     

Must not be greater than 300 characters. Example: b

session_token   string     

Must not be greater than 100 characters. Example: n

Get Google Business/Maps listing details

requires authentication

Resolves one chosen Place's details for prefilling the onboarding form.

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

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": {
        "place_id": "ChIJ...",
        "name": "Aura Bangkok Clinic Asok",
        "primary_type": "medical_clinic",
        "types": [
            "medical_clinic"
        ],
        "formatted_address": "Exchange Tower, Bangkok, Thailand",
        "website_uri": "https://aurabangkokclinic.com",
        "photo_names": []
    }
}
 

Request      

GET api/onboarding/google-places/{placeId}

Headers

Authorization        

Example: Bearer 1|abc123...

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

placeId   string     

The Place ID from the search results. Example: ChIJ...

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-api.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"
        }
    ],
    "provider_region_exceptions": {
        "anthropic": [
            "IE",
            "LU"
        ],
        "deepseek": [
            "IE",
            "LU"
        ]
    }
}
 

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-api.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-api.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-api.subsig.com/api/organisations/2958b10e-6a80-4903-868f-79b048b5e0c4"
);

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: 2958b10e-6a80-4903-868f-79b048b5e0c4

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-api.subsig.com/api/organisations/2958b10e-6a80-4903-868f-79b048b5e0c4"
);

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

let body = {
    "name": "Acme Corp",
    "identity_source": "architecto",
    "website": "https:\/\/acme.com",
    "social_profiles": [
        {
            "platform": "instagram",
            "url": "https:\/\/instagram.com\/acme"
        }
    ]
};

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: 2958b10e-6a80-4903-868f-79b048b5e0c4

organisation   string     

The organisation UUID. Example: 550e8400-e29b-41d4-a716-446655440000

Body Parameters

name   string     

The organisation name. Example: Acme Corp

identity_source   string  optional    

How the brand is identified: website (default) or social. Example: architecto

website   string  optional    

Required (nullable) when identity_source is website; must be omitted when social. Example: https://acme.com

social_profiles   object[]  optional    

Required when identity_source is social; must be omitted otherwise.

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-api.subsig.com/api/organisations/2958b10e-6a80-4903-868f-79b048b5e0c4"
);

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: 2958b10e-6a80-4903-868f-79b048b5e0c4

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-api.subsig.com/api/organisations/2958b10e-6a80-4903-868f-79b048b5e0c4/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: 2958b10e-6a80-4903-868f-79b048b5e0c4

organisation   string     

The organisation UUID. Example: 550e8400-e29b-41d4-a716-446655440000

Platforms

APIs for managing platforms

List All Platforms

requires authentication

Get all platforms.

Example request:
const url = new URL(
    "https://backend-api.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-api.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-api.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-api.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

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-api.subsig.com/api/organisations/2958b10e-6a80-4903-868f-79b048b5e0c4/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: 2958b10e-6a80-4903-868f-79b048b5e0c4

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-api.subsig.com/api/organisations/2958b10e-6a80-4903-868f-79b048b5e0c4/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: 2958b10e-6a80-4903-868f-79b048b5e0c4

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-api.subsig.com/api/organisations/2958b10e-6a80-4903-868f-79b048b5e0c4/projects/b2b0131c-f2cb-42fa-ad1e-00605ce7c898"
);

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: 2958b10e-6a80-4903-868f-79b048b5e0c4

uuid   string     

Example: b2b0131c-f2cb-42fa-ad1e-00605ce7c898

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-api.subsig.com/api/organisations/2958b10e-6a80-4903-868f-79b048b5e0c4/projects/b2b0131c-f2cb-42fa-ad1e-00605ce7c898"
);

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: 2958b10e-6a80-4903-868f-79b048b5e0c4

uuid   string     

Example: b2b0131c-f2cb-42fa-ad1e-00605ce7c898

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-api.subsig.com/api/organisations/2958b10e-6a80-4903-868f-79b048b5e0c4/projects/b2b0131c-f2cb-42fa-ad1e-00605ce7c898"
);

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: 2958b10e-6a80-4903-868f-79b048b5e0c4

uuid   string     

Example: b2b0131c-f2cb-42fa-ad1e-00605ce7c898

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-api.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://app.subsig.com/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://app.subsig.com/dashboard/notification/overview?slack_error=architecto'" />

        <title>Redirecting to https://app.subsig.com/dashboard/notification/overview?slack_error=architecto</title>
    </head>
    <body>
        Redirecting to <a href="https://app.subsig.com/dashboard/notification/overview?slack_error=architecto">https://app.subsig.com/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-api.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-api.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-api.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-api.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-api.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-api.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-api.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-api.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-api.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-api.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-api.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-api.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-api.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-api.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-api.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-api.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":[]}