Introduction
This documentation aims to provide all the information you need to work with our API.
Mail test server: https://backend-staging.subsig.com:8025 (HTTP only)
Postman collection: https://backend-staging.subsig.com/docs.postman
OpenAPI spec: https://backend-staging.subsig.com/docs.openapi
Test Users (Development Only)
The following test accounts are available for testing purposes:
| Password | Organisation | Role | |
|---|---|---|---|
admin@acme.com |
password |
Acme Corporation | admin |
admin@acme.com |
password |
Acme Corporation | organisation_owner |
member@acme.com |
password |
Acme Corporation | organisation_member |
project@acme.com |
password |
Acme Corporation | project_member (CRM & Analytics only) |
alice@techstart.com |
password |
TechStart Inc | organisation_owner |
Note: admin@acme.com is also a member of TechStart Inc for testing multi-organisation switching.
Authenticating requests
To authenticate requests, include an Authorization header with the value "Bearer 1|abc123...".
All authenticated endpoints are marked with a requires authentication badge in the documentation below.
You can retrieve your token by visiting your dashboard and clicking Generate API token.
Registration
Create a new user account to access the application.
Create Account
Register a new user account. After successful registration, the user will be automatically logged in and redirected to the dashboard.
Example request:
const url = new URL(
"https://backend-staging.subsig.com/register"
);
const headers = {
"Content-Type": "application/json",
"Accept": "application/json",
};
let body = {
"name": "John Doe",
"email": "john@example.com",
"password": "SecurePass123!",
"password_confirmation": "SecurePass123!"
};
fetch(url, {
method: "POST",
headers,
body: JSON.stringify(body),
}).then(response => response.json());Example response (201, Account created. User logged in and redirected.):
Example response (422, Validation error.):
{
"message": "The email has already been taken.",
"errors": {
"email": [
"The email has already been taken."
]
}
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Authentication
APIs for user authentication
Create API Token
Generate an API token for authenticated requests. Requires a verified email address.
Example request:
const url = new URL(
"https://backend-staging.subsig.com/api/sanctum/token"
);
const headers = {
"Content-Type": "application/json",
"Accept": "application/json",
};
let body = {
"email": "john@example.com",
"password": "SecurePass123!"
};
fetch(url, {
method: "POST",
headers,
body: JSON.stringify(body),
}).then(response => response.json());Example response (200):
{
"token": "1|abc123..."
}
Example response (201, Email not verified):
{
"message": "Registration successful. Please check your email to verify your account.",
"redirect_link": "https://frontend.example.com/register?email=john%40example.com",
"needsEmailVerification": true
}
Example response (422, Invalid credentials):
{
"message": "The provided credentials are incorrect.",
"errors": {
"email": [
"The provided credentials are incorrect."
]
}
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Verify Email
Verify user's email address using the 4-digit code sent via email. Returns an API token on successful verification. Verification link is sent via email. /verify-email?code=1234&email=john@example.com
Example request:
const url = new URL(
"https://backend-staging.subsig.com/api/email/verify"
);
const headers = {
"Content-Type": "application/json",
"Accept": "application/json",
};
let body = {
"email": "john@example.com",
"code": "1234"
};
fetch(url, {
method: "POST",
headers,
body: JSON.stringify(body),
}).then(response => response.json());Example response (200):
{
"message": "Email verified successfully.",
"token": "1|abc123..."
}
Example response (200, Already verified):
{
"message": "Email already verified.",
"token": "1|abc123..."
}
Example response (422, Invalid code):
{
"message": "Invalid verification code.",
"errors": {
"code": [
"Invalid verification code."
]
}
}
Example response (422, Expired code):
{
"message": "Verification code has expired. Please request a new one.",
"errors": {
"code": [
"Verification code has expired. Please request a new one."
]
}
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Resend Verification Code
Send a new 4-digit verification code to the user's email. Code expires in 60 minutes.
Example request:
const url = new URL(
"https://backend-staging.subsig.com/api/email/resend"
);
const headers = {
"Content-Type": "application/json",
"Accept": "application/json",
};
let body = {
"email": "john@example.com"
};
fetch(url, {
method: "POST",
headers,
body: JSON.stringify(body),
}).then(response => response.json());Example response (200):
{
"message": "Verification code sent."
}
Example response (200, Already verified):
{
"message": "Email already verified."
}
Example response (422, User not found):
{
"message": "No account found with this email.",
"errors": {
"email": [
"No account found with this email."
]
}
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Authenticate with Google
Verify a Google ID token and log in or register based on intent.
Example request:
const url = new URL(
"https://backend-staging.subsig.com/api/auth/google"
);
const headers = {
"Content-Type": "application/json",
"Accept": "application/json",
};
let body = {
"token": "eyJhbGciOiJSUzI1NiIs...",
"intent": "login",
"appsumo_registration_token": "architecto",
"device_name": "n"
};
fetch(url, {
method: "POST",
headers,
body: JSON.stringify(body),
}).then(response => response.json());Example response (200):
{
"token": "1|abc123...",
"user": {
"name": "John Doe",
"email": "john@example.com",
"avatar": "https://lh3.googleusercontent.com/..."
}
}
Example response (401, Invalid token):
{
"message": "Invalid Google token."
}
Example response (401, Google account conflict):
{
"message": "This account is linked to a different Google account."
}
Example response (404, Account not found):
{
"code": "ACCOUNT_NOT_FOUND",
"message": "Account not found.",
"errors": {
"email": [
"No account exists for this Google email."
]
}
}
Example response (422, Account already exists):
{
"code": "ACCOUNT_ALREADY_EXISTS",
"message": "An account with this email already exists. Please log in.",
"errors": {
"email": [
"An account with this email already exists. Please log in."
]
}
}
Example response (422, Validation error):
{
"message": "The token field is required.",
"errors": {
"token": [
"The token field is required."
]
}
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Authenticate with LinkedIn
Verify a LinkedIn access token and log in or register based on intent.
Example request:
const url = new URL(
"https://backend-staging.subsig.com/api/auth/linkedin"
);
const headers = {
"Content-Type": "application/json",
"Accept": "application/json",
};
let body = {
"token": "AQV...",
"intent": "login",
"appsumo_registration_token": "architecto",
"device_name": "n"
};
fetch(url, {
method: "POST",
headers,
body: JSON.stringify(body),
}).then(response => response.json());Example response (200):
{
"token": "1|abc123...",
"user": {
"name": "John Doe",
"email": "john@example.com",
"avatar": "https://media.licdn.com/..."
}
}
Example response (401, Invalid token):
{
"message": "Invalid LinkedIn token."
}
Example response (401, LinkedIn account conflict):
{
"message": "This account is linked to a different LinkedIn account."
}
Example response (404, Account not found):
{
"code": "ACCOUNT_NOT_FOUND",
"message": "Account not found.",
"errors": {
"email": [
"No account exists for this LinkedIn email."
]
}
}
Example response (422, Account already exists):
{
"code": "ACCOUNT_ALREADY_EXISTS",
"message": "An account with this email already exists. Please log in.",
"errors": {
"email": [
"An account with this email already exists. Please log in."
]
}
}
Example response (422, Validation error):
{
"message": "The token field is required.",
"errors": {
"token": [
"The token field is required."
]
}
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Change Password
requires authentication
Change password for an authenticated API user and return a fresh token.
Example request:
const url = new URL(
"https://backend-staging.subsig.com/api/auth/change-password"
);
const headers = {
"Authorization": "Bearer 1|abc123...",
"Content-Type": "application/json",
"Accept": "application/json",
};
let body = {
"current_password": "SecurePass123!",
"password": "NewSecurePass123!"
};
fetch(url, {
method: "POST",
headers,
body: JSON.stringify(body),
}).then(response => response.json());Example response (200):
{
"message": "Password updated successfully.",
"token": "1|abc123..."
}
Example response (401, Unauthenticated):
{
"message": "Unauthenticated."
}
Example response (422, Current password is incorrect):
{
"message": "The given data was invalid.",
"errors": {
"current_password": [
"The provided password does not match your current password."
]
}
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Set Password
requires authentication
Set a password for an authenticated user who signed up via Google or LinkedIn. Plaintext is never required for the previous random placeholder password.
Example request:
const url = new URL(
"https://backend-staging.subsig.com/api/auth/set-password"
);
const headers = {
"Authorization": "Bearer 1|abc123...",
"Content-Type": "application/json",
"Accept": "application/json",
};
let body = {
"password": "NewSecurePass123!",
"password_confirmation": "NewSecurePass123!"
};
fetch(url, {
method: "POST",
headers,
body: JSON.stringify(body),
}).then(response => response.json());Example response (200):
{
"message": "Password set successfully.",
"token": "1|abc123..."
}
Example response (422, Password already set):
{
"message": "A password is already set. Use change-password instead.",
"error": {
"code": "password_already_set"
}
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Complete Profile
requires authentication
Fill in the profile details collected on the "About you" step right after email-only registration (see CreateNewUser::create()). Password is only required the first time -- once hasUsablePassword() is true, omit it and use change-password instead.
Example request:
const url = new URL(
"https://backend-staging.subsig.com/api/auth/complete-profile"
);
const headers = {
"Authorization": "Bearer 1|abc123...",
"Content-Type": "application/json",
"Accept": "application/json",
};
let body = {
"name": "Jane Doe",
"password": "NewSecurePass123!",
"job_role": "founder_ceo",
"company_name": "Acme Corporation",
"phone": "+14155552671",
"agree_to_terms": true,
"agree_to_marketing": false,
"password_confirmation": "NewSecurePass123!"
};
fetch(url, {
method: "POST",
headers,
body: JSON.stringify(body),
}).then(response => response.json());Example response (200):
{
"message": "Profile completed successfully.",
"token": "1|abc123...",
"user": {}
}
Example response (422, Password already set):
{
"message": "A password is already set. Use change-password instead.",
"error": {
"code": "password_already_set"
}
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Register with Invite
Accept an invitation and create a new user account. The email address must match the email address on the invite. After successful registration, the user will be added to the organisation or project and will receive an email verification code.
Note: This endpoint bypasses the business email requirement since the invitation itself validates the user's legitimacy.
Example request:
const url = new URL(
"https://backend-staging.subsig.com/api/invites/accept"
);
const headers = {
"Content-Type": "application/json",
"Accept": "application/json",
};
let body = {
"token": "eEtgjrcdtubjCu4817MfGiimvC2DQLBgaI7LpY1g5kdDMK5wJlQank7ZJ6PWurmb",
"name": "John Doe",
"email": "user@example.com",
"password": "SecurePass123!",
"password_confirmation": "SecurePass123!"
};
fetch(url, {
method: "POST",
headers,
body: JSON.stringify(body),
}).then(response => response.json());Example response (201):
{
"message": "Registration successful. Please check your email to verify your account."
}
Example response (404, Token not found):
{
"message": "Invite not found.",
"errors": {
"token": [
"The invite token is invalid or does not exist."
]
}
}
Example response (410, Invite expired):
{
"message": "Invite has expired.",
"errors": {
"token": [
"This invite has expired. Please request a new invitation."
]
}
}
Example response (410, Invite already accepted):
{
"message": "Invite has already been accepted.",
"errors": {
"token": [
"This invite has already been accepted."
]
}
}
Example response (422, Email mismatch):
{
"message": "The email address does not match the invitation.",
"errors": {
"email": [
"The email address must match the email on the invitation."
]
}
}
Example response (422, Validation error):
{
"message": "The name field is required.",
"errors": {
"name": [
"The name field is required."
]
}
}
Example response (422, Email already registered):
{
"message": "The email has already been taken.",
"errors": {
"email": [
"The email has already been taken."
]
}
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Get Current User
requires authentication
Get the authenticated user's details including organisation and subscription information.
Example request:
const url = new URL(
"https://backend-staging.subsig.com/api/user"
);
const headers = {
"Authorization": "Bearer 1|abc123...",
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "GET",
headers,
}).then(response => response.json());Example response (200):
{
"name": "John Doe",
"email": "john@example.com",
"email_verified_at": "2025-12-04T12:00:00.000000Z",
"created_at": "2025-12-04T10:00:00.000000Z",
"organisation": {
"uuid": "550e8400-e29b-41d4-a716-446655440000",
"name": "Acme Inc",
"website": "https://acme.com",
"product_logo": "https://example.com/logo.png"
},
"role": "organisation_owner",
"subscription": {
"id": 1,
"stripe_price_id": "price_1234567890",
"name": "Pro Plan",
"status": "active",
"expiration_date": "2025-12-31T23:59:59.000000Z",
"trial_end_date": "2025-12-11T23:59:59.000000Z"
}
}
Example response (401, Unauthenticated):
{
"message": "Unauthenticated."
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Update Profile
requires authentication
Update the authenticated user's own profile fields. name is kept in sync as
"{first_name} {last_name}" for every other place in the app that still reads it.
Example request:
const url = new URL(
"https://backend-staging.subsig.com/api/user"
);
const headers = {
"Authorization": "Bearer 1|abc123...",
"Content-Type": "application/json",
"Accept": "application/json",
};
let body = {
"first_name": "Jane",
"last_name": "Doe",
"phone": "+1 555 123 4567",
"job_role": "Marketing",
"company_name": "Acme Inc"
};
fetch(url, {
method: "PATCH",
headers,
body: JSON.stringify(body),
}).then(response => response.json());Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Delete Account
requires authentication
Permanently delete the authenticated user's own account. Any organisation this user is the sole member of is deleted along with it (see UserDeletionService); shared organisations are left intact for the remaining members.
Example request:
const url = new URL(
"https://backend-staging.subsig.com/api/user"
);
const headers = {
"Authorization": "Bearer 1|abc123...",
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "DELETE",
headers,
}).then(response => response.json());Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Log In
Authenticate with your email and password to start a session. On success, you receive a token for subsequent requests.
Example request:
const url = new URL(
"https://backend-staging.subsig.com/login"
);
const headers = {
"Content-Type": "application/json",
"Accept": "application/json",
};
let body = {
"email": "john@example.com",
"password": "SecurePass123!",
"remember": true
};
fetch(url, {
method: "POST",
headers,
body: JSON.stringify(body),
}).then(response => response.json());Example response (200, Login successful. Session started.):
Example response (422, Invalid credentials.):
{
"message": "These credentials do not match our records.",
"errors": {
"email": [
"These credentials do not match our records."
]
}
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Log Out
requires authentication
End your current session. You will need to log in again to access protected resources.
Example request:
const url = new URL(
"https://backend-staging.subsig.com/logout"
);
const headers = {
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "POST",
headers,
}).then(response => response.json());Example response (200, Logged out successfully.):
Example response (401, Not logged in.):
{
"message": "Unauthenticated."
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Password Reset
Recover access to your account if you forgot your password.
Request Password Reset
Send a password reset link to your email. The link expires after 60 minutes. Same response for security even if email not found.
Example request:
const url = new URL(
"https://backend-staging.subsig.com/forgot-password"
);
const headers = {
"Content-Type": "application/json",
"Accept": "application/json",
};
let body = {
"email": "john@example.com"
};
fetch(url, {
method: "POST",
headers,
body: JSON.stringify(body),
}).then(response => response.json());Example response (200, Reset link sent.):
{
"status": "We have emailed your password reset link."
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Reset Password
Set a new password using the token from your email. Token is valid for 60 minutes.
Example request:
const url = new URL(
"https://backend-staging.subsig.com/reset-password"
);
const headers = {
"Content-Type": "application/json",
"Accept": "application/json",
};
let body = {
"token": "a1b2c3d4e5f6g7h8i9j0",
"email": "john@example.com",
"password": "NewSecurePass123!",
"password_confirmation": "NewSecurePass123!"
};
fetch(url, {
method: "POST",
headers,
body: JSON.stringify(body),
}).then(response => response.json());Example response (200, Password reset successful.):
{
"status": "Your password has been reset."
}
Example response (422, Invalid or expired token.):
{
"message": "This password reset token is invalid.",
"errors": {
"email": [
"This password reset token is invalid."
]
}
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
AI Traffic
The AI Traffics dashboard's GA4-sourced overview -- see GoogleAnalyticsTrafficReportService for which fields are real (GA4 rollups) vs placeholders pending the bot-crawler pipeline.
Traffic Overview
requires authentication
Example request:
const url = new URL(
"https://backend-staging.subsig.com/api/organisations/6ff8f7f6-1eb3-3525-be4a-3932c805afed/ai-visibility/traffic"
);
const headers = {
"Authorization": "Bearer {token}",
"Content-Type": "application/json",
"Accept": "application/json",
};
let body = {
"date_from": "architecto",
"date_to": "architecto",
"granularity": "architecto"
};
fetch(url, {
method: "POST",
headers,
body: JSON.stringify(body),
}).then(response => response.json());Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Traffic Pages
requires authentication
Example request:
const url = new URL(
"https://backend-staging.subsig.com/api/organisations/6ff8f7f6-1eb3-3525-be4a-3932c805afed/ai-visibility/traffic/pages"
);
const headers = {
"Authorization": "Bearer {token}",
"Content-Type": "application/json",
"Accept": "application/json",
};
let body = {
"date_from": "architecto",
"date_to": "architecto",
"providers": [
"architecto"
],
"sort_by": "architecto",
"sort_direction": "architecto",
"per_page": 16,
"page": 16
};
fetch(url, {
method: "POST",
headers,
body: JSON.stringify(body),
}).then(response => response.json());Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
AI Visibility - Competitor
Competitor tab — brand list with visibility/SOV/position metrics and CRUD.
Competitor overview
requires authentication
Returns all tracked brands (own + competitors) with visibility, share-of-voice, and average-position metrics for the requested date window, plus change deltas vs the preceding window of equal length.
Example request:
const url = new URL(
"https://backend-staging.subsig.com/api/organisations/660e8400-e29b-41d4-a716-446655440001/ai-visibility/competitors"
);
const headers = {
"Authorization": "Bearer 1|abc123...",
"Content-Type": "application/json",
"Accept": "application/json",
};
let body = {
"date_from": "architecto",
"date_to": "architecto",
"provider": "architecto",
"page": 16,
"per_page": 16,
"search": "architecto"
};
fetch(url, {
method: "POST",
headers,
body: JSON.stringify(body),
}).then(response => response.json());Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Add competitor
requires authentication
Example request:
const url = new URL(
"https://backend-staging.subsig.com/api/organisations/6ff8f7f6-1eb3-3525-be4a-3932c805afed/ai-visibility/competitor-brands"
);
const headers = {
"Authorization": "Bearer 1|abc123...",
"Content-Type": "application/json",
"Accept": "application/json",
};
let body = {
"name": "Acme Corp",
"website": "acme.com"
};
fetch(url, {
method: "POST",
headers,
body: JSON.stringify(body),
}).then(response => response.json());Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Update competitor
requires authentication
Example request:
const url = new URL(
"https://backend-staging.subsig.com/api/organisations/6ff8f7f6-1eb3-3525-be4a-3932c805afed/ai-visibility/competitor-brands/16"
);
const headers = {
"Authorization": "Bearer 1|abc123...",
"Content-Type": "application/json",
"Accept": "application/json",
};
let body = {
"name": "architecto",
"website": "architecto"
};
fetch(url, {
method: "PATCH",
headers,
body: JSON.stringify(body),
}).then(response => response.json());Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Delete competitor
requires authentication
Example request:
const url = new URL(
"https://backend-staging.subsig.com/api/organisations/6ff8f7f6-1eb3-3525-be4a-3932c805afed/ai-visibility/competitor-brands/16"
);
const headers = {
"Authorization": "Bearer 1|abc123...",
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "DELETE",
headers,
}).then(response => response.json());Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
AI Visibility - Dashboard
Ticket 7 — the Visibility tab. Reads exclusively from ai_visibility_daily_metrics (Ticket 5),
never live-joins execution tables — see AiVisibilityMetricsAggregateQuery.
Visibility Tab
requires authentication
Example request:
const url = new URL(
"https://backend-staging.subsig.com/api/organisations/660e8400-e29b-41d4-a716-446655440001/ai-visibility/visibility"
);
const headers = {
"Authorization": "Bearer {token}",
"Content-Type": "application/json",
"Accept": "application/json",
};
let body = {
"date_from": "2026-06-01",
"date_to": "2026-06-30",
"compare_from": "architecto",
"compare_to": "architecto",
"provider": "openai",
"model": "architecto",
"prompt_id": 16,
"country": "architecto",
"language": "architecto",
"source": "architecto"
};
fetch(url, {
method: "POST",
headers,
body: JSON.stringify(body),
}).then(response => response.json());Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Platforms Tab
requires authentication
Example request:
const url = new URL(
"https://backend-staging.subsig.com/api/organisations/660e8400-e29b-41d4-a716-446655440001/ai-visibility/platforms"
);
const headers = {
"Authorization": "Bearer {token}",
"Content-Type": "application/json",
"Accept": "application/json",
};
let body = {
"date_from": "architecto",
"date_to": "architecto"
};
fetch(url, {
method: "POST",
headers,
body: JSON.stringify(body),
}).then(response => response.json());Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Sentiment Tab
requires authentication
Example request:
const url = new URL(
"https://backend-staging.subsig.com/api/organisations/660e8400-e29b-41d4-a716-446655440001/ai-visibility/sentiment"
);
const headers = {
"Authorization": "Bearer {token}",
"Content-Type": "application/json",
"Accept": "application/json",
};
let body = {
"brand": 16,
"per_page": 16
};
fetch(url, {
method: "POST",
headers,
body: JSON.stringify(body),
}).then(response => response.json());Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Export Sentiment Sources
requires authentication
Example request:
const url = new URL(
"https://backend-staging.subsig.com/api/organisations/660e8400-e29b-41d4-a716-446655440001/ai-visibility/sentiment/sources/export"
);
const headers = {
"Authorization": "Bearer {token}",
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "POST",
headers,
}).then(response => response.json());Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Citations Tab
requires authentication
Example request:
const url = new URL(
"https://backend-staging.subsig.com/api/organisations/660e8400-e29b-41d4-a716-446655440001/ai-visibility/citations"
);
const headers = {
"Authorization": "Bearer {token}",
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "POST",
headers,
}).then(response => response.json());Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Citations — Domains
requires authentication
Example request:
const url = new URL(
"https://backend-staging.subsig.com/api/organisations/660e8400-e29b-41d4-a716-446655440001/ai-visibility/citations/domains"
);
const headers = {
"Authorization": "Bearer {token}",
"Content-Type": "application/json",
"Accept": "application/json",
};
let body = {
"per_page": 16
};
fetch(url, {
method: "POST",
headers,
body: JSON.stringify(body),
}).then(response => response.json());Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Citations — URLs
requires authentication
Example request:
const url = new URL(
"https://backend-staging.subsig.com/api/organisations/660e8400-e29b-41d4-a716-446655440001/ai-visibility/citations/urls"
);
const headers = {
"Authorization": "Bearer {token}",
"Content-Type": "application/json",
"Accept": "application/json",
};
let body = {
"per_page": 16
};
fetch(url, {
method: "POST",
headers,
body: JSON.stringify(body),
}).then(response => response.json());Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Export Citation URLs
requires authentication
Flat, one-row-per-citation-occurrence export matching the format competitor tools like Otterly.ai use -- not the on-screen URL rollup, which aggregates many occurrences of the same URL into one summary row and can't carry per-occurrence fields like Prompt/Date.
Example request:
const url = new URL(
"https://backend-staging.subsig.com/api/organisations/660e8400-e29b-41d4-a716-446655440001/ai-visibility/citations/urls/export"
);
const headers = {
"Authorization": "Bearer {token}",
"Content-Type": "application/json",
"Accept": "application/json",
};
let body = {
"domain": "architecto"
};
fetch(url, {
method: "POST",
headers,
body: JSON.stringify(body),
}).then(response => response.json());Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Export Citation Domains
requires authentication
Flat, one-row-per-citation-occurrence export matching the format competitor tools like Otterly.ai use -- not the on-screen domain rollup, which aggregates many occurrences of the same domain into one summary row and can't carry per-occurrence fields like Prompt/Date.
Example request:
const url = new URL(
"https://backend-staging.subsig.com/api/organisations/660e8400-e29b-41d4-a716-446655440001/ai-visibility/citations/domains/export"
);
const headers = {
"Authorization": "Bearer {token}",
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "POST",
headers,
}).then(response => response.json());Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Citations — Domain Detail
requires authentication
Example request:
const url = new URL(
"https://backend-staging.subsig.com/api/organisations/6ff8f7f6-1eb3-3525-be4a-3932c805afed/ai-visibility/citations/domain-detail"
);
const headers = {
"Authorization": "Bearer {token}",
"Content-Type": "application/json",
"Accept": "application/json",
};
let body = {
"domain": "reddit.com"
};
fetch(url, {
method: "POST",
headers,
body: JSON.stringify(body),
}).then(response => response.json());Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Citations — URLs for a Domain (paginated)
requires authentication
Example request:
const url = new URL(
"https://backend-staging.subsig.com/api/organisations/6ff8f7f6-1eb3-3525-be4a-3932c805afed/ai-visibility/citations/domain-urls"
);
const headers = {
"Authorization": "Bearer {token}",
"Content-Type": "application/json",
"Accept": "application/json",
};
let body = {
"domain": "architecto"
};
fetch(url, {
method: "POST",
headers,
body: JSON.stringify(body),
}).then(response => response.json());Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Citations — URL Detail
requires authentication
Example request:
const url = new URL(
"https://backend-staging.subsig.com/api/organisations/6ff8f7f6-1eb3-3525-be4a-3932c805afed/ai-visibility/citations/url-detail"
);
const headers = {
"Authorization": "Bearer {token}",
"Content-Type": "application/json",
"Accept": "application/json",
};
let body = {
"url": "http:\/\/www.bailey.biz\/quos-velit-et-fugiat-sunt-nihil-accusantium-harum.html"
};
fetch(url, {
method: "POST",
headers,
body: JSON.stringify(body),
}).then(response => response.json());Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Citations — AI Responses that cited a URL (paginated)
requires authentication
Example request:
const url = new URL(
"https://backend-staging.subsig.com/api/organisations/6ff8f7f6-1eb3-3525-be4a-3932c805afed/ai-visibility/citations/url-responses"
);
const headers = {
"Authorization": "Bearer {token}",
"Content-Type": "application/json",
"Accept": "application/json",
};
let body = {
"url": "http:\/\/www.bailey.biz\/quos-velit-et-fugiat-sunt-nihil-accusantium-harum.html"
};
fetch(url, {
method: "POST",
headers,
body: JSON.stringify(body),
}).then(response => response.json());Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Export AI Responses that cited a URL
requires authentication
One row per citation occurrence (a citation URL from a specific prompt/service/date), matching the flat format competitor tools like Otterly.ai export -- not one row per response, since a single response can cite several URLs.
Example request:
const url = new URL(
"https://backend-staging.subsig.com/api/organisations/6ff8f7f6-1eb3-3525-be4a-3932c805afed/ai-visibility/citations/url-responses/export"
);
const headers = {
"Authorization": "Bearer {token}",
"Content-Type": "application/json",
"Accept": "application/json",
};
let body = {
"url": "http:\/\/www.bailey.biz\/quos-velit-et-fugiat-sunt-nihil-accusantium-harum.html"
};
fetch(url, {
method: "POST",
headers,
body: JSON.stringify(body),
}).then(response => response.json());Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
AI Visibility - Prompt Detail
Ticket 7 — the prompt-detail scrollable page (overview/citations/responses) and the top-level execution detail view. Kept separate from AiVisibilityPromptController (already ~1700 lines, and owns a different, older payload shape via show()/executionShow()) — these are new endpoints with their own contract, built on the same Ticket 5 aggregate-table services as the rest of Ticket 7 rather than that controller's live-join helpers.
Prompt Overview
requires authentication
Example request:
const url = new URL(
"https://backend-staging.subsig.com/api/organisations/660e8400-e29b-41d4-a716-446655440001/ai-visibility/prompts/1/overview"
);
const headers = {
"Authorization": "Bearer {token}",
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "GET",
headers,
}).then(response => response.json());Example response (401):
Show headers
cache-control: no-cache, private
content-type: application/json
vary: Origin
{
"message": "Unauthenticated."
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Prompt Filter Options
requires authentication
Distinct providers, models, countries and languages that have actually run for this prompt. Used to populate the filter dropdowns on the prompt detail page.
Example request:
const url = new URL(
"https://backend-staging.subsig.com/api/organisations/660e8400-e29b-41d4-a716-446655440001/ai-visibility/prompts/1/filters"
);
const headers = {
"Authorization": "Bearer {token}",
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "GET",
headers,
}).then(response => response.json());Example response (401):
Show headers
cache-control: no-cache, private
content-type: application/json
vary: Origin
{
"message": "Unauthenticated."
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Prompt Citations
requires authentication
Same shape as POST /ai-visibility/citations, scoped to this prompt.
Example request:
const url = new URL(
"https://backend-staging.subsig.com/api/organisations/660e8400-e29b-41d4-a716-446655440001/ai-visibility/prompts/1/citations"
);
const headers = {
"Authorization": "Bearer {token}",
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "POST",
headers,
}).then(response => response.json());Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Prompt Citation Domains (paginated)
requires authentication
Same shape as POST /ai-visibility/citations/domains, scoped to this prompt.
Example request:
const url = new URL(
"https://backend-staging.subsig.com/api/organisations/660e8400-e29b-41d4-a716-446655440001/ai-visibility/prompts/1/citations/domains"
);
const headers = {
"Authorization": "Bearer {token}",
"Content-Type": "application/json",
"Accept": "application/json",
};
let body = {
"per_page": 16,
"page": 16
};
fetch(url, {
method: "POST",
headers,
body: JSON.stringify(body),
}).then(response => response.json());Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Prompt Citation URLs (paginated)
requires authentication
Same shape as POST /ai-visibility/citations/urls, scoped to this prompt.
Example request:
const url = new URL(
"https://backend-staging.subsig.com/api/organisations/660e8400-e29b-41d4-a716-446655440001/ai-visibility/prompts/1/citations/urls"
);
const headers = {
"Authorization": "Bearer {token}",
"Content-Type": "application/json",
"Accept": "application/json",
};
let body = {
"per_page": 16,
"page": 16
};
fetch(url, {
method: "POST",
headers,
body: JSON.stringify(body),
}).then(response => response.json());Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Export Prompt Citation URLs
requires authentication
Example request:
const url = new URL(
"https://backend-staging.subsig.com/api/organisations/660e8400-e29b-41d4-a716-446655440001/ai-visibility/prompts/1/citations/export"
);
const headers = {
"Authorization": "Bearer {token}",
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "POST",
headers,
}).then(response => response.json());Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
GET api/organisations/{organisation_uuid}/ai-visibility/prompts/{prompt}/responses
requires authentication
Example request:
const url = new URL(
"https://backend-staging.subsig.com/api/organisations/205ae76c-bd73-426b-a36a-8338353b9542/ai-visibility/prompts/architecto/responses"
);
const headers = {
"Authorization": "Bearer 1|abc123...",
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "GET",
headers,
}).then(response => response.json());Example response (401):
Show headers
cache-control: no-cache, private
content-type: application/json
vary: Origin
{
"message": "Unauthenticated."
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Prompt Response Detail
requires authentication
Full detail for a single execution — backs the "AI Response" modal on the prompt responses tab.
Example request:
const url = new URL(
"https://backend-staging.subsig.com/api/organisations/660e8400-e29b-41d4-a716-446655440001/ai-visibility/prompts/1/responses/42"
);
const headers = {
"Authorization": "Bearer {token}",
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "GET",
headers,
}).then(response => response.json());Example response (200):
{
"data": {
"execution_id": 42,
"prompt_title": "How does Notion help teams?",
"provider": "openai",
"model": "gpt-4o",
"date": "2026-06-25 09:00:00",
"detected_brands": [
{
"name": "Notion",
"website": "notion.com"
}
],
"sentiment": "positive",
"is_brand_mentioned": true,
"response_content": "Notion helps teams...",
"citations": [
{
"rank": 1,
"url": "https://notion.so/blog",
"title": "Notion Blog",
"domain": "notion.so",
"page_type": "references"
}
]
}
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Full detail for a single radar (Google AI Overview) response.
requires authentication
Example request:
const url = new URL(
"https://backend-staging.subsig.com/api/organisations/6ff8f7f6-1eb3-3525-be4a-3932c805afed/ai-visibility/prompts/16/radar-responses/16"
);
const headers = {
"Authorization": "Bearer 1|abc123...",
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "GET",
headers,
}).then(response => response.json());Example response (401):
Show headers
cache-control: no-cache, private
content-type: application/json
vary: Origin
{
"message": "Unauthenticated."
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Export Prompt Responses
requires authentication
Example request:
const url = new URL(
"https://backend-staging.subsig.com/api/organisations/660e8400-e29b-41d4-a716-446655440001/ai-visibility/prompts/1/responses/export"
);
const headers = {
"Authorization": "Bearer {token}",
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "POST",
headers,
}).then(response => response.json());Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Execution Detail
requires authentication
Not scoped to a prompt and exempt from the dashboard cache — reads live, per Ticket 7's spec. Full detail (including inline citation segments) is available regardless of whether raw_response has since been pruned by retention (Ticket 8) — citation_segments is derived once at normalization time (see NormalizeAiVisibilityExecutionJob) and persisted alongside generated_answer, neither of which the prune command ever touches.
Example request:
const url = new URL(
"https://backend-staging.subsig.com/api/organisations/660e8400-e29b-41d4-a716-446655440001/ai-visibility/executions/501"
);
const headers = {
"Authorization": "Bearer {token}",
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "GET",
headers,
}).then(response => response.json());Example response (401):
Show headers
cache-control: no-cache, private
content-type: application/json
vary: Origin
{
"message": "Unauthenticated."
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
AI Visibility - Prompts
APIs for managing AI Visibility prompts, their scheduled executions, and execution detail.
List Prompts
requires authentication
Example request:
const url = new URL(
"https://backend-staging.subsig.com/api/organisations/660e8400-e29b-41d4-a716-446655440001/ai-visibility/prompts"
);
const params = {
"topic_id": "16",
"status": "active",
"search": "pricing",
"per_page": "20",
};
Object.keys(params)
.forEach(key => url.searchParams.append(key, params[key]));
const headers = {
"Authorization": "Bearer {token}",
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "GET",
headers,
}).then(response => response.json());Example response (401):
Show headers
cache-control: no-cache, private
content-type: application/json
vary: Origin
{
"message": "Unauthenticated."
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Create Prompt
requires authentication
Example request:
const url = new URL(
"https://backend-staging.subsig.com/api/organisations/660e8400-e29b-41d4-a716-446655440001/ai-visibility/prompts"
);
const headers = {
"Authorization": "Bearer {token}",
"Content-Type": "application/json",
"Accept": "application/json",
};
let body = {
"text": "How does our product compare to competitors?",
"topic_id": 16,
"country": "NG",
"language": "en",
"frequency": "weekly",
"providers": [
"openai",
"perplexity"
],
"skip_first_run": true
};
fetch(url, {
method: "POST",
headers,
body: JSON.stringify(body),
}).then(response => response.json());Example response (201):
{
"data": {
"id": 1,
"text": "How does our product compare to competitors?",
"topic_id": 16,
"country": "NG",
"language": "en",
"frequency": "weekly",
"status": "active",
"providers": [
"openai",
"perplexity"
]
}
}
Example response (403):
{
"message": "AI Visibility is not enabled for this organisation."
}
Example response (422):
{
"message": "Active prompt limit reached for this plan."
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Bulk Import Prompts
requires authentication
Accepts a flat list of prompt texts assigned to a single topic, split against the plan active-prompt limit: prompts that fit are created as active and dispatched on the priority queue; any overflow is created as paused. Case-insensitive exact duplicates are skipped.
Example request:
const url = new URL(
"https://backend-staging.subsig.com/api/organisations/6ff8f7f6-1eb3-3525-be4a-3932c805afed/ai-visibility/prompts/bulk-import"
);
const headers = {
"Authorization": "Bearer {token}",
"Content-Type": "application/json",
"Accept": "application/json",
};
let body = {
"topic_id": 16,
"prompts": [
"architecto"
],
"activate": false
};
fetch(url, {
method: "POST",
headers,
body: JSON.stringify(body),
}).then(response => response.json());Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Get Organisation-Level Execution Status
requires authentication
Polling endpoint for the frontend to show a banner ("results are being recalculated") while any AI Visibility execution across the organisation is still in progress. Bounded by updated_at (not scheduled_at -- recurring prompts get scheduled_at pinned to midnight, so that column can't tell "just went pending/running" apart from "queued 10 hours ago") to a recent window rather than all-time, since any legitimate in-flight execution resolves (completes, fails, or gets reaped by ai-visibility:reap-stale-executions) well within it. Also counts radar (Google AI Overview) rows still in 'pending' status.
in_progress_count is the number of distinct prompts still in flight, not the number of rows -- a single prompt fans out to one execution row per provider (openai, perplexity, ...) plus its own radar row, so counting rows would overstate how many prompts are actually still running.
Always queried fresh (no caching layer, no-store response header) — every poll reflects the current DB state exactly.
Example request:
const url = new URL(
"https://backend-staging.subsig.com/api/organisations/660e8400-e29b-41d4-a716-446655440001/ai-visibility/prompts/execution-status"
);
const headers = {
"Authorization": "Bearer {token}",
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "GET",
headers,
}).then(response => response.json());Example response (200):
{
"data": {
"in_progress": true,
"in_progress_count": 2
}
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Regions In Use
requires authentication
Distinct region/country codes actually configured on this organisation's own prompts -- NOT the full curated config('ai_visibility.supported_regions') list that OrganisationController::supportedRegions() returns (that one backs region pickers on creation forms, where every supported region is a valid choice). This backs the AI Visibility dashboard's region filter dropdowns (Platforms/Visibility/Sentiment/ Citations), which should only ever offer a region that at least one of this org's prompts could actually produce data for -- offering the full global list there let an org filter by a region with zero prompts and see an always-empty result with no indication why.
Scoped to active/paused prompts only, same default scope as index() -- an archived prompt's region isn't a live filtering concern.
Example request:
const url = new URL(
"https://backend-staging.subsig.com/api/organisations/660e8400-e29b-41d4-a716-446655440001/ai-visibility/prompts/regions"
);
const headers = {
"Authorization": "Bearer {token}",
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "GET",
headers,
}).then(response => response.json());Example response (200):
{
"data": [
{
"value": "US",
"label": "United States"
},
{
"value": "GB",
"label": "United Kingdom"
}
]
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Get Prompt
requires authentication
Example request:
const url = new URL(
"https://backend-staging.subsig.com/api/organisations/660e8400-e29b-41d4-a716-446655440001/ai-visibility/prompts/1"
);
const headers = {
"Authorization": "Bearer {token}",
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "GET",
headers,
}).then(response => response.json());Example response (401):
Show headers
cache-control: no-cache, private
content-type: application/json
vary: Origin
{
"message": "Unauthenticated."
}
Example response (404):
{
"message": "Prompt not found."
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Update Prompt
requires authentication
Example request:
const url = new URL(
"https://backend-staging.subsig.com/api/organisations/660e8400-e29b-41d4-a716-446655440001/ai-visibility/prompts/1"
);
const headers = {
"Authorization": "Bearer {token}",
"Content-Type": "application/json",
"Accept": "application/json",
};
let body = {
"text": "How does our product compare to competitors?",
"topic_id": 16,
"country": "NG",
"language": "en",
"frequency": "weekly",
"status": "active",
"providers": [
"openai",
"perplexity"
]
};
fetch(url, {
method: "PATCH",
headers,
body: JSON.stringify(body),
}).then(response => response.json());Example response (404):
{
"message": "Prompt not found."
}
Example response (422):
{
"message": "This frequency is not allowed on the current plan."
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Duplicate Prompt
requires authentication
Creates a paused copy of the prompt with the same topic, text, country, language, frequency and providers.
Example request:
const url = new URL(
"https://backend-staging.subsig.com/api/organisations/660e8400-e29b-41d4-a716-446655440001/ai-visibility/prompts/1/duplicate"
);
const headers = {
"Authorization": "Bearer {token}",
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "POST",
headers,
}).then(response => response.json());Example response (404):
{
"message": "Prompt not found."
}
Example response (422):
{
"message": "Active prompt limit reached for this plan."
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Delete Prompt
requires authentication
Example request:
const url = new URL(
"https://backend-staging.subsig.com/api/organisations/660e8400-e29b-41d4-a716-446655440001/ai-visibility/prompts/1"
);
const headers = {
"Authorization": "Bearer {token}",
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "DELETE",
headers,
}).then(response => response.json());Example response (204):
Empty response
Example response (404):
{
"message": "Prompt not found."
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Run Prompt Now
requires authentication
Manually dispatches a fresh execution for this prompt across all of its allowed providers, independent of the scheduler.
Example request:
const url = new URL(
"https://backend-staging.subsig.com/api/organisations/660e8400-e29b-41d4-a716-446655440001/ai-visibility/prompts/1/run"
);
const headers = {
"Authorization": "Bearer {token}",
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "POST",
headers,
}).then(response => response.json());Example response (202):
{
"data": {
"prompt_id": 1,
"execution_ids": [
501,
502
]
}
}
Example response (404):
{
"message": "Prompt not found."
}
Example response (422):
{
"message": "No allowed and available provider could run this prompt."
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
List Prompt Executions
requires authentication
"All Responses" list — one row per execution ("chat") run for this prompt, newest first.
Example request:
const url = new URL(
"https://backend-staging.subsig.com/api/organisations/660e8400-e29b-41d4-a716-446655440001/ai-visibility/prompts/1/executions"
);
const params = {
"provider": "openai",
"status": "completed",
"per_page": "20",
};
Object.keys(params)
.forEach(key => url.searchParams.append(key, params[key]));
const headers = {
"Authorization": "Bearer {token}",
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "GET",
headers,
}).then(response => response.json());Example response (401):
Show headers
cache-control: no-cache, private
content-type: application/json
vary: Origin
{
"message": "Unauthenticated."
}
Example response (404):
{
"message": "Prompt not found."
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Get Prompt Execution Detail
requires authentication
"Edit Responses" chat detail — full generated answer, citations, and per-brand mentions for a single execution.
Example request:
const url = new URL(
"https://backend-staging.subsig.com/api/organisations/660e8400-e29b-41d4-a716-446655440001/ai-visibility/prompts/1/executions/501"
);
const headers = {
"Authorization": "Bearer {token}",
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "GET",
headers,
}).then(response => response.json());Example response (401):
Show headers
cache-control: no-cache, private
content-type: application/json
vary: Origin
{
"message": "Unauthenticated."
}
Example response (404):
{
"message": "Execution not found."
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Get Prompt Execution Status
requires authentication
Polling endpoint for the frontend to show a loading skeleton while a prompt's latest run is still in progress. "In progress" covers both an execution that hasn't finished calling its provider yet, and one that has (status=completed) but whose citations/brand-mentions haven't been written yet by the normalization job that runs right after — status alone flips to completed before that data exists, so this checks processing_version too rather than reporting "ready" before there's anything real to show. Also folds in the org's latest radar (Google AI Overview) pull for this prompt, if radar is enabled on its plan — radar rows live in a separate table (ai_visibility_radar_responses) with their own pending/normalised/failed lifecycle, but ai_visibility_radar_responses.prompt_id is a real FK to this same prompt, so it belongs in this prompt's status too.
Always queried fresh (no caching layer, no-store response header) — every poll reflects the current DB state exactly.
Example request:
const url = new URL(
"https://backend-staging.subsig.com/api/organisations/660e8400-e29b-41d4-a716-446655440001/ai-visibility/prompts/1/execution-status"
);
const headers = {
"Authorization": "Bearer {token}",
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "GET",
headers,
}).then(response => response.json());Example response (200):
{
"data": {
"status": "in_progress",
"scheduled_at": "2026-08-14 12:23:00",
"providers": [
{
"provider": "openai",
"status": "in_progress"
},
{
"provider": "perplexity",
"status": "ready"
},
{
"provider": "google_ai_overviews",
"status": "ready"
}
]
}
}
Example response (404):
{
"message": "Prompt not found."
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
AI Visibility - Suggestions
Prompt and topic suggestions generated by GenerateAiVisibilitySuggestionsJob. Suggestions start
as completed and move to accepted or dismissed when the user acts on them.
List prompt suggestions
requires authentication
Returns up to 15 prompt suggestions with status completed for the organisation,
ordered by source (radar first), then volume descending, then most recent.
Example request:
const url = new URL(
"https://backend-staging.subsig.com/api/organisations/660e8400-e29b-41d4-a716-446655440001/ai-visibility/prompts/suggested"
);
const params = {
"exclude_categories[0]": "branded",
};
Object.keys(params)
.forEach(key => url.searchParams.append(key, params[key]));
const headers = {
"Authorization": "Bearer {token}",
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "GET",
headers,
}).then(response => response.json());Example response (200):
{
"data": [
{
"id": 1,
"text": "What is the best tool for notes and docs?",
"category": "awareness",
"source": "radar",
"volume": 12000,
"status": "completed",
"generated_at": "2026-06-01 00:00:00"
}
]
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Refetch suggestions
requires authentication
A separate, standalone generation path from GenerateAiVisibilitySuggestionsJob (the
onboarding job) — deliberately not shared code, so this can never change that job's
behavior. Two differences from onboarding generation: (1) previously dismissed suggestions
are deleted first so a fresh batch can take their place — an upsert alone would otherwise
leave a dismissed row's regenerated duplicate stuck as dismissed forever, since the upsert
never touches status; completed/accepted suggestions are left untouched. (2) an LLM
failure here is a real error, not silently swallowed into the generic fallback set — a user
who clicks "refetch" wants real, freshly-generated suggestions, and substituting the generic
fallback would look like a genuine refresh when it isn't one.
Rate-limited to once per 10 minutes per org: a successful call holds its lock for the full
cooldown (not released in a finally) so a second click can't trigger another LLM call too
soon; a failed call releases immediately since it never produced anything worth protecting.
A request that lands inside an active cooldown isn't rejected — it gets back the same
existing (still-valid) suggestions with cached: true, rather than a bare error.
Example request:
const url = new URL(
"https://backend-staging.subsig.com/api/organisations/660e8400-e29b-41d4-a716-446655440001/ai-visibility/prompts/suggested/refetch"
);
const headers = {
"Authorization": "Bearer {token}",
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "POST",
headers,
}).then(response => response.json());Example response (200):
{
"data": {
"topics": [
{
"id": 1,
"name": "Feature Discovery",
"description": "...",
"source": "llm",
"status": "completed",
"generated_at": "2026-06-01 00:00:00"
}
],
"prompts": [
{
"id": 1,
"text": "What is the best tool for notes and docs?",
"category": "awareness",
"source": "llm",
"status": "completed",
"generated_at": "2026-06-01 00:00:00"
}
]
}
}
Example response (200):
{
"data": {
"topics": [],
"prompts": []
},
"cached": true
}
Example response (422):
{
"message": "No own brand found for this organisation."
}
Example response (502):
{
"message": "Failed to generate suggestions. Please try again."
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Accept a prompt suggestion
requires authentication
Creates a live prompt from the suggestion, dispatches an immediate first run, and marks the
suggestion as accepted. Returns the new prompt's ID and settings.
Example request:
const url = new URL(
"https://backend-staging.subsig.com/api/organisations/660e8400-e29b-41d4-a716-446655440001/ai-visibility/prompts/suggested/1/accept"
);
const headers = {
"Authorization": "Bearer {token}",
"Content-Type": "application/json",
"Accept": "application/json",
};
let body = {
"frequency": "daily",
"providers": [
"openai"
],
"topic_id": 3,
"country": "US",
"language": "en",
"skip_first_run": true
};
fetch(url, {
method: "POST",
headers,
body: JSON.stringify(body),
}).then(response => response.json());Example response (201):
{
"data": {
"prompt_id": 42,
"text": "What is the best tool for notes and docs?",
"topic_id": 3,
"frequency": "daily",
"providers": [
"openai"
],
"country": "US",
"language": "en"
}
}
Example response (422):
{
"message": "Active prompt limit reached for this plan."
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Dismiss a prompt suggestion
requires authentication
Marks the suggestion as dismissed. It will no longer appear in the suggestions list.
Example request:
const url = new URL(
"https://backend-staging.subsig.com/api/organisations/660e8400-e29b-41d4-a716-446655440001/ai-visibility/prompts/suggested/1"
);
const headers = {
"Authorization": "Bearer {token}",
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "DELETE",
headers,
}).then(response => response.json());Example response (204):
Empty response
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Onboarding suggestions
requires authentication
Returns both topic and prompt suggestions for the onboarding wizard in a single call.
Dispatch GenerateAiVisibilitySuggestionsJob for the org before calling this endpoint to
ensure suggestions are populated.
Example request:
const url = new URL(
"https://backend-staging.subsig.com/api/organisations/660e8400-e29b-41d4-a716-446655440001/ai-visibility/onboarding/suggestions"
);
const headers = {
"Authorization": "Bearer {token}",
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "GET",
headers,
}).then(response => response.json());Example response (200):
{
"data": {
"topics": [
{
"id": 1,
"name": "Feature Discovery",
"description": "...",
"source": "llm",
"status": "completed",
"generated_at": "2026-06-01 00:00:00"
}
],
"prompts": [
{
"id": 1,
"text": "What is the best tool for notes?",
"category": "awareness",
"source": "radar",
"volume": 12000,
"status": "completed",
"generated_at": "2026-06-01 00:00:00"
}
]
}
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Onboarding kickoff
requires authentication
Dispatches first-run executions on the priority queue for all active prompts the org accepted during onboarding (those created with skip_first_run=true). Call this from the BrandAnalysisProgress loading screen, after the user has committed to onboarding (step 4), so we don't waste compute on users who abandon after the suggestion step.
Example request:
const url = new URL(
"https://backend-staging.subsig.com/api/organisations/6ff8f7f6-1eb3-3525-be4a-3932c805afed/ai-visibility/onboarding/kickoff"
);
const headers = {
"Authorization": "Bearer {token}",
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "POST",
headers,
}).then(response => response.json());Example response (202):
{
"data": {
"dispatched": 5
}
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Onboarding analysis status
requires authentication
Returns how many of the org's active prompt executions have completed and a live visibility percentage computed directly from execution_brands rows (no daily_metrics aggregation needed — available as soon as the first execution is normalised).
Poll this from BrandAnalysisProgress until ready is true.
Example request:
const url = new URL(
"https://backend-staging.subsig.com/api/organisations/6ff8f7f6-1eb3-3525-be4a-3932c805afed/ai-visibility/onboarding/status"
);
const headers = {
"Authorization": "Bearer {token}",
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "GET",
headers,
}).then(response => response.json());Example response (200):
{
"data": {
"ready": true,
"executions_total": 10,
"executions_completed": 8,
"visibility_pct": 45
}
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Onboarding improvement estimate
requires authentication
One-shot, synchronous LLM call — no queue, no polling. Call this once from the onboarding screen right when onboardingStatus() first reports has_own_brand_data (i.e. the real, aggregated visibility_pct is already showing), so the estimate stays grounded in the exact same number the dashboard will show after login. Cached for 30 days, so a repeat call (re-entering onboarding, a second tab) returns instantly without re-running the LLM. Returns potential_visibility_pct: null if there isn't enough own-brand data yet, or if the estimate genuinely can't be computed (LLM call failed, no headroom found).
Example request:
const url = new URL(
"https://backend-staging.subsig.com/api/organisations/6ff8f7f6-1eb3-3525-be4a-3932c805afed/ai-visibility/onboarding/improvement"
);
const headers = {
"Authorization": "Bearer {token}",
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "POST",
headers,
}).then(response => response.json());Example response (200):
{
"data": {
"potential_visibility_pct": 45
}
}
Example response (200):
{
"data": {
"potential_visibility_pct": null
}
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
List suggested competitors
requires authentication
Returns up to 10 pending competitor suggestions for the organisation, most recent first. Populated by SuggestAiVisibilityCompetitorsJob (dispatched at onboarding and daily thereafter via ai-visibility:suggest-competitors).
Example request:
const url = new URL(
"https://backend-staging.subsig.com/api/organisations/660e8400-e29b-41d4-a716-446655440001/ai-visibility/competitors/suggested"
);
const headers = {
"Authorization": "Bearer {token}",
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "GET",
headers,
}).then(response => response.json());Example response (200):
{
"data": [
{
"id": 1,
"name": "Acme Corp",
"website": "acme.com",
"reason": "Direct competitor in the same market.",
"generated_at": "2026-07-09 00:00:00"
}
]
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Accept a suggested competitor
requires authentication
Creates a real, tracked competitor brand from the suggestion (source=manual, same as one added directly via the Competitor tab), backfills its historical mention data across every completed execution so far, and marks the suggestion accepted.
Example request:
const url = new URL(
"https://backend-staging.subsig.com/api/organisations/660e8400-e29b-41d4-a716-446655440001/ai-visibility/competitors/suggested/1/accept"
);
const headers = {
"Authorization": "Bearer {token}",
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "POST",
headers,
}).then(response => response.json());Example response (201):
{
"data": {
"brand_id": 42,
"name": "Acme Corp",
"website": "acme.com"
}
}
Example response (422):
{
"message": "This suggestion has already been accepted."
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Dismiss a suggested competitor
requires authentication
Marks the competitor suggestion as rejected. It will no longer appear in the suggestions
list, and the name will never be suggested again for this organisation.
Example request:
const url = new URL(
"https://backend-staging.subsig.com/api/organisations/660e8400-e29b-41d4-a716-446655440001/ai-visibility/competitors/suggested/1"
);
const headers = {
"Authorization": "Bearer {token}",
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "DELETE",
headers,
}).then(response => response.json());Example response (204):
Empty response
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
List topic suggestions
requires authentication
Returns up to 10 topic suggestions with status completed for the organisation,
ordered by most recent first.
Example request:
const url = new URL(
"https://backend-staging.subsig.com/api/organisations/660e8400-e29b-41d4-a716-446655440001/ai-visibility/topics/suggested"
);
const headers = {
"Authorization": "Bearer {token}",
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "GET",
headers,
}).then(response => response.json());Example response (200):
{
"data": [
{
"id": 1,
"name": "Feature Discovery",
"description": "Questions about discovering features in productivity tools",
"source": "llm",
"status": "completed",
"generated_at": "2026-06-01 00:00:00"
}
]
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Accept a topic suggestion
requires authentication
Creates a real topic from the suggestion and marks it as accepted.
Returns the new topic's ID and details.
Example request:
const url = new URL(
"https://backend-staging.subsig.com/api/organisations/660e8400-e29b-41d4-a716-446655440001/ai-visibility/topics/suggested/1/accept"
);
const headers = {
"Authorization": "Bearer {token}",
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "POST",
headers,
}).then(response => response.json());Example response (201):
{
"data": {
"topic_id": 5,
"name": "Feature Discovery",
"description": "Questions about discovering features in productivity tools"
}
}
Example response (422):
{
"message": "This suggestion has already been accepted."
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Dismiss a topic suggestion
requires authentication
Marks the topic suggestion as dismissed. It will no longer appear in the suggestions list.
Example request:
const url = new URL(
"https://backend-staging.subsig.com/api/organisations/660e8400-e29b-41d4-a716-446655440001/ai-visibility/topics/suggested/1"
);
const headers = {
"Authorization": "Bearer {token}",
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "DELETE",
headers,
}).then(response => response.json());Example response (204):
Empty response
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
AI Visibility - Topics
APIs for managing topics used to group AI Visibility prompts within an organisation.
List Topics
requires authentication
Example request:
const url = new URL(
"https://backend-staging.subsig.com/api/organisations/660e8400-e29b-41d4-a716-446655440001/ai-visibility/topics"
);
const headers = {
"Authorization": "Bearer {token}",
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "GET",
headers,
}).then(response => response.json());Example response (200):
{
"data": [
{
"id": 1,
"name": "Competitor comparisons",
"description": "Prompts comparing us against competitors",
"created_at": "2026-06-05T10:00:00.000000Z",
"updated_at": "2026-06-05T10:00:00.000000Z",
"active_count": 3,
"inactive_count": 1,
"suggested_count": 0
}
]
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Create Topic
requires authentication
Example request:
const url = new URL(
"https://backend-staging.subsig.com/api/organisations/660e8400-e29b-41d4-a716-446655440001/ai-visibility/topics"
);
const headers = {
"Authorization": "Bearer {token}",
"Content-Type": "application/json",
"Accept": "application/json",
};
let body = {
"name": "Competitor comparisons",
"description": "Prompts comparing us against competitors"
};
fetch(url, {
method: "POST",
headers,
body: JSON.stringify(body),
}).then(response => response.json());Example response (201):
{
"data": {
"id": 1,
"name": "Competitor comparisons",
"description": "Prompts comparing us against competitors",
"created_at": "2026-06-05T10:00:00.000000Z",
"updated_at": "2026-06-05T10:00:00.000000Z"
}
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Update Topic
requires authentication
Example request:
const url = new URL(
"https://backend-staging.subsig.com/api/organisations/660e8400-e29b-41d4-a716-446655440001/ai-visibility/topics/1"
);
const headers = {
"Authorization": "Bearer {token}",
"Content-Type": "application/json",
"Accept": "application/json",
};
let body = {
"name": "Competitor comparisons",
"description": "Prompts comparing us against competitors"
};
fetch(url, {
method: "PATCH",
headers,
body: JSON.stringify(body),
}).then(response => response.json());Example response (200):
{
"data": {
"id": 1,
"name": "Competitor comparisons",
"description": "Prompts comparing us against competitors",
"created_at": "2026-06-05T10:00:00.000000Z",
"updated_at": "2026-06-05T10:00:00.000000Z"
}
}
Example response (404):
{
"message": "Topic not found."
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Delete Topic
requires authentication
Example request:
const url = new URL(
"https://backend-staging.subsig.com/api/organisations/660e8400-e29b-41d4-a716-446655440001/ai-visibility/topics/1"
);
const headers = {
"Authorization": "Bearer {token}",
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "DELETE",
headers,
}).then(response => response.json());Example response (204):
Empty response
Example response (404):
{
"message": "Topic not found."
}
Example response (422):
{
"message": "Cannot delete a topic with active prompts assigned to it."
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
API Keys
APIs for managing production API keys for the current organisation's billing group. A key is always generated from (and owned by) the group's billing-anchor organisation, but each key is independently scoped to whichever subset of the group's organisations (workspaces) was chosen when it was created -- a billing group can hold several keys, each covering a different subset, for different integrations.
List every production API key generated for this billing group.
requires authentication
Returns every non-revoked key generated from the billing-anchor organisation of whichever organisation was selected via X-Organisation-Id, each with the workspaces it's scoped to. Plaintext API keys are never returned from this endpoint.
Example request:
const url = new URL(
"https://backend-staging.subsig.com/api/api-keys"
);
const headers = {
"Authorization": "Bearer {token}",
"X-Organisation-Id": "string required The organisation UUID.",
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "GET",
headers,
}).then(response => response.json());Example response (200):
{
"data": [
{
"uuid": "550e8400-e29b-41d4-a716-446655440000",
"environment": "live",
"status": "active",
"key": null,
"masked_key": "sk_live_****************************Ab12",
"last_four": "Ab12",
"plain_text_available": false,
"idempotent_replay": false,
"rotation_expires_at": null,
"expires_at": null,
"revoked_at": null,
"organisations": [
{
"uuid": "660e8400-e29b-41d4-a716-446655440000",
"name": "Acme Inc",
"is_primary": true
}
],
"created_at": "2026-06-05T10:00:00.000000Z",
"updated_at": "2026-06-05T10:00:00.000000Z"
}
]
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Generate a production API key scoped to a chosen subset of this billing group's organisations. Plaintext is returned only on first creation.
requires authentication
Example request:
const url = new URL(
"https://backend-staging.subsig.com/api/api-keys/generate"
);
const headers = {
"Authorization": "Bearer {token}",
"X-Organisation-Id": "string required The organisation UUID.",
"Idempotency-Key": "string optional Safe retry key for this generation request.",
"Content-Type": "application/json",
"Accept": "application/json",
};
let body = {
"name": "Claude Desktop MCP",
"confirmed": true,
"organisation_uuids": [
"architecto"
]
};
fetch(url, {
method: "POST",
headers,
body: JSON.stringify(body),
}).then(response => response.json());Example response (201):
{
"message": "API key generated successfully.",
"data": {
"uuid": "550e8400-e29b-41d4-a716-446655440000",
"environment": "live",
"status": "active",
"key": "sk_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
"masked_key": "sk_live_****************************Ab12",
"last_four": "Ab12",
"plain_text_available": true,
"idempotent_replay": false,
"organisations": [
{
"uuid": "660e8400-e29b-41d4-a716-446655440000",
"name": "Acme Inc",
"is_primary": true
}
],
"created_at": "2026-06-05T10:00:00.000000Z",
"updated_at": "2026-06-05T10:00:00.000000Z"
}
}
Example response (422, Workspace outside this billing group):
{
"message": "One or more selected workspaces are not part of this account's billing group.",
"error": {
"code": "organisation_not_in_billing_scope"
}
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Rotate one production API key. Plaintext is returned only on first rotation. The new key keeps the exact same workspace scope as the key it replaces.
requires authentication
Creates a new active production key immediately and keeps the previous key valid for 24 hours before it is auto-revoked.
Example request:
const url = new URL(
"https://backend-staging.subsig.com/api/api-keys/bb5acf44-73b3-4cf7-a3d8-9eeed414dc5a/rotate"
);
const headers = {
"Authorization": "Bearer {token}",
"X-Organisation-Id": "string required The organisation UUID.",
"Idempotency-Key": "string optional Safe retry key for this rotation request.",
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "POST",
headers,
}).then(response => response.json());Example response (201):
{
"message": "API key rotated successfully.",
"data": {
"current_key": {
"uuid": "660e8400-e29b-41d4-a716-446655440000",
"environment": "live",
"status": "active",
"key": "sk_live_yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy",
"masked_key": "sk_live_****************************Cd34",
"last_four": "Cd34",
"plain_text_available": true,
"idempotent_replay": false
},
"previous_key": {
"uuid": "550e8400-e29b-41d4-a716-446655440000",
"environment": "live",
"status": "rotating",
"key": null,
"masked_key": "sk_live_****************************Ab12",
"last_four": "Ab12",
"plain_text_available": false,
"idempotent_replay": false,
"rotation_expires_at": "2026-06-06T10:00:00.000000Z"
}
}
}
Example response (409, Idempotency key conflict):
{
"message": "This idempotency key was already used for a different API key action.",
"error": {
"code": "idempotency_key_conflict"
}
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Revoke one production API key immediately (and its still-rotating predecessor, if any).
requires authentication
The request must include the exact confirmation text REVOKE.
Example request:
const url = new URL(
"https://backend-staging.subsig.com/api/api-keys/bb5acf44-73b3-4cf7-a3d8-9eeed414dc5a/revoke"
);
const headers = {
"Authorization": "Bearer {token}",
"X-Organisation-Id": "string required The organisation UUID.",
"Content-Type": "application/json",
"Accept": "application/json",
};
let body = {
"confirmation": "REVOKE"
};
fetch(url, {
method: "POST",
headers,
body: JSON.stringify(body),
}).then(response => response.json());Example response (200):
{
"message": "API key revoked successfully.",
"data": {
"revoked_count": 1
}
}
Example response (422, Missing confirmation):
{
"message": "The selected confirmation is invalid.",
"errors": {
"confirmation": [
"The selected confirmation is invalid."
]
}
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Replace one production API key's workspace scope entirely -- adds any newly-listed workspace and drops any workspace no longer listed, in one call. Does not affect the key itself (its value, status, or expiry).
requires authentication
Example request:
const url = new URL(
"https://backend-staging.subsig.com/api/api-keys/bb5acf44-73b3-4cf7-a3d8-9eeed414dc5a/workspaces"
);
const headers = {
"Authorization": "Bearer {token}",
"X-Organisation-Id": "string required The organisation UUID.",
"Content-Type": "application/json",
"Accept": "application/json",
};
let body = {
"organisation_uuids": [
"architecto"
]
};
fetch(url, {
method: "PATCH",
headers,
body: JSON.stringify(body),
}).then(response => response.json());Example response (200):
{
"message": "API key workspaces updated successfully.",
"data": {
"uuid": "550e8400-e29b-41d4-a716-446655440000",
"organisations": [
{
"uuid": "660e8400-e29b-41d4-a716-446655440000",
"name": "Acme Inc",
"is_primary": true
},
{
"uuid": "770e8400-e29b-41d4-a716-446655440000",
"name": "Acme Support",
"is_primary": false
}
]
}
}
Example response (422, Workspace outside this billing group):
{
"message": "One or more selected workspaces are not part of this account's billing group.",
"error": {
"code": "organisation_not_in_billing_scope"
}
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
AppSumo
APIs for AppSumo lifetime-deal tier comparison and license redemption
Read-only preview so the FE can show "you're redeeming Tier 2 -- here's what you get" before the user commits. No writes.
Example request:
const url = new URL(
"https://backend-staging.subsig.com/api/appsumo/licenses/validate"
);
const headers = {
"Content-Type": "application/json",
"Accept": "application/json",
};
let body = {
"license_key": "architecto"
};
fetch(url, {
method: "POST",
headers,
body: JSON.stringify(body),
}).then(response => response.json());Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
POST api/appsumo/licenses/redeem
Example request:
const url = new URL(
"https://backend-staging.subsig.com/api/appsumo/licenses/redeem"
);
const headers = {
"Content-Type": "application/json",
"Accept": "application/json",
};
let body = {
"license_key": "architecto"
};
fetch(url, {
method: "POST",
headers,
body: JSON.stringify(body),
}).then(response => response.json());Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
GET api/appsumo/licenses/current
Example request:
const url = new URL(
"https://backend-staging.subsig.com/api/appsumo/licenses/current"
);
const headers = {
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "GET",
headers,
}).then(response => response.json());Example response (401):
Show headers
cache-control: no-cache, private
content-type: application/json
vary: Origin
{
"message": "Unauthenticated."
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
List AppSumo tiers
Unauthenticated tier comparison listing (name/price/reviews/mentions/credits per tier) for a marketing/comparison page -- unlike GET /subscription-plans, deliberately does not filter on is_active, since every AppSumo tier is seeded with is_active=false (see AppSumoPlanSeeder) precisely so it's excluded from that general, checkout-oriented listing. Ordered tier 1 -> 4 by config('billing.appsumo_tier_price_ids') key order, not by id.
Example request:
const url = new URL(
"https://backend-staging.subsig.com/api/appsumo/plans"
);
const headers = {
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "GET",
headers,
}).then(response => response.json());Example response (200):
{
"data": [
{
"tier": 1,
"stripe_price_id": "price_internal_appsumo_tier1",
"name": "AppSumo Lifetime - Tier 1",
"description": "AppSumo lifetime deal, Tier 1 - 1,200 reviews, 1,000 mentions, 10 tracked prompts, 240 lifetime AI credits",
"amount": 4900,
"currency": "usd",
"plan_threshold": {}
}
]
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Endpoints
Send a reset link to the given user.
Example request:
const url = new URL(
"https://backend-staging.subsig.com/api/auth/forgot-password"
);
const headers = {
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "POST",
headers,
}).then(response => response.json());Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Reset the user's password.
Example request:
const url = new URL(
"https://backend-staging.subsig.com/api/auth/reset-password"
);
const headers = {
"Content-Type": "application/json",
"Accept": "application/json",
};
let body = {
"token": "architecto"
};
fetch(url, {
method: "POST",
headers,
body: JSON.stringify(body),
}).then(response => response.json());Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Bulk Deactivate Brands (Temporary)
Disabled unless TEMP_BULK_BRAND_DEACTIVATE_ENABLED is set. When TEMP_BULK_BRAND_DEACTIVATE_TOKEN is set, the request must include a matching X-Temporary-Bulk-Token header.
Example request:
const url = new URL(
"https://backend-staging.subsig.com/api/brands/deactivate-bulk-temporary"
);
const headers = {
"X-Temporary-Bulk-Token": "{token}",
"Content-Type": "application/json",
"Accept": "application/json",
};
let body = {
"chunk_size": 1,
"dry_run": true
};
fetch(url, {
method: "POST",
headers,
body: JSON.stringify(body),
}).then(response => response.json());Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Send a test webhook (manual trigger).
Example request:
const url = new URL(
"https://backend-staging.subsig.com/api/outbound-webhooks/architecto/test"
);
const headers = {
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "POST",
headers,
}).then(response => response.json());Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
List outbound webhooks for the current organisation.
Example request:
const url = new URL(
"https://backend-staging.subsig.com/api/outbound-webhooks"
);
const headers = {
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "GET",
headers,
}).then(response => response.json());Example response (401):
Show headers
cache-control: no-cache, private
content-type: application/json
vary: Origin
{
"message": "Unauthenticated."
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Create an outbound webhook.
Example request:
const url = new URL(
"https://backend-staging.subsig.com/api/outbound-webhooks"
);
const headers = {
"Content-Type": "application/json",
"Accept": "application/json",
};
let body = {
"url": "http:\/\/www.bailey.biz\/quos-velit-et-fugiat-sunt-nihil-accusantium-harum.html",
"headers": [
"i"
],
"auth_type": "none",
"auth_token": "k",
"secret": "h",
"is_active": true
};
fetch(url, {
method: "POST",
headers,
body: JSON.stringify(body),
}).then(response => response.json());Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Show a single outbound webhook.
Example request:
const url = new URL(
"https://backend-staging.subsig.com/api/outbound-webhooks/architecto"
);
const headers = {
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "GET",
headers,
}).then(response => response.json());Example response (401):
Show headers
cache-control: no-cache, private
content-type: application/json
vary: Origin
{
"message": "Unauthenticated."
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Update an outbound webhook.
Example request:
const url = new URL(
"https://backend-staging.subsig.com/api/outbound-webhooks/architecto"
);
const headers = {
"Content-Type": "application/json",
"Accept": "application/json",
};
let body = {
"url": "http:\/\/www.bailey.biz\/quos-velit-et-fugiat-sunt-nihil-accusantium-harum.html",
"headers": [
"i"
],
"auth_type": "none",
"auth_token": "k",
"secret": "h",
"is_active": false
};
fetch(url, {
method: "PUT",
headers,
body: JSON.stringify(body),
}).then(response => response.json());Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Delete an outbound webhook.
Example request:
const url = new URL(
"https://backend-staging.subsig.com/api/outbound-webhooks/architecto"
);
const headers = {
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "DELETE",
headers,
}).then(response => response.json());Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Lets the frontend resolve the org's own tracked brand_id on its own -- every agent endpoint requires it (see AgentRegistry::baseInputSchema()), but there's exactly one is_own_brand row per org and nothing today returns just that id without also computing a full competitor/metrics payload (AiVisibilityCompetitorController::index).
Example request:
const url = new URL(
"https://backend-staging.subsig.com/api/organisations/205ae76c-bd73-426b-a36a-8338353b9542/agents/own-brand"
);
const headers = {
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "GET",
headers,
}).then(response => response.json());Example response (401):
Show headers
cache-control: no-cache, private
content-type: application/json
vary: Origin
{
"message": "Unauthenticated."
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Populates the run form's "Try our AI Recommendations" chips with real, org-specific observations (see AgentSuggestedPromptGenerator) instead of the agent's static suggestedPrompts() copy. Meaningful for root_cause_analysis and competitive_benchmark -- other agents fall back to their registry-declared suggestedPrompts() unchanged since nothing generates dynamic ones for them yet.
Example request:
const url = new URL(
"https://backend-staging.subsig.com/api/organisations/205ae76c-bd73-426b-a36a-8338353b9542/agents/architecto/suggested-prompts"
);
const headers = {
"Content-Type": "application/json",
"Accept": "application/json",
};
let body = {
"brand_id": 16,
"timeframe": "90d",
"data_source": "openai"
};
fetch(url, {
method: "GET",
headers,
body: JSON.stringify(body),
}).then(response => response.json());Example response (401):
Show headers
cache-control: no-cache, private
content-type: application/json
vary: Origin
{
"message": "Unauthenticated."
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
POST api/organisations/{organisation_uuid}/agents/{key}/runs
Example request:
const url = new URL(
"https://backend-staging.subsig.com/api/organisations/205ae76c-bd73-426b-a36a-8338353b9542/agents/architecto/runs"
);
const headers = {
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "POST",
headers,
}).then(response => response.json());Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Ticket 5's Recent Outputs listing endpoint -- never built alongside show()/download(), so that page has been stuck on hardcoded demo rows. Matches the mockup's columns exactly: Focus Topics, Agents, Status, Date created. Filterable by agent_type/status per Ticket 5's definition of done; paginated the same way AiVisibilityCompetitorController::index() is.
Example request:
const url = new URL(
"https://backend-staging.subsig.com/api/organisations/205ae76c-bd73-426b-a36a-8338353b9542/agent-runs"
);
const headers = {
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "GET",
headers,
}).then(response => response.json());Example response (401):
Show headers
cache-control: no-cache, private
content-type: application/json
vary: Origin
{
"message": "Unauthenticated."
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Pulled forward from Ticket 5's full listing/polling scope -- the Root Cause Analysis run page needs to poll run_plan step-by-step progress and read result once completed, and nothing else in Ticket 5 (agent listing, Recent Outputs table) is needed for that yet.
Example request:
const url = new URL(
"https://backend-staging.subsig.com/api/organisations/205ae76c-bd73-426b-a36a-8338353b9542/agent-runs/6"
);
const headers = {
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "GET",
headers,
}).then(response => response.json());Example response (401):
Show headers
cache-control: no-cache, private
content-type: application/json
vary: Origin
{
"message": "Unauthenticated."
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Ticket 4/5's own definition of done requires this endpoint, but it was never actually built alongside RenderAgentReportPdfJob -- the PDF existed with nothing to fetch it through. Never returns a permanent URL, only a signed one generated on demand.
Backs both the frontend's Download button (open-and-forget) and Share button (copy the link to hand to someone without a Subsig login) -- one URL serves both, so the expiry has to be long enough for a genuinely shared link to still work hours later, not just a quick click. 24h, matching this feature's other "good enough, not permanent" TTLs (see AgentSuggestedPromptGenerator's suggestion cache).
Example request:
const url = new URL(
"https://backend-staging.subsig.com/api/organisations/205ae76c-bd73-426b-a36a-8338353b9542/agent-runs/6/download"
);
const params = {
"format": "docx",
};
Object.keys(params)
.forEach(key => url.searchParams.append(key, params[key]));
const headers = {
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "GET",
headers,
}).then(response => response.json());Example response (401):
Show headers
cache-control: no-cache, private
content-type: application/json
vary: Origin
{
"message": "Unauthenticated."
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Radar Usage
Returns this month's SerpApi spend for the organisation's passive brand radar, broken down against the plan budget cap (null when uncapped).
Example request:
const url = new URL(
"https://backend-staging.subsig.com/api/organisations/6ff8f7f6-1eb3-3525-be4a-3932c805afed/ai-visibility/radar/usage"
);
const headers = {
"Authorization": "Bearer {token}",
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "GET",
headers,
}).then(response => response.json());Example response (401):
Show headers
cache-control: no-cache, private
content-type: application/json
vary: Origin
{
"message": "Unauthenticated."
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Exchange the single-use `code` AppSumo redirects buyers with (after "Activate now") for a license_key + status. Unauthenticated -- a brand-new buyer has no subsig session yet.
AppSumo's own status field flips to "active" as soon as our webhook responds 200 to their
activate event -- which happens independently of whether this specific person has ever
been through our OAuth flow (AppSumoWebhookController always 200s that event). So it does
NOT mean "this browser's user already has a subsig account" -- every first-time buyer would
incorrectly see "active" before ever signing up. already_redeemed is the real signal:
whether our own AppSumoLicense record has an organisation attached yet.
Frontend branches on already_redeemed for new-vs-returning, and on status === "deactivated"
(still AppSumo's own billing-state field, legitimately authoritative for this one case) to
block a cancelled/refunded license outright regardless of redemption state.
Example request:
const url = new URL(
"https://backend-staging.subsig.com/api/appsumo/oauth/exchange"
);
const headers = {
"Content-Type": "application/json",
"Accept": "application/json",
};
let body = {
"code": "architecto"
};
fetch(url, {
method: "POST",
headers,
body: JSON.stringify(body),
}).then(response => response.json());Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
POST api/webhook
Example request:
const url = new URL(
"https://backend-staging.subsig.com/api/webhook"
);
const headers = {
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "POST",
headers,
}).then(response => response.json());Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
GET api/v1/workspaces
Example request:
const url = new URL(
"https://backend-staging.subsig.com/api/v1/workspaces"
);
const headers = {
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "GET",
headers,
}).then(response => response.json());Example response (401):
Show headers
cache-control: no-cache, private
content-type: application/json
x-request-id: 230a42d1-273f-49db-9e2b-7bc30ab9c025
vary: Origin
{
"error": {
"code": "unauthorized",
"message": "Unauthorized.",
"docs_url": "https://docs.subsig.com/errors/unauthorized",
"request_id": "230a42d1-273f-49db-9e2b-7bc30ab9c025"
}
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
GET api/v1/ai-visibility/topics
Example request:
const url = new URL(
"https://backend-staging.subsig.com/api/v1/ai-visibility/topics"
);
const headers = {
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "GET",
headers,
}).then(response => response.json());Example response (401):
Show headers
cache-control: no-cache, private
content-type: application/json
x-request-id: 0e8d9117-2fd9-45a1-a933-469f26608487
vary: Origin
{
"error": {
"code": "unauthorized",
"message": "Unauthorized.",
"docs_url": "https://docs.subsig.com/errors/unauthorized",
"request_id": "0e8d9117-2fd9-45a1-a933-469f26608487"
}
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
GET api/v1/ai-visibility/prompts
Example request:
const url = new URL(
"https://backend-staging.subsig.com/api/v1/ai-visibility/prompts"
);
const headers = {
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "GET",
headers,
}).then(response => response.json());Example response (401):
Show headers
cache-control: no-cache, private
content-type: application/json
x-request-id: bad3a0be-5a89-43aa-bf4d-67742229db96
vary: Origin
{
"error": {
"code": "unauthorized",
"message": "Unauthorized.",
"docs_url": "https://docs.subsig.com/errors/unauthorized",
"request_id": "bad3a0be-5a89-43aa-bf4d-67742229db96"
}
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
GET api/v1/ai-visibility/prompts/{prompt}
Example request:
const url = new URL(
"https://backend-staging.subsig.com/api/v1/ai-visibility/prompts/564"
);
const headers = {
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "GET",
headers,
}).then(response => response.json());Example response (401):
Show headers
cache-control: no-cache, private
content-type: application/json
x-request-id: 8611e2c8-ae7b-46cc-82fa-e3f3d54ab07d
vary: Origin
{
"error": {
"code": "unauthorized",
"message": "Unauthorized.",
"docs_url": "https://docs.subsig.com/errors/unauthorized",
"request_id": "8611e2c8-ae7b-46cc-82fa-e3f3d54ab07d"
}
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Every brand tracked by AI Visibility (own brand + registered competitors), ranked, with visibility %/share-of-voice %/average position for each — the discovery step before digging into a specific brand via show() or into sentiment/citations for it. Built on the same AiVisibilityBrandRankService::brandMetrics()/AiVisibilityRanker::rankComposite() combination the session-auth AiVisibilityCompetitorController::index() (Competitor tab) already uses.
Example request:
const url = new URL(
"https://backend-staging.subsig.com/api/v1/ai-visibility/brands"
);
const headers = {
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "GET",
headers,
}).then(response => response.json());Example response (401):
Show headers
cache-control: no-cache, private
content-type: application/json
x-request-id: 40edd226-b7ff-4aed-a7d1-1a7507403c9f
vary: Origin
{
"error": {
"code": "unauthorized",
"message": "Unauthorized.",
"docs_url": "https://docs.subsig.com/errors/unauthorized",
"request_id": "40edd226-b7ff-4aed-a7d1-1a7507403c9f"
}
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
GET api/v1/ai-visibility/overview
Example request:
const url = new URL(
"https://backend-staging.subsig.com/api/v1/ai-visibility/overview"
);
const headers = {
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "GET",
headers,
}).then(response => response.json());Example response (401):
Show headers
cache-control: no-cache, private
content-type: application/json
x-request-id: ab3d98b7-cdef-4320-9659-7430f33fdfd7
vary: Origin
{
"error": {
"code": "unauthorized",
"message": "Unauthorized.",
"docs_url": "https://docs.subsig.com/errors/unauthorized",
"request_id": "ab3d98b7-cdef-4320-9659-7430f33fdfd7"
}
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Detail for a single brand tracked by AI Visibility: own brand or a specific competitor, resolved by name against the SAME ai_visibility_brands-backed set index() draws its competitors list from. The 404 below says so explicitly rather than leaving the caller to guess why a name wasn't found.
Example request:
const url = new URL(
"https://backend-staging.subsig.com/api/v1/ai-visibility/brand"
);
const headers = {
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "GET",
headers,
}).then(response => response.json());Example response (401):
Show headers
cache-control: no-cache, private
content-type: application/json
x-request-id: 096ad0b6-fccd-4dbf-b4a4-d1de13b84f20
vary: Origin
{
"error": {
"code": "unauthorized",
"message": "Unauthorized.",
"docs_url": "https://docs.subsig.com/errors/unauthorized",
"request_id": "096ad0b6-fccd-4dbf-b4a4-d1de13b84f20"
}
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
GET api/v1/ai-visibility/platforms
Example request:
const url = new URL(
"https://backend-staging.subsig.com/api/v1/ai-visibility/platforms"
);
const headers = {
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "GET",
headers,
}).then(response => response.json());Example response (401):
Show headers
cache-control: no-cache, private
content-type: application/json
x-request-id: cd19ac92-ee92-4a8e-8c50-0d9a091a3ca1
vary: Origin
{
"error": {
"code": "unauthorized",
"message": "Unauthorized.",
"docs_url": "https://docs.subsig.com/errors/unauthorized",
"request_id": "cd19ac92-ee92-4a8e-8c50-0d9a091a3ca1"
}
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
GET api/v1/ai-visibility/sentiment
Example request:
const url = new URL(
"https://backend-staging.subsig.com/api/v1/ai-visibility/sentiment"
);
const headers = {
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "GET",
headers,
}).then(response => response.json());Example response (401):
Show headers
cache-control: no-cache, private
content-type: application/json
x-request-id: 27c8835c-12da-440d-a236-7fb3bb4009b1
vary: Origin
{
"error": {
"code": "unauthorized",
"message": "Unauthorized.",
"docs_url": "https://docs.subsig.com/errors/unauthorized",
"request_id": "27c8835c-12da-440d-a236-7fb3bb4009b1"
}
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
GET api/v1/ai-visibility/citations
Example request:
const url = new URL(
"https://backend-staging.subsig.com/api/v1/ai-visibility/citations"
);
const headers = {
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "GET",
headers,
}).then(response => response.json());Example response (401):
Show headers
cache-control: no-cache, private
content-type: application/json
x-request-id: 61a46285-b4b6-44a2-9978-d0229d14eb3c
vary: Origin
{
"error": {
"code": "unauthorized",
"message": "Unauthorized.",
"docs_url": "https://docs.subsig.com/errors/unauthorized",
"request_id": "61a46285-b4b6-44a2-9978-d0229d14eb3c"
}
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
GET api/v1/ai-visibility/status
Example request:
const url = new URL(
"https://backend-staging.subsig.com/api/v1/ai-visibility/status"
);
const headers = {
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "GET",
headers,
}).then(response => response.json());Example response (401):
Show headers
cache-control: no-cache, private
content-type: application/json
x-request-id: 505e799b-99dd-444b-a3c2-9bc91b3836e3
vary: Origin
{
"error": {
"code": "unauthorized",
"message": "Unauthorized.",
"docs_url": "https://docs.subsig.com/errors/unauthorized",
"request_id": "505e799b-99dd-444b-a3c2-9bc91b3836e3"
}
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
GET api/v1/agent-reports
Example request:
const url = new URL(
"https://backend-staging.subsig.com/api/v1/agent-reports"
);
const headers = {
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "GET",
headers,
}).then(response => response.json());Example response (401):
Show headers
cache-control: no-cache, private
content-type: application/json
x-request-id: 9301ec08-32e8-4e59-b401-551c6f40f15e
vary: Origin
{
"error": {
"code": "unauthorized",
"message": "Unauthorized.",
"docs_url": "https://docs.subsig.com/errors/unauthorized",
"request_id": "9301ec08-32e8-4e59-b401-551c6f40f15e"
}
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
GET api/v1/agent-reports/{run_id}
Example request:
const url = new URL(
"https://backend-staging.subsig.com/api/v1/agent-reports/6"
);
const headers = {
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "GET",
headers,
}).then(response => response.json());Example response (401):
Show headers
cache-control: no-cache, private
content-type: application/json
x-request-id: 0a83ae38-983c-476a-8c89-2966e88aeb62
vary: Origin
{
"error": {
"code": "unauthorized",
"message": "Unauthorized.",
"docs_url": "https://docs.subsig.com/errors/unauthorized",
"request_id": "0a83ae38-983c-476a-8c89-2966e88aeb62"
}
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
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:
- completes onboarding, or
- if a matching non-competitor project already exists under the user account, skips onboarding and returns a magic link for direct sign-in flow.
Example request:
const url = new URL(
"https://backend-staging.subsig.com/api/external/claim-onboarding"
);
const headers = {
"Content-Type": "application/json",
"Accept": "application/json",
};
let body = {
"name": "John Doe",
"email": "john@example.com",
"organisation_name": "Acme Inc",
"product_name": "Acme CRM",
"product_website": "https:\/\/acme.com",
"product_logo": "https:\/\/cdn.example.com\/logo.png"
};
fetch(url, {
method: "POST",
headers,
body: JSON.stringify(body),
}).then(response => response.json());Example response (201):
{
"message": "Onboarding completed successfully.",
"token": "1|abc123...",
"magic_link": "https://subsig-frontend.vercel.app/login?qid=1%7Cabc123...&is_claim_profile=1&org_id=550e8400-e29b-41d4-a716-446655440000",
"data": {
"organisation_uuid": "550e8400-e29b-41d4-a716-446655440000",
"product_uuid": "660e8400-e29b-41d4-a716-446655440001"
}
}
Example response (401):
{
"message": "Unauthorized."
}
Example response (422):
{
"message": "Provided organisation does not match the existing claimed profile context.",
"errors": {
"organisation_name": [
"Organisation does not match existing profile project."
]
}
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Google Analytics Integration
Org-scoped via the X-Organisation-Id header (see ResolveOrganisation middleware), same
pattern as the Slack integration -- no organisation_uuid path segment, $request->organisation
is already resolved and access-checked by the time these methods run.
Connect Google Analytics Account
Exchanges an authorization code (obtained by the frontend's own OAuth flow) for tokens and creates or updates the connection. Supports multiple Google accounts per organisation -- a second call with a different account creates a second connection, not a collision.
Example request:
const url = new URL(
"https://backend-staging.subsig.com/api/integrations/google-analytics/connect"
);
const headers = {
"Content-Type": "application/json",
"Accept": "application/json",
};
let body = {
"code": "4\/0Ab_...",
"redirect_uri": "architecto"
};
fetch(url, {
method: "POST",
headers,
body: JSON.stringify(body),
}).then(response => response.json());Example response (201):
{
"data": {
"id": "...",
"account_email": "user@example.com",
"status": "connected",
"connected_at": "...",
"selected_property": null
}
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
List Google Analytics Connections
No properties included -- lazy-loaded separately per connection when its row is expanded.
Example request:
const url = new URL(
"https://backend-staging.subsig.com/api/integrations/google-analytics/connections"
);
const headers = {
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "GET",
headers,
}).then(response => response.json());Example response (401):
Show headers
cache-control: no-cache, private
content-type: application/json
vary: Origin
{
"message": "Unauthenticated."
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
List GA4 Properties For A Connection
Fetched live from Google on first row-expand, briefly cached after (see GoogleAnalyticsService::listProperties()). Not eagerly included in the connections list.
Example request:
const url = new URL(
"https://backend-staging.subsig.com/api/integrations/google-analytics/connections/architecto/properties"
);
const headers = {
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "GET",
headers,
}).then(response => response.json());Example response (200):
{
"data": [
{
"id": "properties/123",
"name": "Subsig - GA4",
"domain": "subscribed.fyi",
"sessions_per_month": 12400,
"matches_site": true
}
]
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Select GA4 Property For A Connection
Validated against the connection's own real property list -- a customer can only select a property their own connected account actually has. Historical rows already collected under a previously selected property are never reassigned by changing this.
Example request:
const url = new URL(
"https://backend-staging.subsig.com/api/integrations/google-analytics/connections/architecto/property"
);
const headers = {
"Content-Type": "application/json",
"Accept": "application/json",
};
let body = {
"property_id": "architecto"
};
fetch(url, {
method: "PATCH",
headers,
body: JSON.stringify(body),
}).then(response => response.json());Example response (200):
{
"data": {
"id": "...",
"account_email": "...",
"status": "connected",
"connected_at": "...",
"selected_property": {
"id": "properties/123",
"name": "Subsig - GA4"
}
}
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Disconnect Google Analytics Account
Best-effort revoke on Google's side, then removes the connection regardless of whether the revoke call succeeds.
Example request:
const url = new URL(
"https://backend-staging.subsig.com/api/integrations/google-analytics/connections/architecto"
);
const headers = {
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "DELETE",
headers,
}).then(response => response.json());Example response (204):
Empty response
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Home
Feeds the standalone Recommendations page, AI-Visibility-only. All the actual signal-gathering and recommendation logic lives in AiVisibilityRecommendationService, shared by nothing else, so this stays a thin request/response adapter.
GET api/organisations/{organisation_uuid}/agents/recommendations
Example request:
const url = new URL(
"https://backend-staging.subsig.com/api/organisations/205ae76c-bd73-426b-a36a-8338353b9542/agents/recommendations"
);
const headers = {
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "GET",
headers,
}).then(response => response.json());Example response (401):
Show headers
cache-control: no-cache, private
content-type: application/json
vary: Origin
{
"message": "Unauthenticated."
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Invites
APIs for managing invites
Validate Invite
Validate an invite token and return the associated email if the invite is valid. Also returns whether a user account already exists for the invite email, so the frontend can route to login vs signup. This endpoint is public and does not require authentication.
Example request:
const url = new URL(
"https://backend-staging.subsig.com/api/invites/validate/abc123def456..."
);
const headers = {
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "GET",
headers,
}).then(response => response.json());Example response (200):
{
"email": "user@example.com"
}
Example response (404, Invite not found):
{
"message": "Invite not found."
}
Example response (410, Invite expired):
{
"message": "This invite has expired."
}
Example response (410, Invite no longer valid):
{
"message": "This invite is no longer valid."
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Accept Invite (Existing User)
requires authentication
Accept an invitation as an already-registered, authenticated user. The authenticated user's email must match the invite email. The user will be added to the organisation or project specified in the invite.
Example request:
const url = new URL(
"https://backend-staging.subsig.com/api/invites/accept-invite"
);
const headers = {
"Authorization": "Bearer 1|abc123...",
"Content-Type": "application/json",
"Accept": "application/json",
};
let body = {
"token": "eEtgjrcdtubjCu4817MfGiimvC2DQLBgaI7LpY1g5kdDMK5wJlQank7ZJ6PWurmb"
};
fetch(url, {
method: "POST",
headers,
body: JSON.stringify(body),
}).then(response => response.json());Example response (200):
{
"message": "Invite accepted successfully.",
"data": {
"organisation": {
"uuid": "550e8400-e29b-41d4-a716-446655440000",
"name": "Acme Inc"
}
}
}
Example response (403, Email mismatch):
{
"message": "This invite was sent to a different email address.",
"errors": {
"email": [
"The invite email does not match your account email."
]
}
}
Example response (404, Token not found):
{
"message": "Invite not found.",
"errors": {
"token": [
"The invite token is invalid or does not exist."
]
}
}
Example response (410, Invite expired):
{
"message": "Invite has expired.",
"errors": {
"token": [
"This invite has expired. Please request a new invitation."
]
}
}
Example response (410, Invite already accepted):
{
"message": "Invite has already been accepted.",
"errors": {
"token": [
"This invite has already been accepted."
]
}
}
Example response (422, Already a member):
{
"message": "You are already a member of this organisation.",
"errors": {
"organisation": [
"You are already a member of this organisation."
]
}
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
List Invites
requires authentication
Get invites based on context:
- Organisation level (no project_id): Returns all organisation invites + all project invites for the organisation
- Project level (with project_id): Returns only invites for the specified project
Example request:
const url = new URL(
"https://backend-staging.subsig.com/api/invites"
);
const params = {
"organisation_id": "550e8400-e29b-41d4-a716-446655440000",
"project_id": "660e8400-e29b-41d4-a716-446655440001",
};
Object.keys(params)
.forEach(key => url.searchParams.append(key, params[key]));
const headers = {
"Authorization": "Bearer 1|abc123...",
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "GET",
headers,
}).then(response => response.json());Example response (200):
[
{
"id": 1,
"email": "user@example.com",
"type": "organisation",
"status": "pending",
"expires_at": "2025-12-31T10:00:00.000000Z",
"organisation_id": "550e8400-e29b-41d4-a716-446655440000",
"organisation": {
"uuid": "550e8400-e29b-41d4-a716-446655440000",
"name": "Acme Corp"
},
"project_id": null,
"project": null,
"inviter": {
"name": "John Admin",
"email": "admin@acme.com"
},
"created_at": "2025-12-24T10:00:00.000000Z",
"updated_at": "2025-12-24T10:00:00.000000Z"
},
{
"id": 2,
"email": "developer@example.com",
"type": "project",
"status": "pending",
"expires_at": "2025-12-31T10:00:00.000000Z",
"organisation_id": null,
"organisation": null,
"project_id": "660e8400-e29b-41d4-a716-446655440001",
"project": {
"uuid": "660e8400-e29b-41d4-a716-446655440001",
"name": "My Product"
},
"inviter": {
"name": "John Admin",
"email": "admin@acme.com"
},
"created_at": "2025-12-24T10:00:00.000000Z",
"updated_at": "2025-12-24T10:00:00.000000Z"
}
]
Example response (403, No access to project):
{
"message": "You do not have access to this project."
}
Example response (403, No access to organisation):
{
"message": "You do not have access to this organisation."
}
Example response (404, Project not found):
{
"message": "Project not found."
}
Example response (404, Organisation not found):
{
"message": "Organisation not found."
}
Example response (422, No organisation context):
{
"message": "No organisation context found.",
"errors": {
"organisation": [
"Please select an organisation or set current organisation."
]
}
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Create Invite
requires authentication
Create a new invite for a user to join an organisation or project. For organisation invites, the organisation is determined from the provided organisation UUID.
Example request:
const url = new URL(
"https://backend-staging.subsig.com/api/invites"
);
const headers = {
"Authorization": "Bearer 1|abc123...",
"Content-Type": "application/json",
"Accept": "application/json",
};
let body = {
"email": "user@example.com",
"type": "organisation",
"organisation_id": "550e8400-e29b-41d4-a716-446655440000",
"project_id": "660e8400-e29b-41d4-a716-446655440001"
};
fetch(url, {
method: "POST",
headers,
body: JSON.stringify(body),
}).then(response => response.json());Example response (201):
{
"message": "Invite created successfully.",
"data": {
"id": 1,
"email": "user@example.com",
"type": "organisation",
"status": "pending",
"token": "abc123...",
"expires_at": "2025-12-31T10:00:00.000000Z",
"organisation_id": "550e8400-e29b-41d4-a716-446655440000",
"project_id": null,
"created_at": "2025-12-24T10:00:00.000000Z",
"updated_at": "2025-12-24T10:00:00.000000Z"
}
}
Example response (403, Inviter not whitelisted):
{
"message": "You are not permitted to send invites at this time."
}
Example response (422, No organisation context):
{
"message": "No organisation context found.",
"errors": {
"organisation": [
"Please select an organisation or set current organisation."
]
}
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Delete Invite
requires authentication
Delete an invite. Only the inviter or organisation owners can delete invites.
Example request:
const url = new URL(
"https://backend-staging.subsig.com/api/invites/1"
);
const headers = {
"Authorization": "Bearer 1|abc123...",
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "DELETE",
headers,
}).then(response => response.json());Example response (200):
{
"message": "Invite deleted successfully."
}
Example response (403, Not authorized):
{
"message": "You are not authorized to delete this invite."
}
Example response (404, Invite not found):
{
"message": "Invite not found."
}
Example response (422, No organisation context):
{
"message": "No organisation context found.",
"errors": {
"organisation": [
"Please select an organisation or set current organisation."
]
}
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Links
APIs for managing project links
List Project Links
requires authentication
Get all links associated with a specific project.
Example request:
const url = new URL(
"https://backend-staging.subsig.com/api/projects/660e8400-e29b-41d4-a716-446655440001/links"
);
const headers = {
"Authorization": "Bearer 1|abc123...",
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "GET",
headers,
}).then(response => response.json());Example response (200):
{
"data": [
{
"uuid": "550e8400-e29b-41d4-a716-446655440000",
"platform": "g2",
"url": "https://g2.com/products/acme",
"enabled": true,
"created_at": "2026-01-22T10:00:00.000000Z",
"updated_at": "2026-01-22T10:00:00.000000Z"
}
]
}
Example response (403, No Access):
{
"message": "You do not have access to this project."
}
Example response (404, Project Not Found):
{
"message": "Project not found."
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Onboarding
APIs for user onboarding flow
Complete Onboarding
requires authentication
Create an organisation and project in a single step during onboarding. The authenticated user becomes the organisation owner.
Example request:
const url = new URL(
"https://backend-staging.subsig.com/api/onboarding"
);
const headers = {
"Authorization": "Bearer 1|abc123...",
"Content-Type": "application/json",
"Accept": "application/json",
};
let body = {
"organisation_name": "Acme Inc",
"product_name": "Acme CRM",
"product_website": "https:\/\/acme.com",
"product_logo": "https:\/\/cdn.brandfetch.io\/acme.com\/fallback\/lettermark\/icon?c=BRANDFETCH_CLIENT_ID",
"threshold_consumption_date": "last_14_days. Allowed: last_14_days, last_3_months, last_6_months, last_12_months, last_2_years, last_3_years, all_time"
};
fetch(url, {
method: "POST",
headers,
body: JSON.stringify(body),
}).then(response => response.json());Example response (201):
{
"message": "Onboarding completed successfully.",
"data": {
"organisation_uuid": "550e8400-e29b-41d4-a716-446655440000",
"product_uuid": "660e8400-e29b-41d4-a716-446655440001",
"organisation": {
"uuid": "550e8400-e29b-41d4-a716-446655440000",
"name": "Acme Inc",
"website": "https://acme.com",
"threshold_consumption_date": "2026-05-11"
},
"project": {
"uuid": "660e8400-e29b-41d4-a716-446655440001",
"product_name": "Acme CRM",
"product_website": "https://acme.com",
"product_logo": "https://cdn.brandfetch.io/acme.com/fallback/lettermark/icon?c=BRANDFETCH_CLIENT_ID"
}
}
}
Example response (422, Validation error):
{
"message": "The organisation name field is required.",
"errors": {
"organisation_name": [
"The organisation name field is required."
]
}
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Update Onboarding
requires authentication
Update the current organisation and project for the authenticated user.
Example request:
const url = new URL(
"https://backend-staging.subsig.com/api/onboarding"
);
const headers = {
"Authorization": "Bearer 1|abc123...",
"Content-Type": "application/json",
"Accept": "application/json",
};
let body = {
"organisation_name": "Acme Inc",
"product_name": "Acme CRM",
"product_website": "https:\/\/acme.com",
"product_logo": "https:\/\/cdn.brandfetch.io\/acme.com\/fallback\/lettermark\/icon?c=BRANDFETCH_CLIENT_ID",
"threshold_consumption_date": "last_6_months"
};
fetch(url, {
method: "PUT",
headers,
body: JSON.stringify(body),
}).then(response => response.json());Example response (200):
{
"message": "Onboarding updated successfully.",
"data": {
"organisation": {
"uuid": "550e8400-e29b-41d4-a716-446655440000",
"name": "Acme Inc",
"website": "https://acme.com",
"threshold_consumption_date": "2026-02-25"
},
"project": {
"uuid": "660e8400-e29b-41d4-a716-446655440001",
"product_name": "Acme CRM",
"product_website": "https://acme.com",
"product_logo": "https://cdn.brandfetch.io/acme.com/fallback/lettermark/icon?c=BRANDFETCH_CLIENT_ID"
}
}
}
Example response (404, No organisation or project found):
{
"message": "No organisation or project found for the current user."
}
Example response (422, Validation error):
{
"message": "The organisation name field is required.",
"errors": {
"organisation_name": [
"The organisation name field is required."
]
}
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Organisations
APIs for managing organisations
Supported Regions
Curated list of region/country codes usable for an organisation's region (and, by
fallback, a prompt's country -- see AiVisibilityPromptController::store()). Backs the
frontend's region/country picker so it never offers a value the backend would then reject.
See config('ai_visibility.supported_regions')'s docblock for why this list isn't simply
"every ISO 3166-1 alpha-2 code" or restricted to any one provider's own narrower support.
Example request:
const url = new URL(
"https://backend-staging.subsig.com/api/organisations/supported-regions"
);
const headers = {
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "GET",
headers,
}).then(response => response.json());Example response (200):
{
"data": [
{
"value": "US",
"label": "United States"
},
{
"value": "GB",
"label": "United Kingdom"
}
]
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
List Organisations
requires authentication
Get all organisations the authenticated user belongs to.
Example request:
const url = new URL(
"https://backend-staging.subsig.com/api/organisations"
);
const headers = {
"Authorization": "Bearer 1|abc123...",
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "GET",
headers,
}).then(response => response.json());Example response (200):
{
"data": [
{
"uuid": "550e8400-e29b-41d4-a716-446655440000",
"name": "Acme Inc",
"website": "https://acme.com",
"role": "organisation_owner",
"created_at": "2025-12-10T10:00:00.000000Z",
"updated_at": "2025-12-10T10:00:00.000000Z"
}
]
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Create Organisation
requires authentication
Create a new organisation. The authenticated user becomes the admin.
Example request:
const url = new URL(
"https://backend-staging.subsig.com/api/organisations"
);
const headers = {
"Authorization": "Bearer 1|abc123...",
"Content-Type": "application/json",
"Accept": "application/json",
};
let body = {
"name": "Acme Inc",
"website": "https:\/\/acme.com",
"region": "architecto",
"language": "ngzmiy"
};
fetch(url, {
method: "POST",
headers,
body: JSON.stringify(body),
}).then(response => response.json());Example response (201):
{
"message": "Organisation created successfully.",
"data": {
"uuid": "550e8400-e29b-41d4-a716-446655440000",
"name": "Acme Inc",
"website": "https://acme.com",
"role": "organisation_owner",
"created_at": "2025-12-10T10:00:00.000000Z",
"updated_at": "2025-12-10T10:00:00.000000Z"
}
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Get Organisation
requires authentication
Get details of a specific organisation.
Example request:
const url = new URL(
"https://backend-staging.subsig.com/api/organisations/205ae76c-bd73-426b-a36a-8338353b9542"
);
const headers = {
"Authorization": "Bearer 1|abc123...",
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "GET",
headers,
}).then(response => response.json());Example response (200):
{
"data": {
"uuid": "550e8400-e29b-41d4-a716-446655440000",
"name": "Acme Inc",
"website": "https://acme.com",
"role": "organisation_owner",
"created_at": "2025-12-10T10:00:00.000000Z",
"updated_at": "2025-12-10T10:00:00.000000Z"
}
}
Example response (403, No access):
{
"message": "You do not have access to this organisation."
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Update Organisation
requires authentication
Update an organisation's details. Requires admin role.
Example request:
const url = new URL(
"https://backend-staging.subsig.com/api/organisations/205ae76c-bd73-426b-a36a-8338353b9542"
);
const headers = {
"Authorization": "Bearer 1|abc123...",
"Content-Type": "application/json",
"Accept": "application/json",
};
let body = {
"name": "Acme Corp",
"website": "https:\/\/acme.com",
"region": "architecto",
"language": "ngzmiy"
};
fetch(url, {
method: "PUT",
headers,
body: JSON.stringify(body),
}).then(response => response.json());Example response (200):
{
"message": "Organisation updated successfully.",
"data": {
"uuid": "550e8400-e29b-41d4-a716-446655440000",
"name": "Acme Corp",
"website": "https://acme.com",
"role": "organisation_owner",
"created_at": "2025-12-10T10:00:00.000000Z",
"updated_at": "2025-12-10T10:00:00.000000Z"
}
}
Example response (403, Not admin):
{
"message": "You must be an organisation admin to perform this action."
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Delete Organisation
requires authentication
Delete an organisation and its projects (cascaded). Requires organisation owner role. Cannot delete the authenticated user's current (primary) workspace; switch first.
Example request:
const url = new URL(
"https://backend-staging.subsig.com/api/organisations/205ae76c-bd73-426b-a36a-8338353b9542"
);
const headers = {
"Authorization": "Bearer 1|abc123...",
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "DELETE",
headers,
}).then(response => response.json());Example response (200):
{
"message": "Organisation deleted successfully."
}
Example response (403, No access):
{
"message": "You do not have access to this organisation."
}
Example response (403, Not owner):
{
"message": "You must be an organisation admin to perform this action."
}
Example response (422, Current workspace):
{
"message": "You cannot delete your current workspace. Switch to another organisation first."
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Switch Current Organisation
requires authentication
Set the user's current organisation for subsequent requests.
Example request:
const url = new URL(
"https://backend-staging.subsig.com/api/organisations/205ae76c-bd73-426b-a36a-8338353b9542/switch"
);
const headers = {
"Authorization": "Bearer 1|abc123...",
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "POST",
headers,
}).then(response => response.json());Example response (200):
{
"message": "Switched to organisation successfully.",
"data": {
"uuid": "550e8400-e29b-41d4-a716-446655440000",
"name": "Acme Inc"
}
}
Example response (403, No access):
{
"message": "You do not have access to this organisation."
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Ownership Transfer
Hands an entire organisation (and every other organisation its current owner also owns -- they move as one bundle) over to a new owner. Modeled as a third InviteType on the existing Invite table rather than a parallel system, since it already has everything a token-based offer/accept flow needs.
Accept Ownership Transfer
Example request:
const url = new URL(
"https://backend-staging.subsig.com/api/ownership-transfers/accept"
);
const headers = {
"Authorization": "Bearer {token}",
"Content-Type": "application/json",
"Accept": "application/json",
};
let body = {
"token": "architecto"
};
fetch(url, {
method: "POST",
headers,
body: JSON.stringify(body),
}).then(response => response.json());Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Initiate Ownership Transfer
Example request:
const url = new URL(
"https://backend-staging.subsig.com/api/ownership-transfers"
);
const headers = {
"Authorization": "Bearer {token}",
"Content-Type": "application/json",
"Accept": "application/json",
};
let body = {
"email": "gbailey@example.net",
"organisation_id": "architecto"
};
fetch(url, {
method: "POST",
headers,
body: JSON.stringify(body),
}).then(response => response.json());Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Platforms
APIs for managing platforms
List All Platforms
requires authentication
Get all platforms.
Example request:
const url = new URL(
"https://backend-staging.subsig.com/api/platforms"
);
const headers = {
"Authorization": "Bearer 1|abc123...",
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "GET",
headers,
}).then(response => response.json());Example response (200):
[
{
"uuid": "550e8400-e29b-41d4-a716-446655440000",
"name": "G2",
"created_at": "2026-01-08T10:00:00.000000Z",
"updated_at": "2026-01-08T10:00:00.000000Z"
}
]
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Create Platform
requires authentication
Create a new platform.
Example request:
const url = new URL(
"https://backend-staging.subsig.com/api/platforms"
);
const headers = {
"Authorization": "Bearer 1|abc123...",
"Content-Type": "application/json",
"Accept": "application/json",
};
let body = {
"name": "G2"
};
fetch(url, {
method: "POST",
headers,
body: JSON.stringify(body),
}).then(response => response.json());Example response (201):
{
"message": "Platform created successfully.",
"data": {
"uuid": "550e8400-e29b-41d4-a716-446655440000",
"name": "G2",
"created_at": "2026-01-08T10:00:00.000000Z",
"updated_at": "2026-01-08T10:00:00.000000Z"
}
}
Example response (422, Validation Error):
{
"message": "The name has already been taken.",
"errors": {
"name": [
"The name has already been taken."
]
}
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Update Platform
requires authentication
Update a platform's details.
Example request:
const url = new URL(
"https://backend-staging.subsig.com/api/platforms/550e8400-e29b-41d4-a716-446655440000"
);
const headers = {
"Authorization": "Bearer 1|abc123...",
"Content-Type": "application/json",
"Accept": "application/json",
};
let body = {
"name": "Capterra"
};
fetch(url, {
method: "PATCH",
headers,
body: JSON.stringify(body),
}).then(response => response.json());Example response (200):
{
"message": "Platform updated successfully.",
"data": {
"uuid": "550e8400-e29b-41d4-a716-446655440000",
"name": "Capterra",
"created_at": "2026-01-08T10:00:00.000000Z",
"updated_at": "2026-01-08T10:30:00.000000Z"
}
}
Example response (404, Not Found):
{
"message": "Platform not found."
}
Example response (422, Validation Error):
{
"message": "The name has already been taken.",
"errors": {
"name": [
"The name has already been taken."
]
}
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Delete Platform
requires authentication
Delete a platform.
Example request:
const url = new URL(
"https://backend-staging.subsig.com/api/platforms/550e8400-e29b-41d4-a716-446655440000"
);
const headers = {
"Authorization": "Bearer 1|abc123...",
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "DELETE",
headers,
}).then(response => response.json());Example response (200):
{
"message": "Platform deleted successfully."
}
Example response (404, Not Found):
{
"message": "Platform not found."
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Profiles
APIs for managing product claims and product profile data.
The Profiles API group covers two related workflows:
- Claim Profiles - Submit and manage ownership claims for products
- Product Profiles - Edit and sync detailed product information
Claim Profiles
Claim profiles allow users to claim ownership of products listed in the system.
Once a claim is submitted, it goes through a review process. Approved claims grant access to edit the product's profile data.
Claim Statuses:
unclaimed- Product not yet claimedclaim_started- Claim submitted, awaiting reviewconfirmed_claimed- Approved (free plan)confirmed_verified- Approved (paid/verified plan)rejected- Claim was rejected
Staging Environment:
In staging, use test products from Curiosity (names ending with _test):
- Test Alpha _test, Test Beta _test, Test Gamma _test, etc.
Configure via CURIOSITY_TEST_PRODUCT_IDS and CURIOSITY_RESTRICT_TO_TEST_PRODUCTS environment variables.
Search Products
requires authentication
Search for products in the Curiosity database to claim.
Example request:
const url = new URL(
"https://backend-staging.subsig.com/api/products/search"
);
const params = {
"q": "slack",
"limit": "10",
};
Object.keys(params)
.forEach(key => url.searchParams.append(key, params[key]));
const headers = {
"Authorization": "Bearer 1|abc123...",
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "GET",
headers,
}).then(response => response.json());Example response (200):
{
"data": {
"products": [
{
"id": 123,
"name": "Slack",
"url": "https://slack.com",
"logo_path": "products/slack-logo.png"
}
]
}
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
List All Claim Profiles
requires authentication
Get all claim profiles across all organisations. Intended for admin panel usage.
Example request:
const url = new URL(
"https://backend-staging.subsig.com/api/claim-profiles"
);
const headers = {
"Authorization": "Bearer 1|abc123...",
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "GET",
headers,
}).then(response => response.json());Example response (200):
[
{
"uuid": "880e8400-e29b-41d4-a716-446655440000",
"business_email": "john@company.com",
"job_title": "Product Manager",
"business_phone": "+1234567890",
"status": "pending",
"project": {
"uuid": "660e8400-e29b-41d4-a716-446655440001",
"product_name": "Acme App"
},
"organisation": {
"uuid": "550e8400-e29b-41d4-a716-446655440000",
"name": "Acme Corp"
},
"status_updated_by": null,
"created_at": "2026-01-03T12:00:00.000000Z",
"updated_at": "2026-01-03T12:00:00.000000Z"
}
]
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Create Claim Profile
requires authentication
Create a new claim profile for a project. The organisation is automatically derived from the authenticated user's current organisation context.
Example request:
const url = new URL(
"https://backend-staging.subsig.com/api/claim-profiles"
);
const headers = {
"Authorization": "Bearer 1|abc123...",
"Content-Type": "application/json",
"Accept": "application/json",
};
let body = {
"project_id": "660e8400-e29b-41d4-a716-446655440001",
"scraper_product_id": 123,
"product_name": "Slack",
"product_url": "https:\/\/slack.com",
"business_email": "john@company.com",
"job_title": "Product Manager",
"business_phone": "+1234567890"
};
fetch(url, {
method: "POST",
headers,
body: JSON.stringify(body),
}).then(response => response.json());Example response (201, Success):
{
"message": "Claim profile created successfully.",
"data": {
"uuid": "880e8400-e29b-41d4-a716-446655440000",
"scraper_product_id": 123,
"product_name": "Slack",
"product_url": "https://slack.com",
"business_email": "john@company.com",
"job_title": "Product Manager",
"business_phone": "+1234567890",
"status": "pending",
"created_at": "2026-01-03T12:00:00.000000Z",
"updated_at": "2026-01-03T12:00:00.000000Z"
}
}
Example response (403, No organisation context):
{
"message": "This action is unauthorized."
}
Example response (422, Validation Error):
{
"message": "The project is required. (and 1 more error)",
"errors": {
"project_id": [
"The project is required."
],
"business_email": [
"Please use a business email address."
]
}
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Product Profiles
Product profiles contain detailed information about claimed products, synced bidirectionally with the Curiosity database.
Product profiles are created automatically when a claim is approved. They include:
- Basic Info: Name, URL, logo, subtitle, overview
- Categorization: Parent category, sub-categories, segments, search fields
- Reviews: G2, Capterra (editable URLs), Trustpilot (read-only scores)
- Content: Pricing, analysis, FAQ, pros/cons, alternatives
- SEO: Meta titles and descriptions for main, deals, and cancellation pages
Sync Operations:
pull- Fetch latest data from Curiosity (overwrites local changes)push- Send local changes to Curiosity
Staging Environment:
Sync operations are restricted to test products. Configure via:
CURIOSITY_TEST_PRODUCT_IDS- Comma-separated list of allowed product IDsCURIOSITY_RESTRICT_TO_TEST_PRODUCTS- Set totrueto enable restriction
Get Product Profile
requires authentication
Get the product profile for a specific project. Returns all profile data including content, SEO meta, categories, and sync status.
Example request:
const url = new URL(
"https://backend-staging.subsig.com/api/projects/660e8400-e29b-41d4-a716-446655440001/profile"
);
const headers = {
"Authorization": "Bearer 1|abc123...",
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "GET",
headers,
}).then(response => response.json());Example response (200):
{
"data": {
"uuid": "770e8400-e29b-41d4-a716-446655440002",
"curiosity_product_id": 123,
"name": "Slack",
"url": "https://slack.com",
"logo_path": "products/slack-logo.png",
"local_logo": null,
"subtitle": "<p>Where work happens</p>",
"overview": "<p>Slack is a messaging platform...</p>",
"pricing": "<p>Free, Pro $7.25/user, Business+ $12.50/user</p>",
"verified_badge": true,
"parent_category": {
"id": 5,
"name": "Communication"
},
"review_platforms": {
"g2": {
"enabled": true,
"url": "https://g2.com/products/slack",
"score": 4.5,
"reviews_count": 120
},
"capterra": {
"enabled": true,
"url": "https://capterra.com/p/123/slack",
"score": 4.6,
"reviews_count": 85
},
"trustpilot": {
"score": 3.6,
"reviews_count": 11218
}
},
"categories": [
{
"id": 1,
"name": "Communication"
}
],
"segments": [
{
"id": 1,
"name": "Enterprise"
}
],
"search_fields": {
"built_for": [
{
"id": 1,
"name": "Marketing Teams"
}
],
"platform": [
{
"id": 2,
"name": "Web"
}
],
"pricing_model": [
{
"id": 3,
"name": "Subscription"
}
]
},
"competitors": [
{
"id": 456,
"name": "Microsoft Teams",
"url": "https://teams.microsoft.com",
"logo_path": null
}
],
"videos": [
"https://www.youtube.com/watch?v=abc"
],
"awards": [
{
"id": 1,
"name": "Best Communication Tool 2025"
}
],
"deal": "Get 20% off annual plans",
"deals_meta_title": "Best Slack Deals",
"deals_meta_description": null,
"cancellation_content": "<p>To cancel your subscription...</p>",
"cancellation_content_summary": "<p>Cancel anytime from settings</p>",
"book_demo_url": "https://slack.com/demo",
"pricing_url": "https://slack.com/pricing",
"pros_cons": "Pros: Easy to use\nCons: Can be expensive",
"analysis": "<p>Detailed analysis of Slack...</p>",
"faq": "Q: How much does it cost?\nA: Free tier available",
"alternatives_text": "<p>Consider Microsoft Teams or Discord...</p>",
"pricing_range": "$0-$15/user/mo",
"is_ai_powered": false,
"meta": {
"main_page": {
"title": "Slack - Where Work Happens",
"description": "Team messaging platform"
},
"deals": {
"title": "Slack Deals",
"description": "Best Slack discounts"
},
"cancellation": {
"title": "Cancel Slack",
"description": "How to cancel"
}
},
"sync_status": "synced",
"synced_at": "2026-01-06T10:00:00.000000Z",
"local_changes_at": null,
"last_change_request": {
"uuid": "880e8400-e29b-41d4-a716-446655440003",
"status": "pending",
"requested_at": "2026-01-06T11:00:00.000000Z",
"reviewed_at": null
},
"created_at": "2026-01-06T09:00:00.000000Z",
"updated_at": "2026-01-06T10:00:00.000000Z"
}
}
Example response (404):
{
"message": "Project not found."
}
Example response (404):
{
"message": "This project does not have a product profile yet."
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Update Product Profile
requires authentication
Submit a change request for the product profile. Changes are stored as a pending request for admin review. Once approved, changes will be applied and synced to Curiosity. Fields like subtitle, overview, pricing, analysis, and alternatives_text support HTML/richtext.
Example request:
const url = new URL(
"https://backend-staging.subsig.com/api/projects/660e8400-e29b-41d4-a716-446655440001/profile"
);
const headers = {
"Authorization": "Bearer 1|abc123...",
"Content-Type": "application/json",
"Accept": "application/json",
};
let body = {
"subtitle": "<p>Where work happens<\/p>",
"overview": "<p>Slack is a messaging platform...<\/p>",
"pricing": "<p>Free, Pro $7.25\/user<\/p>",
"parent_category_id": 5,
"review_platforms": [],
"videos": [
"architecto"
],
"categories": [
[]
],
"segments": [
[]
],
"search_fields": [],
"competitors": [
[]
],
"deal": "architecto",
"deals_meta_title": "Best Slack Deals 2026",
"deals_meta_description": "architecto",
"cancellation_content": "architecto",
"cancellation_content_summary": "architecto",
"book_demo_url": "https:\/\/slack.com\/demo",
"pricing_url": "https:\/\/slack.com\/pricing",
"pros_cons": "architecto",
"analysis": "architecto",
"faq": "architecto",
"alternatives_text": "architecto",
"pricing_range": "$99-$999\/mo",
"is_ai_powered": true,
"meta": []
};
fetch(url, {
method: "PUT",
headers,
body: JSON.stringify(body),
}).then(response => response.json());Example response (200):
{
"message": "Change request submitted for review.",
"data": {
"change_request_uuid": "880e8400-e29b-41d4-a716-446655440003",
"status": "pending"
}
}
Example response (404):
{
"message": "This project does not have a product profile yet."
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Upload Product Logo
requires authentication
Upload a new logo image for the product profile. The logo is stored locally and will be synced to Curiosity when you push changes.
Example request:
const url = new URL(
"https://backend-staging.subsig.com/api/projects/660e8400-e29b-41d4-a716-446655440001/profile/logo"
);
const headers = {
"Authorization": "Bearer 1|abc123...",
"Content-Type": "multipart/form-data",
"Accept": "application/json",
};
const body = new FormData();
body.append('logo', document.querySelector('input[name="logo"]').files[0]);
fetch(url, {
method: "POST",
headers,
body,
}).then(response => response.json());Example response (200):
{
"message": "Logo uploaded successfully.",
"data": {
"local_logo": "product-logos/abc123.png",
"sync_status": "local_changes",
"local_changes_at": "2026-01-06T11:00:00.000000Z"
}
}
Example response (404):
{
"message": "This project does not have a product profile yet."
}
Example response (422):
{
"message": "The logo field is required.",
"errors": {
"logo": [
"The logo field is required."
]
}
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Get Sync Status
requires authentication
Get the current sync status of a project's product profile.
Example request:
const url = new URL(
"https://backend-staging.subsig.com/api/projects/660e8400-e29b-41d4-a716-446655440001/profile/status"
);
const headers = {
"Authorization": "Bearer 1|abc123...",
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "GET",
headers,
}).then(response => response.json());Example response (200):
{
"data": {
"has_profile": true,
"has_curiosity_link": true,
"sync_status": "local_changes",
"synced_at": "2026-01-06T10:00:00.000000Z",
"local_changes_at": "2026-01-06T11:00:00.000000Z",
"last_change_request": {
"uuid": "880e8400-e29b-41d4-a716-446655440003",
"status": "pending",
"requested_at": "2026-01-06T11:00:00.000000Z",
"reviewed_at": null
}
}
}
Example response (200):
{
"data": {
"has_profile": false,
"has_curiosity_link": true,
"sync_status": null,
"synced_at": null,
"local_changes_at": null,
"last_change_request": null
}
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Catalog
Reference data for product categorization and filtering.
These endpoints provide access to taxonomy data from Curiosity used when editing product profiles.
Available Data:
- Parent Categories - Top-level categories (e.g., Communication, Marketing)
- Categories - Detailed sub-categories
- Segments - Market segments (e.g., Enterprise, SMB)
- Search Fields - Filter attributes grouped by type (built_for, platform, pricing_model)
List Parent Categories
requires authentication
Get all top-level parent categories for product classification. Parent categories represent broad product domains.
Example request:
const url = new URL(
"https://backend-staging.subsig.com/api/catalog/parent-categories"
);
const headers = {
"Authorization": "Bearer 1|abc123...",
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "GET",
headers,
}).then(response => response.json());Example response (200):
{
"data": [
{
"id": 1,
"name": "Analytics"
},
{
"id": 2,
"name": "Communication"
},
{
"id": 3,
"name": "Marketing"
},
{
"id": 4,
"name": "Project Management"
},
{
"id": 5,
"name": "Sales"
}
]
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
List Categories
requires authentication
Get all detailed categories for product classification. Categories are more specific than parent categories and can be assigned to products.
Example request:
const url = new URL(
"https://backend-staging.subsig.com/api/catalog/categories"
);
const headers = {
"Authorization": "Bearer 1|abc123...",
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "GET",
headers,
}).then(response => response.json());Example response (200):
{
"data": [
{
"id": 1,
"name": "Video Conferencing"
},
{
"id": 2,
"name": "Team Chat"
},
{
"id": 3,
"name": "Email Marketing"
},
{
"id": 4,
"name": "CRM"
},
{
"id": 5,
"name": "Task Management"
}
]
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
List Segments
requires authentication
Get all market segments for product targeting. Segments define the target audience or market size for products.
Example request:
const url = new URL(
"https://backend-staging.subsig.com/api/catalog/segments"
);
const headers = {
"Authorization": "Bearer 1|abc123...",
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "GET",
headers,
}).then(response => response.json());Example response (200):
{
"data": [
{
"id": 1,
"name": "Enterprise"
},
{
"id": 2,
"name": "Mid-Market"
},
{
"id": 3,
"name": "SMB"
},
{
"id": 4,
"name": "Startup"
},
{
"id": 5,
"name": "Freelancer"
}
]
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
List Search Fields
requires authentication
Get all search field options grouped by type. Search fields are structured attributes used for filtering and discovery.
Field Types:
built_for- Target user roles or teams (e.g., "Marketing Teams", "Developers")platform- Deployment platforms (e.g., "Web", "iOS", "Android", "Desktop")pricing_model- Business models (e.g., "Subscription", "One-time", "Freemium")
Example request:
const url = new URL(
"https://backend-staging.subsig.com/api/catalog/search-fields"
);
const headers = {
"Authorization": "Bearer 1|abc123...",
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "GET",
headers,
}).then(response => response.json());Example response (200):
{
"data": {
"built_for": [
{
"id": 1,
"name": "Marketing Teams"
},
{
"id": 2,
"name": "Sales Teams"
},
{
"id": 3,
"name": "Developers"
},
{
"id": 4,
"name": "HR Teams"
}
],
"platform": [
{
"id": 1,
"name": "Web"
},
{
"id": 2,
"name": "iOS"
},
{
"id": 3,
"name": "Android"
},
{
"id": 4,
"name": "Desktop"
}
],
"pricing_model": [
{
"id": 1,
"name": "Subscription"
},
{
"id": 2,
"name": "One-time Purchase"
},
{
"id": 3,
"name": "Freemium"
},
{
"id": 4,
"name": "Usage-based"
}
]
}
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Projects
APIs for managing projects within organisations
List Projects
requires authentication
Get all projects in an organisation that the user has access to.
Example request:
const url = new URL(
"https://backend-staging.subsig.com/api/organisations/205ae76c-bd73-426b-a36a-8338353b9542/projects"
);
const headers = {
"Authorization": "Bearer 1|abc123...",
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "GET",
headers,
}).then(response => response.json());Example response (200):
{
"data": [
{
"uuid": "660e8400-e29b-41d4-a716-446655440001",
"product_name": "Acme App",
"product_website": "https://acme.com",
"created_at": "2025-12-10T10:00:00.000000Z",
"updated_at": "2025-12-10T10:00:00.000000Z"
}
]
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Create Project
requires authentication
Create a new project in an organisation. Requires organisation admin role.
Example request:
const url = new URL(
"https://backend-staging.subsig.com/api/organisations/205ae76c-bd73-426b-a36a-8338353b9542/projects"
);
const headers = {
"Authorization": "Bearer 1|abc123...",
"Content-Type": "application/json",
"Accept": "application/json",
};
let body = {
"product_name": "Acme App",
"product_website": "https:\/\/acme.com",
"product_logo": "https:\/\/cdn.brandfetch.io\/acme.com\/fallback\/lettermark\/icon?c=BRANDFETCH_CLIENT_ID"
};
fetch(url, {
method: "POST",
headers,
body: JSON.stringify(body),
}).then(response => response.json());Example response (201):
{
"message": "Project created successfully.",
"data": {
"uuid": "660e8400-e29b-41d4-a716-446655440001",
"product_name": "Acme App",
"product_website": "https://acme.com",
"product_logo": "https://cdn.brandfetch.io/acme.com/fallback/lettermark/icon?c=BRANDFETCH_CLIENT_ID",
"is_competitor": false,
"created_at": "2025-12-10T10:00:00.000000Z",
"updated_at": "2025-12-10T10:00:00.000000Z"
}
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Get Project
requires authentication
Get details of a specific project.
Example request:
const url = new URL(
"https://backend-staging.subsig.com/api/organisations/205ae76c-bd73-426b-a36a-8338353b9542/projects/cbe3b865-507c-442a-aeab-0e6b948c10e0"
);
const headers = {
"Authorization": "Bearer 1|abc123...",
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "GET",
headers,
}).then(response => response.json());Example response (200):
{
"data": {
"uuid": "660e8400-e29b-41d4-a716-446655440001",
"product_name": "Acme App",
"product_website": "https://acme.com",
"created_at": "2025-12-10T10:00:00.000000Z",
"updated_at": "2025-12-10T10:00:00.000000Z"
}
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Update Project
requires authentication
Update a project's details. Requires organisation admin role.
Example request:
const url = new URL(
"https://backend-staging.subsig.com/api/organisations/205ae76c-bd73-426b-a36a-8338353b9542/projects/cbe3b865-507c-442a-aeab-0e6b948c10e0"
);
const headers = {
"Authorization": "Bearer 1|abc123...",
"Content-Type": "application/json",
"Accept": "application/json",
};
let body = {
"product_name": "Acme App Pro",
"product_website": "https:\/\/acme.com",
"product_logo": "https:\/\/cdn.brandfetch.io\/acme.com\/fallback\/lettermark\/icon?c=BRANDFETCH_CLIENT_ID"
};
fetch(url, {
method: "PUT",
headers,
body: JSON.stringify(body),
}).then(response => response.json());Example response (200):
{
"message": "Project updated successfully.",
"data": {
"uuid": "660e8400-e29b-41d4-a716-446655440001",
"product_name": "Acme App Pro",
"product_website": "https://acme.com",
"product_logo": "https://cdn.brandfetch.io/acme.com/fallback/lettermark/icon?c=BRANDFETCH_CLIENT_ID",
"is_competitor": false,
"created_at": "2025-12-10T10:00:00.000000Z",
"updated_at": "2025-12-10T10:00:00.000000Z"
}
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Delete Project
requires authentication
Delete a project. Requires organisation admin role.
Example request:
const url = new URL(
"https://backend-staging.subsig.com/api/organisations/205ae76c-bd73-426b-a36a-8338353b9542/projects/cbe3b865-507c-442a-aeab-0e6b948c10e0"
);
const headers = {
"Authorization": "Bearer 1|abc123...",
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "DELETE",
headers,
}).then(response => response.json());Example response (200):
{
"message": "Project deleted successfully."
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Slack Integration
APIs for connecting and managing Slack workspace integration
Slack OAuth Callback
Handles the OAuth callback from Slack after user authorization. Exchanges the authorization code for an access token. This endpoint is unauthenticated - uses cached state for auth context.
Example request:
const url = new URL(
"https://backend-staging.subsig.com/api/integrations/slack/callback"
);
const params = {
"code": "123456789.abcdef",
"state": "abc123...",
"error": "access_denied",
};
Object.keys(params)
.forEach(key => url.searchParams.append(key, params[key]));
const headers = {
"Content-Type": "application/json",
"Accept": "application/json",
};
let body = {
"code": "architecto",
"state": "ngzmiyvdljnikhwaykcmyuwpwlvqwrsitcpscqldzsnrwtujwvlxjklqppwqbewt",
"error": "architecto"
};
fetch(url, {
method: "GET",
headers,
body: JSON.stringify(body),
}).then(response => response.json());Example response (302, Success):
Redirects to /dashboard/notification/overview?slack_connected=true&workspace={name}
Example response (302, Error):
Redirects to /dashboard/notification/overview?slack_error={error_code}
Example response (302):
Show headers
cache-control: no-cache, private
location: https://subsig-frontend.vercel.app/dashboard/notification/overview?slack_error=architecto
content-type: text/html; charset=utf-8
vary: Origin
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<meta http-equiv="refresh" content="0;url='https://subsig-frontend.vercel.app/dashboard/notification/overview?slack_error=architecto'" />
<title>Redirecting to https://subsig-frontend.vercel.app/dashboard/notification/overview?slack_error=architecto</title>
</head>
<body>
Redirecting to <a href="https://subsig-frontend.vercel.app/dashboard/notification/overview?slack_error=architecto">https://subsig-frontend.vercel.app/dashboard/notification/overview?slack_error=architecto</a>.
</body>
</html>
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Initiate Slack OAuth
requires authentication
Starts the OAuth flow to connect a Slack workspace. Redirects to Slack's authorization page.
Example request:
const url = new URL(
"https://backend-staging.subsig.com/api/integrations/slack/connect"
);
const headers = {
"Authorization": "Bearer 1|abc123...",
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "GET",
headers,
}).then(response => response.json());Example response (302, Redirect to Slack):
Redirects to Slack OAuth page
Example response (400, Not configured):
{
"error": "configuration",
"message": "Slack integration is not configured."
}
Example response (401):
Show headers
cache-control: no-cache, private
content-type: application/json
vary: Origin
{
"message": "Unauthenticated."
}
Example response (403, No organisation):
{
"message": "Organisation context required."
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Get Slack Connection Status
requires authentication
Returns the current Slack connection status for the organisation.
Example request:
const url = new URL(
"https://backend-staging.subsig.com/api/integrations/slack/status"
);
const headers = {
"Authorization": "Bearer 1|abc123...",
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "GET",
headers,
}).then(response => response.json());Example response (200, Connected):
{
"connected": true,
"team_id": "T123456789",
"team_name": "My Workspace",
"scopes": "chat:write,channels:read",
"connected_at": "2025-01-01T12:00:00Z"
}
Example response (200, Not connected):
{
"connected": false
}
Example response (200, Invalid token):
{
"connected": false,
"error": "token_invalid",
"message": "Slack connection needs to be re-authorized."
}
Example response (403, No organisation):
{
"message": "Organisation context required."
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Disconnect Slack
requires authentication
Removes the Slack workspace connection for the organisation.
Example request:
const url = new URL(
"https://backend-staging.subsig.com/api/integrations/slack/disconnect"
);
const headers = {
"Authorization": "Bearer 1|abc123...",
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "POST",
headers,
}).then(response => response.json());Example response (200, Success):
{
"success": true,
"message": "Slack connection removed."
}
Example response (200, Not connected):
{
"success": true,
"message": "No Slack connection found."
}
Example response (403, No organisation):
{
"message": "Organisation context required."
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
List Slack Channels
requires authentication
Fetches the list of channels from the connected Slack workspace.
Example request:
const url = new URL(
"https://backend-staging.subsig.com/api/integrations/slack/channels"
);
const headers = {
"Authorization": "Bearer 1|abc123...",
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "GET",
headers,
}).then(response => response.json());Example response (200, Success):
{
"ok": true,
"channels": [
{
"id": "C123456789",
"name": "general",
"is_member": true
},
{
"id": "C987654321",
"name": "random",
"is_member": false
}
]
}
Example response (401, Not connected):
{
"ok": false,
"error": "not_connected",
"message": "Not connected to Slack."
}
Example response (401, Invalid token):
{
"ok": false,
"error": "token_invalid",
"message": "Slack connection needs to be re-authorized."
}
Example response (403, No organisation):
{
"message": "Organisation context required."
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Send a message to Slack
requires authentication
Accepts a payload in either Reviews format (data = array of reviews) or Mentions format (data = object with posts and/or comments) and posts a Block Kit message to the given Slack channel via the notification dispatcher.
Example request:
const url = new URL(
"https://backend-staging.subsig.com/api/integrations/slack/send"
);
const headers = {
"Authorization": "Bearer 1|abc123...",
"Content-Type": "application/json",
"Accept": "application/json",
};
let body = {
"channel_id": "b",
"status": "architecto",
"process_id": "architecto",
"created_at": "architecto",
"data": []
};
fetch(url, {
method: "POST",
headers,
body: JSON.stringify(body),
}).then(response => response.json());Example response (200, Success):
{
"ok": true,
"message": "Message sent."
}
Example response (400, Slack API error):
{
"ok": false,
"error": "...",
"message": "..."
}
Example response (401, Not connected):
{
"ok": false,
"error": "not_connected",
"message": "Not connected to Slack."
}
Example response (422, Invalid payload):
{
"message": "Invalid payload: data must be reviews array or mentions object."
}
Example response (500, Server error):
{
"message": "Failed to send message."
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Subscriptions
APIs for managing subscriptions
List Subscription Plans
requires authentication
Get all available subscription plans.
Example request:
const url = new URL(
"https://backend-staging.subsig.com/api/subscription-plans"
);
const headers = {
"Authorization": "Bearer 1|abc123...",
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "GET",
headers,
}).then(response => response.json());Example response (200):
{
"data": [
{
"id": 1,
"stripe_price_id": "price_internal_free",
"name": "Free",
"description": "Post-trial free tier",
"amount": 0,
"currency": "usd",
"interval": "month",
"plan_threshold": {}
}
]
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Get Threshold Usage
requires authentication
Returns plan soft limits in threshold and limits.*_soft.
Usage is all-time across organisations owned by the billing user.
Per-platform breakdowns are uncapped; they may sum above the top-level totals.
Example request:
const url = new URL(
"https://backend-staging.subsig.com/api/subscriptions/threshold-usage"
);
const headers = {
"Authorization": "Bearer 1|abc123...",
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "GET",
headers,
}).then(response => response.json());Example response (200):
{
"data": {
"threshold": {
"competitor_brands_limit": 3,
"history_months": 6,
"refresh_interval": "daily",
"users_limit": null,
"workspaces_limit": null,
"data_export_type": null,
"ai_visibility_prompts_limit": 20
},
"limits": {
"ai_visibility_prompts_soft": 20
},
"usage": {
"mentions": 1000,
"mentions_per_platform": {
"reddit": 200
},
"competitor_brands": 0,
"ai_visibility_prompts": 7,
"workspaces": 1,
"users": 1
},
"usage_period": {
"scope": "all_time",
"start": "2025-01-01",
"end": "2026-04-02"
},
"trial_days_left": 3
}
}
Example response (403, No organisation access):
{
"message": "You do not have access to this organisation."
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
POST api/subscriptions/checkout
Example request:
const url = new URL(
"https://backend-staging.subsig.com/api/subscriptions/checkout"
);
const headers = {
"Content-Type": "application/json",
"Accept": "application/json",
};
let body = {
"plan_id": 16,
"success_url": "http:\/\/bailey.com\/",
"cancel_url": "http:\/\/rempel.com\/sunt-nihil-accusantium-harum-mollitia",
"addons": {
"ai_visibility_prompts": {
"price": 23,
"quantity": 64
}
}
};
fetch(url, {
method: "POST",
headers,
body: JSON.stringify(body),
}).then(response => response.json());Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Update Subscription Add-ons
requires authentication
Update add-ons on the organisation's current active subscription.
Supports optional proration_date to control Stripe proration timing.
Example request:
const url = new URL(
"https://backend-staging.subsig.com/api/subscriptions/addons"
);
const headers = {
"Authorization": "Bearer 1|abc123...",
"Content-Type": "application/json",
"Accept": "application/json",
};
let body = {
"addons": {
"ai_visibility_prompts": {
"price": 14000,
"quantity": 1000
}
},
"proration_date": 1715000000
};
fetch(url, {
method: "POST",
headers,
body: JSON.stringify(body),
}).then(response => response.json());Example response (200):
{
"data": {
"id": 1,
"stripe_subscription_id": "sub_1234567890",
"status": "active",
"addons": [
{
"id": 10,
"product_id": "prod_ai_visibility_prompts_123",
"stripe_price_id": "price_inline_ai_visibility_prompts_1",
"price": 2000,
"ai_visibility_prompts_quantity": 1,
"status": "active"
}
]
}
}
Example response (403, Not owner or no organisation access):
{
"message": "You must be an organisation owner to manage subscriptions."
}
Example response (404, No active subscription):
{
"message": "No active subscription found for this organisation."
}
Example response (422, Validation error):
{
"message": "The given data was invalid."
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Preview Add-on Proration
requires authentication
Preview Stripe prorated invoice impact for add-on changes without applying the subscription update.
Example request:
const url = new URL(
"https://backend-staging.subsig.com/api/subscriptions/addons/preview-proration"
);
const headers = {
"Authorization": "Bearer 1|abc123...",
"Content-Type": "application/json",
"Accept": "application/json",
};
let body = {
"addons": {
"ai_visibility_prompts": {
"price": 14000,
"quantity": 1000
}
},
"proration_date": 1715000000
};
fetch(url, {
method: "POST",
headers,
body: JSON.stringify(body),
}).then(response => response.json());Example response (200):
{
"data": {
"amount_due": 2400,
"subtotal": 2400,
"total": 2400,
"currency": "usd",
"proration_lines": [
{
"id": "il_proration_123",
"amount": 2400,
"currency": "usd",
"description": "Proration adjustment"
}
]
}
}
Example response (403, Not owner or no organisation access):
{
"message": "You must be an organisation owner to manage subscriptions."
}
Example response (404, No active subscription):
{
"message": "No active subscription found for this organisation."
}
Example response (422, Validation error):
{
"message": "The given data was invalid."
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
AI Platforms Pricing
requires authentication
Cents-per-prompt rate for each AI platform add-on, keyed in the frontend's own provider vocabulary (GetMoreAiPlatformsModal.tsx multiplies this by the org's current AI Visibility Prompts quantity to render its preview) -- this is now also the exact table repriceAiPlatformsAddons() uses server-side to compute the real charge in updateAddons(), so the preview and the actual charge can never drift out of sync.
Example request:
const url = new URL(
"https://backend-staging.subsig.com/api/subscriptions/ai-platforms/pricing"
);
const headers = {
"Authorization": "Bearer 1|abc123...",
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "GET",
headers,
}).then(response => response.json());Example response (200):
{
"data": {
"openai": 0,
"perplexity": 0,
"google_ai_overviews": 0,
"bing_copilot": 0,
"google_ai_mode": 43,
"gemini": 161,
"claude": 142,
"deepseek": 12,
"grok": 120
}
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Get Current Subscription
requires authentication
Get the current subscription for the organisation.
Example request:
const url = new URL(
"https://backend-staging.subsig.com/api/subscriptions/current"
);
const headers = {
"Authorization": "Bearer 1|abc123...",
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "GET",
headers,
}).then(response => response.json());Example response (200):
{
"data": {
"id": 1,
"stripe_subscription_id": "sub_1234567890",
"status": "active",
"current_period_start": "2025-12-01T00:00:00.000000Z",
"current_period_end": "2026-01-01T00:00:00.000000Z",
"plan": {
"id": 1,
"stripe_price_id": "price_1234567890",
"description": "Professional features",
"name": "Pro Plan",
"amount": 2999,
"currency": "usd",
"interval": "month",
"features": [
"Feature 1",
"Feature 2"
]
}
}
}
Example response (403, No organisation access):
{
"message": "You do not have access to this organisation."
}
Example response (404, No subscription):
{
"message": "No subscription found for this organisation."
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Refresh subscription from Stripe
requires authentication
Fetch the organisation's current subscription from Stripe and sync to the database. Use after the user returns from the billing portal so subscription and threshold data are up to date.
Example request:
const url = new URL(
"https://backend-staging.subsig.com/api/subscriptions/refresh"
);
const headers = {
"Authorization": "Bearer 1|abc123...",
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "POST",
headers,
}).then(response => response.json());Example response (200):
{
"data": {
"id": 1,
"stripe_subscription_id": "sub_xxx",
"status": "active",
"current_period_start": "2025-12-01T00:00:00.000000Z",
"current_period_end": "2026-01-01T00:00:00.000000Z",
"canceled_at": null,
"ends_at": null,
"plan": { "id": 1, "stripe_price_id": "price_xxx", "name": "Pro - Monthly", ... }
}
}
Example response (403, Not owner):
{
"message": "You must be an organisation owner to manage subscriptions."
}
Example response (404, No subscription):
{
"message": "No active subscription found for this organisation."
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Get Billing Portal URL
requires authentication
Create a Stripe Billing Portal session for the organisation's customer. The portal allows customers to manage their subscription, update payment methods, view invoices, and cancel their subscription.
Example request:
const url = new URL(
"https://backend-staging.subsig.com/api/subscriptions/billing-portal"
);
const headers = {
"Authorization": "Bearer 1|abc123...",
"Content-Type": "application/json",
"Accept": "application/json",
};
let body = {
"return_url": "https:\/\/app.example.com\/settings\/billing"
};
fetch(url, {
method: "POST",
headers,
body: JSON.stringify(body),
}).then(response => response.json());Example response (200):
{
"portal_url": "https://billing.stripe.com/p/session/..."
}
Example response (403, Not owner):
{
"message": "You must be an organisation owner to manage subscriptions."
}
Example response (404, No subscription):
{
"message": "No active subscription found for this organisation."
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Utilities
Validate URL Reachability
requires authentication
Validates URL format and checks if the URL is reachable.
Example request:
const url = new URL(
"https://backend-staging.subsig.com/api/validate-url"
);
const headers = {
"Authorization": "Bearer 1|abc123...",
"Content-Type": "application/json",
"Accept": "application/json",
};
let body = {
"url": "https:\/\/example.com"
};
fetch(url, {
method: "POST",
headers,
body: JSON.stringify(body),
}).then(response => response.json());Example response (200):
{
"valid": true,
"status_code": 200,
"message": "URL is valid and reachable"
}
Example response (422):
{
"valid": false,
"message": "Invalid URL format"
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Webhook Tools
Utility endpoints for webhook signature testing.
Generate webhook signature
requires authentication
Generate X-Scraper-Signature using HMAC SHA256 from the exact raw_body string you send.
Use this for Swagger testing before calling scrape webhook endpoints.
Example request:
const url = new URL(
"https://backend-staging.subsig.com/api/tools/webhook-signatures"
);
const headers = {
"Authorization": "Bearer 1|abc123...",
"Content-Type": "application/json",
"Accept": "application/json",
};
let body = {
"target": "social",
"raw_body": "{\"status\":\"SUCCESS\",\"process_id\":76445,\"created_at\":\"2026-01-19 14:23:11\",\"link_url\":\"https:\/\/www.trustpilot.com\/review\/example.com\",\"data\":[]}"
};
fetch(url, {
method: "POST",
headers,
body: JSON.stringify(body),
}).then(response => response.json());Example response (200):
{
"signature": "8e1b1b0d7c6c1b3b8c9a..."
}
Example response (422):
{
"message": "The given data was invalid.",
"errors": {
"target": [
"The selected target is invalid."
]
}
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.