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"
};
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"
};
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.
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.
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.
Reviews
APIs for fetching aggregated reviews from multiple platforms, scoped to organizations.
Get Organization Reviews
requires authentication
Fetch and aggregate reviews from multiple projects/brands within an organization. Reviews are fetched from external API, persisted to database, and returned with filters applied. Supports filtering by multiple brands (projects), platforms, ratings, languages, and more.
Example request:
const url = new URL(
"https://backend-staging.subsig.com/api/organisations/660e8400-e29b-41d4-a716-446655440001/reviews"
);
const headers = {
"Authorization": "Bearer 1|abc123...",
"Content-Type": "application/json",
"Accept": "application/json",
};
let body = {
"brands": [
"uuid-1",
"uuid-2"
],
"view_uuid": "a1b2c3d4-e5f6-g7h8-i9j0",
"platforms": [
"G2",
"Capterra"
],
"date_range": {
"type": "custom",
"value": "last_6_months"
},
"date_range_custom": {
"start": "2026-08-04T12:24:26",
"end": "2052-08-27"
},
"date_from": "2026-01-01",
"date_to": "2026-01-24",
"rating_buckets": [
"5.0",
"4.0-4.9"
],
"languages": [
"en",
"de"
],
"read_status": "unread",
"search": "customer support",
"sort_by": "creation_date",
"sort_direction": "desc\n\nNote: Results are paginated with 10 items per page (fixed, not configurable).",
"max_period": 67,
"max_results": 66,
"period_in_days": 30,
"page": 1
};
fetch(url, {
method: "POST",
headers,
body: JSON.stringify(body),
}).then(response => response.json());Example response (200):
{
"data": {
"organisation_id": "660e8400-e29b-41d4-a716-446655440001",
"organisation_name": "Acme Corp",
"reviews": {
"current_page": 1,
"data": [
{
"id": 1,
"scraper_review_id": 10452,
"organisation_id": 5,
"project_id": 30,
"platform": "Capterra",
"platform_icon": null,
"rating": 5,
"content": "Great product with excellent features...",
"author": "John Smith",
"job_role": "Product Manager",
"language": "en",
"date": "2026-01-13",
"link_url": "https://www.capterra.com/reviews/",
"direct_review_url": "https://scraper.example.com/reviews",
"created_at": "2026-01-15T09:30:21.000000Z"
}
],
"per_page": 10,
"total": 150
}
}
}
Example response (403):
{
"message": "You do not have access to this organisation."
}
Example response (404):
{
"message": "Organisation 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.
Sync review aggregation tables from scraper historical data.
requires authentication
Fetches enabled link URLs from the links table (via link_project), calls scraper
/v1/reviews for all pages, aggregates data, and upserts into:
- review_aggregation_daily
- review_aggregation_rating_daily
Historical flow only (no process_id and no webhook deliveries checks).
Example request:
const url = new URL(
"https://backend-staging.subsig.com/api/organisations/660e8400-e29b-41d4-a716-446655440001/reviews/aggregation/sync"
);
const headers = {
"Authorization": "Bearer 1|abc123...",
"Content-Type": "application/json",
"Accept": "application/json",
};
let body = {
"all_organisations": true,
"brands": [
"uuid-1",
"uuid-2"
],
"period_in_days": 365
};
fetch(url, {
method: "POST",
headers,
body: JSON.stringify(body),
}).then(response => response.json());Example response (202):
{
"data": {
"organisation_id": "660e8400-e29b-41d4-a716-446655440001",
"organisation_name": "Acme Corp",
"run_id": "0f57a8e6-89e6-43d8-8d5d-176c52747467",
"period_in_days": 365,
"projects_total": 1,
"links_queued": 2,
"status": "queued"
}
}
Example response (403):
{
"message": "You do not have access to this organisation."
}
Example response (404):
{
"message": "Organisation not found."
}
Example response (422):
{
"message": "Review aggregation sync is unavailable. Scraper service is not configured."
}
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 aggregation run status using run_id.
requires authentication
Example request:
const url = new URL(
"https://backend-staging.subsig.com/api/organisations/205ae76c-bd73-426b-a36a-8338353b9542/reviews/aggregation/runs/architecto"
);
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": {
"run_id": "0f57a8e6-89e6-43d8-8d5d-176c52747467",
"status": "running",
"period_in_days": 365,
"projects_total": 1,
"total_links": 8,
"processed_links": 5,
"success_links": 4,
"failed_links": 1,
"no_rows_links": 1,
"queued_at": "2026-04-22T06:31:06.000000Z",
"started_at": "2026-04-22T06:31:10.000000Z",
"completed_at": null
}
}
Example response (403):
{
"message": "You do not have access to this organisation."
}
Example response (404):
{
"message": "Aggregation run 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 Review Analytics
requires authentication
Calculate analytics metrics for reviews including new reviews count, average rating, reviews per month, with comparison to previous period. Also includes rating distribution and platform breakdown. Each product in data includes is_scraping_reviews (boolean): true if a review scrape is currently in progress for that product; use it to show a loading state in the UI. is_data (boolean): true if any aggregation record exists for the same scope (project_ids, platforms, date range) as the metrics; false otherwise.
Example request:
const url = new URL(
"https://backend-staging.subsig.com/api/organisations/660e8400-e29b-41d4-a716-446655440001/reviews/analytics"
);
const headers = {
"Authorization": "Bearer 1|abc123...",
"Content-Type": "application/json",
"Accept": "application/json",
};
let body = {
"brands": [
"uuid-1",
"uuid-2"
],
"compare_products": [
"uuid-1",
"uuid-2"
],
"platforms": [
"G2",
"Capterra"
],
"date_range": {
"type": "preset",
"value": "last_3_months"
},
"date_range_custom": {
"start": "2025-10-01",
"end": "2026-01-27"
},
"max_results": 22,
"max_period": 67
};
fetch(url, {
method: "POST",
headers,
body: JSON.stringify(body),
}).then(response => response.json());Example response (200):
{
"data": [
{
"product_uuid": "660e8400-e29b-41d4-a716-446655440001",
"product_name": "Acme Product",
"product_logo": "https://...",
"period": {
"start": "2025-10-28",
"end": "2026-01-27",
"duration": 3
},
"metrics": {
"new_reviews": {
"current": 2,
"previous": 10,
"change_percentage": -80,
"trend": "down"
},
"average_rating": {
"current": 4.1,
"previous": 4.4,
"change_percentage": -6.8,
"trend": "down"
},
"reviews_per_month": {
"current": 2,
"previous": 6,
"change_percentage": -68.3,
"trend": "down"
},
"review_velocity": {
"default_interval": "week",
"allowed_intervals": [
"week",
"month"
],
"by_interval": {
"week": {
"data": [
{
"group": "2025-W48",
"count": 1,
"cumulative_before": 0,
"rating": 4.2
}
]
},
"month": {
"data": [
{
"group": "2025-11",
"count": 1,
"cumulative_before": 0,
"rating": 4.2
}
]
}
},
"interval": "week",
"data": [
{
"group": "2025-W48",
"count": 1,
"cumulative_before": 0,
"rating": 4.2
}
]
}
},
"breakdown": {
"ratings": {
"5.0": 0,
"4.0": 2,
"3.0": 0,
"2.0": 0,
"1.0": 0
},
"platforms": [
{
"name": "G2",
"has_data": 1
},
{
"name": "Capterra",
"has_data": 2
}
]
},
"is_scraping_reviews": false
}
],
"competitors": [
{
"project_uuid": "660e8400-e29b-41d4-a716-446655440001",
"product_name": "Base Product",
"product_logo": "https://...",
"is_competitor": false,
"total_reviews": {
"current": 10,
"previous": 5,
"change_percentage": 100,
"trend": "up"
},
"average_rating": {
"current": 4.3,
"previous": 4.1,
"change_percentage": 4.9,
"trend": "up"
},
"rank": 1
}
],
"applied_filters": {
"brands": [
"uuid-1"
],
"platforms": [
"G2",
"Capterra"
],
"date_range": {
"type": "preset",
"value": "last_3_months"
}
},
"organisation_id": "660e8400-e29b-41d4-a716-446655440001",
"organisation_name": "Acme Corp",
"is_data": true
}
Example response (200, Multi-product comparison):
{
"data": [
{
"product_uuid": "660e8400-e29b-41d4-a716-446655440001",
"product_name": "Product 1",
"product_logo": "https://...",
"period": {
"start": "2025-10-28",
"end": "2026-01-27",
"duration": 3
},
"metrics": {
"new_reviews": {...},
"average_rating": {...},
"reviews_per_month": {...},
"review_velocity": {
"default_interval": "week",
"allowed_intervals": ["week", "month"],
"by_interval": {"week": {"data": []}, "month": {"data": []}},
"interval": "week",
"data": [{"group": "...", "count": 0, "cumulative_before": 0, "rating": 4.1}]
}
},
"breakdown": {
"ratings": {"5.0": 0, "4.0": 2, "3.0": 0, "2.0": 0, "1.0": 0},
"platforms": [
{"name": "G2", "has_data": 1},
{"name": "Capterra", "has_data": 2}
]
},
"is_scraping_reviews": false
},
{
"product_uuid": "770e8400-e29b-41d4-a716-446655440002",
"product_name": "Product 2",
"product_logo": "https://...",
"period": {...},
"metrics": {...},
"breakdown": {...},
"is_scraping_reviews": false
}
],
"competitors": [
{
"project_uuid": "660e8400-e29b-41d4-a716-446655440001",
"product_name": "Base Product",
"product_logo": "https://...",
"is_competitor": false,
"total_reviews": {"current": 10, "previous": 5, "change_percentage": 100.0, "trend": "up"},
"average_rating": {"current": 4.3, "previous": 4.1, "change_percentage": 4.9, "trend": "up"},
"rank": 1
}
],
"applied_filters": {
"brands": ["660e8400-e29b-41d4-a716-446655440001", "770e8400-e29b-41d4-a716-446655440002"],
"platforms": [],
"date_range": {"type": "preset", "value": "last_3_months"}
},
"organisation_id": "660e8400-e29b-41d4-a716-446655440000",
"organisation_name": "Acme Corp",
"is_data": true
}
Example response (403):
{
"message": "You do not have access to this organisation."
}
Example response (404):
{
"message": "Organisation 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 Review Cap Thresholds
requires authentication
Returns per-platform cap thresholds for each project in the organisation. Use projectIds to narrow the response to specific projects; omit (or pass []) for all.
Example request:
const url = new URL(
"https://backend-staging.subsig.com/api/organisations/6ff8f7f6-1eb3-3525-be4a-3932c805afed/reviews/cap-thresholds"
);
const headers = {
"Authorization": "Bearer 1|abc123...",
"Content-Type": "application/json",
"Accept": "application/json",
};
let body = {
"projectIds": [
"architecto"
],
"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.
Generate AEO/GEO Report
requires authentication
Generate a one-page AEO (Answer Engine Optimization) and GEO (Generative Engine Optimization) report from the organisation's filtered reviews. Reuses the same filter shape as the reviews index/export endpoints, samples the top-N most recent matching reviews, and sends them to OpenAI for analysis. Returns a structured JSON report ready to render as a one-pager.
Example request:
const url = new URL(
"https://backend-staging.subsig.com/api/organisations/6ff8f7f6-1eb3-3525-be4a-3932c805afed/reviews/report"
);
const headers = {
"Authorization": "Bearer 1|abc123...",
"Content-Type": "application/json",
"Accept": "application/json",
};
let body = {
"brands": [
"6ff8f7f6-1eb3-3525-be4a-3932c805afed"
],
"view_uuid": "6b72fe4a-5b40-307c-bc24-f79acf9a1bb9",
"platforms": [
"play_store"
],
"date_range": {
"type": "all_time",
"value": "last_6_months"
},
"date_range_custom": {
"start": "2026-08-04T12:24:26",
"end": "2052-08-27"
},
"date_from": "2026-08-04T12:24:26",
"date_to": "2052-08-27",
"rating_buckets": [
"3.0-3.9"
],
"languages": [
"architecto"
],
"read_status": "all",
"search": "n",
"sort_by": "creation_date",
"sort_direction": "asc",
"max_period": 67,
"max_results": 66,
"period_in_days": 17,
"page": 35
};
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 aggregation run status using run_id without organisation in URL.
requires authentication
Example request:
const url = new URL(
"https://backend-staging.subsig.com/api/reviews/aggregation/runs/architecto"
);
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.
Social Posts
APIs for fetching and filtering social media mentions from multiple platforms, scoped to organizations.
Get Organization Social Posts
requires authentication
Fetch and aggregate social posts from multiple projects/brands within an organization. Posts are fetched from external Scraper API, persisted to database, and returned with filters applied. Supports filtering by brands, platforms, types, sentiments, keywords, and more.
Sync social aggregation tables from mentions API historical data.
requires authentication
Uses projects.social_platform.brand_id and fetches both /v1/mentions/posts and /v1/mentions/comments for all supported platforms: reddit, github, stackoverflow, hackernews, x, youtube, linkedin.
Get social aggregation run status for an organisation-scoped run.
requires authentication
Organisation-level social analytics.
requires authentication
Returns mention count and average sentiment with period-over-period comparison. Filters: brands (brand_id), platforms, date_range, date_range_custom (start/end), or custom_date_range (from/to). data.is_scraping_mentions (boolean): true if a mentions scrape is currently in progress for any project in scope; use it to show a loading state in the UI. is_data (boolean): true if any aggregation record exists for the same scope (organisation, brands, platforms, date range) as the metrics; false otherwise.
List Social Post Views
requires authentication
Get all saved filter views for the current user in this organization.
Store Social Post View
requires authentication
Create a new saved filter view for social posts.
Show Social Post View
requires authentication
Get a specific saved filter view.
Update Social Post View
requires authentication
Update a saved filter view.
Delete Social Post View
requires authentication
Delete a saved filter view.
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
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
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
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. Returns {status: "detail_expired"} once raw_response has been pruned by retention
(Ticket 8), since the full chat detail this view exists to show is gone at that point.
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": 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.
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.
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.
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.
Show current production API key metadata for the selected organisation.
requires authentication
Returns the current masked production API key for the organisation selected by the X-Organisation-Id header. Plaintext API keys are never returned from this endpoint.
Example request:
const url = new URL(
"https://backend-staging.subsig.com/api/api-key"
);
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,
"created_at": "2026-06-05T10:00:00.000000Z",
"updated_at": "2026-06-05T10:00:00.000000Z"
}
}
Example response (200, No API key exists):
{
"data": 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.
Generate a production API key. Plaintext is returned only on first creation.
requires authentication
Creates one production API key for the current organisation. The plaintext key is shown only in the first successful response and is never stored.
Example request:
const url = new URL(
"https://backend-staging.subsig.com/api/api-key/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 = {
"confirmed": true
};
fetch(url, {
method: "POST",
headers,
body: JSON.stringify(body),
}).then(response => response.json());Example response (200, Idempotent replay):
{
"message": "API key generation request replayed.",
"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": true
}
}
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,
"rotation_expires_at": null,
"expires_at": null,
"revoked_at": null,
"created_at": "2026-06-05T10:00:00.000000Z",
"updated_at": "2026-06-05T10:00:00.000000Z"
}
}
Example response (409, Active key already exists):
{
"message": "An active production API key already exists for this account.",
"error": {
"code": "active_api_key_exists"
}
}
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 the current production API key. Plaintext is returned only on first rotation.
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-key/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 all currently usable production API keys for the selected organisation.
requires authentication
Revokes both the active key and any old rotating key immediately. The request must include the exact confirmation text REVOKE.
Example request:
const url = new URL(
"https://backend-staging.subsig.com/api/api-key/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.
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": 7900,
"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.
POST api/organisations/{organisation_uuid}/reviews/export
Example request:
const url = new URL(
"https://backend-staging.subsig.com/api/organisations/6ff8f7f6-1eb3-3525-be4a-3932c805afed/reviews/export"
);
const headers = {
"Content-Type": "application/json",
"Accept": "application/json",
};
let body = {
"brands": [
"6ff8f7f6-1eb3-3525-be4a-3932c805afed"
],
"view_uuid": "6b72fe4a-5b40-307c-bc24-f79acf9a1bb9",
"platforms": [
"clutch"
],
"date_range": {
"type": "custom",
"value": "all_time"
},
"date_range_custom": {
"start": "2026-08-04T12:24:24",
"end": "2052-08-27"
},
"date_from": "2026-08-04T12:24:24",
"date_to": "2052-08-27",
"rating_buckets": [
"1.0-1.9"
],
"languages": [
"architecto"
],
"read_status": "unread",
"search": "n",
"sort_by": "created_at",
"sort_direction": "asc",
"max_period": 67,
"max_results": 66,
"period_in_days": 17,
"page": 35
};
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/organisations/{organisation_uuid}/social/export
Bulk Deactivate Links (Temporary)
Disabled unless TEMP_BULK_REVIEW_SCRAPE_ENABLED is set. When TEMP_BULK_REVIEW_SCRAPE_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/links/deactivate-bulk-temporary"
);
const headers = {
"X-Temporary-Bulk-Token": "{token}",
"Content-Type": "application/json",
"Accept": "application/json",
};
let body = {
"platform": "b",
"chunk_size": 22,
"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.
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": 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.
POST api/organisations/{organisation_uuid}/universal-keywords/analytics
Example request:
const url = new URL(
"https://backend-staging.subsig.com/api/organisations/205ae76c-bd73-426b-a36a-8338353b9542/universal-keywords/analytics"
);
const headers = {
"Content-Type": "application/json",
"Accept": "application/json",
};
let body = {
"keyword_uuids": [
"6ff8f7f6-1eb3-3525-be4a-3932c805afed"
],
"platforms": [
"x"
],
"date_range": {
"type": "all_time",
"value": "last_2_years"
},
"date_range_custom": {
"start": "2026-08-04T12:24:25",
"end": "2052-08-27"
},
"max_results": 22,
"max_period": 67
};
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": "bearer",
"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": true
};
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.
POST api/notifications/email
Example request:
const url = new URL(
"https://backend-staging.subsig.com/api/notifications/email"
);
const headers = {
"Content-Type": "application/json",
"Accept": "application/json",
};
let body = {
"email": "gbailey@example.net",
"template": "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.
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": "7d",
"data_source": "gemini"
};
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 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/brands
Example request:
const url = new URL(
"https://backend-staging.subsig.com/api/v1/brands"
);
const headers = {
"Content-Type": "application/json",
"Accept": "application/json",
};
let body = {
"query": "b",
"limit": 22,
"cursor": "g"
};
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
x-request-id: 4ec86744-dc62-4210-9c3d-0acc726d4a7c
vary: Origin
{
"error": {
"code": "unauthorized",
"message": "Unauthorized.",
"docs_url": "https://docs.subsig.com/errors/unauthorized",
"request_id": "4ec86744-dc62-4210-9c3d-0acc726d4a7c"
}
}
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/mentions
Example request:
const url = new URL(
"https://backend-staging.subsig.com/api/v1/mentions"
);
const headers = {
"Content-Type": "application/json",
"Accept": "application/json",
};
let body = {
"brand": "b",
"start_date": "2022-08-29",
"end_date": "2022-08-29",
"platform": "n",
"sentiment": "neutral",
"limit": 67,
"cursor": "z"
};
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
x-request-id: b327c42f-0fac-4b3a-9dc5-00b4b6a2c492
vary: Origin
{
"error": {
"code": "unauthorized",
"message": "Unauthorized.",
"docs_url": "https://docs.subsig.com/errors/unauthorized",
"request_id": "b327c42f-0fac-4b3a-9dc5-00b4b6a2c492"
}
}
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/reviews
Example request:
const url = new URL(
"https://backend-staging.subsig.com/api/v1/reviews"
);
const headers = {
"Content-Type": "application/json",
"Accept": "application/json",
};
let body = {
"brand": "b",
"start_date": "2022-08-29",
"end_date": "2022-08-29",
"platform": "n",
"min_rating": 1,
"max_rating": 2,
"limit": 27,
"cursor": "i"
};
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
x-request-id: 8dd26224-4f47-49e9-944e-d1e6727cc2ee
vary: Origin
{
"error": {
"code": "unauthorized",
"message": "Unauthorized.",
"docs_url": "https://docs.subsig.com/errors/unauthorized",
"request_id": "8dd26224-4f47-49e9-944e-d1e6727cc2ee"
}
}
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",
"platforms": [
"reddit",
"github",
"x"
]
};
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.
Home
Feeds the dashboard "Home" overview page (KPI cards, visibility trend/ranking, recommendations, module summary). reviews_count/mentions_count reuse the same ReviewAnalyticsService/SocialAnalyticsService aggregation queries as the Review Monitoring and Brand Mentions pages, scoped to the organisation's own (non-competitor) project, so the figures shown here always match what those pages report. Reuses the exact same AiVisibilityBrandRankService calls as the Visibility tab (AiVisibilityVisibilityController) rather than re-deriving metrics.
The standalone Recommendations page's data source -- same real recommendation logic overview() already computes for the Home dashboard's top-3 widget (buildRecommendations()), just with a higher limit since a dedicated page can reasonably show more than a 3-card preview. Deliberately NOT extracted into a separate service: every input buildRecommendations() needs (brandMetrics, pendingSuggestionsCount, moduleCardMetrics) is already a method on this controller with its own real DB/service dependencies already wired via the constructor -- re-running that same gathering for a second, independently-timed page request is consistent with how overview() itself already works (nothing here is cached except the LLM-backed content-gap recommendation, which buildRecommendations() already handles internally).
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.
Home Overview
Example request:
const url = new URL(
"https://backend-staging.subsig.com/api/organisations/6ff8f7f6-1eb3-3525-be4a-3932c805afed/home/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.
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.
Notifications
APIs for managing notification rules
List Notifications
requires authentication
Get all notification rules for the current organisation.
Example request:
const url = new URL(
"https://backend-staging.subsig.com/api/notifications"
);
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": "770e8400-e29b-41d4-a716-446655440000",
"name": "Daily Review Alerts",
"trigger_type": "new_review",
"languages": [
"en",
"es"
],
"auto_translate": true,
"rating_filters": [
1,
2,
3
],
"sentiment_filters": [
"negative"
],
"condition_filters": {
"logic": "and",
"rules": [
{
"field": "platforms",
"operator": "contains",
"value": [
"g2"
]
}
]
},
"notification_frequency": "instant",
"read_status": "all",
"is_active": true,
"channels": [],
"projects": [],
"created_at": "2025-12-31T12:00:00.000000Z",
"updated_at": "2025-12-31T12:00:00.000000Z"
}
]
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 Notification
requires authentication
Create a new notification rule with channels and project mappings.
Example request:
const url = new URL(
"https://backend-staging.subsig.com/api/notifications"
);
const headers = {
"Authorization": "Bearer 1|abc123...",
"Content-Type": "application/json",
"Accept": "application/json",
};
let body = {
"name": "Daily Review Alerts",
"trigger_type": "new_review",
"is_active": true,
"languages": [
"en",
"es",
"fr",
"de"
],
"auto_translate": true,
"rating_filters": [
1,
2,
3,
4,
5
],
"sentiment_filters": [
"positive",
"neutral",
"negative"
],
"keyword": [
"ai",
"support"
],
"content_type": "post",
"notification_frequency": "instant",
"read_status": "all",
"condition_filters": {
"logic": "and",
"rules": [
{
"field": "platforms",
"operator": "contains",
"value": "[\"g2\", \"capterra\"]",
"logic": "or",
"rules": [
[]
]
}
]
},
"channels": [
{
"channel_type": "slack",
"is_active": true,
"config": {
"channel_names": [
"alerts",
"incidents"
],
"channel_url": "https:\/\/hooks.slack.com\/services\/xxx",
"recipients": [
"alerts@company.com"
],
"template": "generic",
"url": "https:\/\/your-server.com\/webhook\/notifications",
"webhook_url": "http:\/\/www.bailey.biz\/quos-velit-et-fugiat-sunt-nihil-accusantium-harum.html"
}
}
],
"projects": [
{
"project_uuid": "5f1812b1-15f7-432d-996a-9ab5cfbe01d6",
"is_active": true,
"platforms": {
"review": [
"g2",
"capterra",
"product_hunt",
"play_store"
],
"social": [
"reddit",
"hackernews",
"linkedin"
]
},
"product_logo": "https:\/\/cdn.example.com\/logo.png"
}
]
};
fetch(url, {
method: "POST",
headers,
body: JSON.stringify(body),
}).then(response => response.json());Example response (201, Success):
{
"message": "Notification created successfully.",
"data": {
"uuid": "770e8400-e29b-41d4-a716-446655440000",
"name": "Daily Review Alerts",
"trigger_type": "new_review",
"languages": [
"en",
"es"
],
"auto_translate": true,
"rating_filters": [
1,
2,
3,
4,
5
],
"sentiment_filters": [
"positive",
"neutral",
"negative"
],
"condition_filters": {
"logic": "and",
"rules": [
{
"field": "platforms",
"operator": "contains",
"value": [
"g2",
"capterra"
]
},
{
"field": "sentiment",
"operator": "contains",
"value": [
"negative"
]
}
]
},
"notification_frequency": "instant",
"read_status": "unread",
"keyword": null,
"content_type": "post",
"is_active": true,
"channels": [
{
"uuid": "880e8400-e29b-41d4-a716-446655440001",
"channel_type": "slack",
"is_active": true,
"config": {
"channel_names": [
"alerts",
"incidents"
],
"channel_url": "https://hooks.slack.com/services/xxx",
"recipients": [
"alerts@company.com"
],
"url": "https://your-server.com/webhook/notifications"
}
}
],
"projects": [
{
"uuid": "990e8400-e29b-41d4-a716-446655440002",
"project_uuid": "5f1812b1-15f7-432d-996a-9ab5cfbe01d6",
"project_name": "Acme App",
"is_active": true,
"product_logo": "https://cdn.example.com/logo.png",
"platforms": {
"review": [
"g2",
"capterra"
],
"social": [
"reddit"
]
}
}
],
"created_at": "2025-12-31T12:00:00.000000Z",
"updated_at": "2025-12-31T12:00:00.000000Z"
}
}
Example response (422, Validation Error):
{
"message": "The name field is required. (and 2 more errors)",
"errors": {
"name": [
"The name field is required."
],
"channels": [
"At least one channel is required."
],
"projects": [
"Select at least one brand."
]
}
}
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 Notification
requires authentication
Get details of a specific notification.
Example request:
const url = new URL(
"https://backend-staging.subsig.com/api/notifications/770e8400-e29b-41d4-a716-446655440000"
);
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": "770e8400-e29b-41d4-a716-446655440000",
"name": "Daily Review Alerts",
"trigger_type": "new_review",
"languages": [
"en",
"es"
],
"auto_translate": true,
"rating_filters": [
1,
2,
3
],
"sentiment_filters": [
"negative"
],
"condition_filters": {
"logic": "and",
"rules": [
{
"field": "platforms",
"operator": "contains",
"value": [
"g2"
]
}
]
},
"notification_frequency": "instant",
"read_status": "all",
"keyword": null,
"is_active": true,
"channels": [],
"projects": [],
"created_at": "2025-12-31T12:00:00.000000Z",
"updated_at": "2025-12-31T12: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 Notification
requires authentication
Update an existing notification rule with channels and project mappings.
Example request:
const url = new URL(
"https://backend-staging.subsig.com/api/notifications/770e8400-e29b-41d4-a716-446655440000"
);
const headers = {
"Authorization": "Bearer 1|abc123...",
"Content-Type": "application/json",
"Accept": "application/json",
};
let body = {
"name": "Daily Review Alerts",
"trigger_type": "new_review",
"is_active": true,
"languages": [
"en",
"es",
"fr",
"de"
],
"auto_translate": true,
"rating_filters": [
1,
2,
3,
4,
5
],
"sentiment_filters": [
"positive",
"neutral",
"negative"
],
"keyword": [
"ai",
"support"
],
"content_type": "post",
"notification_frequency": "instant",
"read_status": "all",
"condition_filters": {
"logic": "and",
"rules": [
{
"field": "platforms",
"operator": "contains",
"value": "[\"g2\", \"capterra\"]"
}
]
},
"channels": [
{
"channel_type": "slack",
"is_active": true,
"config": {
"channel_names": [
"alerts",
"incidents"
],
"channel_url": "https:\/\/hooks.slack.com\/services\/xxx",
"recipients": [
"alerts@company.com"
],
"template": "mention",
"url": "https:\/\/your-server.com\/webhook\/notifications",
"webhook_url": "http:\/\/www.bailey.biz\/quos-velit-et-fugiat-sunt-nihil-accusantium-harum.html"
}
}
],
"projects": [
{
"project_uuid": "5f1812b1-15f7-432d-996a-9ab5cfbe01d6",
"is_active": true,
"platforms": {
"review": [
"gartner"
],
"social": [
"stackoverflow"
]
},
"product_logo": "g"
}
]
};
fetch(url, {
method: "PUT",
headers,
body: JSON.stringify(body),
}).then(response => response.json());Example response (200, Success):
{
"message": "Notification updated successfully.",
"data": {
"uuid": "770e8400-e29b-41d4-a716-446655440000",
"name": "Updated Review Alerts",
"trigger_type": "new_review",
"languages": [
"en"
],
"auto_translate": false,
"rating_filters": [
1,
2
],
"sentiment_filters": [
"negative"
],
"condition_filters": {
"logic": "and",
"rules": [
{
"field": "sentiment",
"operator": "contains",
"value": [
"negative"
]
}
]
},
"notification_frequency": "daily",
"read_status": "unread",
"keyword": null,
"content_type": "comment",
"is_active": true,
"channels": [],
"projects": [],
"created_at": "2025-12-31T12:00:00.000000Z",
"updated_at": "2025-12-31T13:00:00.000000Z"
}
}
Example response (404, Not Found):
{
"message": "Notification 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 Notification
requires authentication
Delete a notification and all associated channels and project mappings.
Example request:
const url = new URL(
"https://backend-staging.subsig.com/api/notifications/770e8400-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": "Notification deleted successfully."
}
Example response (404):
{
"message": "Notification 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",
"review_platforms": {
"g2": {
"enabled": true,
"url": "https:\/\/g2.com\/products\/acme"
},
"capterra": {
"enabled": false,
"url": null
}
},
"reddit_keywords": [
"acme",
"acme crm"
],
"reddit_brand_name": "Acme",
"platforms": [
"reddit",
"github",
"x"
],
"negative_keywords": [
"spam",
"competitor"
],
"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",
"review_platforms": {},
"reddit_keywords": [
"acme",
"acme crm"
],
"negative_keywords": [],
"reddit_brand_name": "Acme",
"platforms": [
"reddit",
"github"
],
"social_platform": {}
},
"scraper_sync": {
"success": true,
"synced_platforms": [
"g2",
"capterra"
],
"errors": []
},
"brand_sync": {
"success": true,
"brand_id": "12345",
"error": null
}
}
}
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",
"review_platforms": {
"g2": {
"enabled": true,
"url": "https:\/\/g2.com\/products\/acme"
},
"capterra": {
"enabled": false,
"url": null
}
},
"reddit_keywords": [
"acme",
"acme crm"
],
"reddit_brand_name": "Acme",
"platforms": [
"reddit",
"github",
"x"
],
"negative_keywords": [
"spam",
"competitor"
],
"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",
"review_platforms": {},
"reddit_keywords": [
"acme",
"acme crm"
],
"negative_keywords": [],
"reddit_brand_name": "Acme",
"platforms": [
"reddit",
"github"
],
"social_platform": {}
},
"scraper_sync": {
"success": true,
"synced_platforms": [
"g2",
"capterra"
],
"errors": []
},
"brand_sync": {
"success": true,
"brand_id": "12345",
"error": null
}
}
}
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.
Search Platform URLs
requires authentication
Search for a product's listing pages across multiple review platforms using Perplexity AI-powered web search. Product name used for lookup is derived from the product_website domain label. Each URL is validated and assigned a status: valid, invalid, or needs_review.
Example request:
const url = new URL(
"https://backend-staging.subsig.com/api/platform-urls"
);
const headers = {
"Authorization": "Bearer 1|abc123...",
"Content-Type": "application/json",
"Accept": "application/json",
};
let body = {
"product_name": "Slack",
"product_website": "https:\/\/slack.com",
"platform": "g2"
};
fetch(url, {
method: "POST",
headers,
body: JSON.stringify(body),
}).then(response => response.json());Example response (200, All platforms):
{
"product_name": "Slack",
"product_website": "https://slack.com",
"reviews": {
"g2": {
"url": "https://www.g2.com/products/slack/reviews",
"status": "valid"
},
"capterra": {
"url": "https://www.capterra.com/p/135003/Slack/",
"status": "valid"
},
"software_advice": {
"url": "https://www.softwareadvice.com/team-communication/slack-profile/",
"status": "needs_review"
},
"trustpilot": {
"url": "https://www.trustpilot.com/review/slack.com",
"status": "valid"
},
"omr_reviews": {
"url": "https://omr.com/reviews/product/slack",
"status": "valid"
},
"clutch": {
"url": null,
"status": "invalid"
},
"sourceforge": {
"url": "https://sourceforge.net/software/product/Slack/",
"status": "valid"
},
"product_hunt": {
"url": "https://www.producthunt.com/products/slack",
"status": "valid"
},
"hubspot_directory": {
"url": null,
"status": "invalid"
},
"goodfirms": {
"url": "https://www.goodfirms.co/software/slack",
"status": "valid"
},
"chrome_web_store": {
"url": null,
"status": "invalid"
},
"google_workspace_marketplace": {
"url": "https://workspace.google.com/marketplace/app/slack/429783454934",
"status": "needs_review"
},
"app_store": {
"url": null,
"status": "invalid"
},
"play_store": {
"url": null,
"status": "invalid"
},
"subscribed_fyi": {
"url": "https://subscribed.fyi/slack/reviews/",
"status": "valid"
}
}
}
Example response (200, Filtered by platform):
{
"product_name": "Slack",
"product_website": "https://slack.com",
"reviews": {
"g2": {
"url": "https://www.g2.com/products/slack/reviews",
"status": "valid"
}
}
}
Example response (422, Validation error):
{
"message": "The product name field is required.",
"errors": {
"product_name": [
"The product 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.
Generate Reddit Keywords
requires authentication
Generate commonly used variations of a brand name for Reddit tracking, including misspellings, abbreviations, and nicknames.
Example request:
const url = new URL(
"https://backend-staging.subsig.com/api/reddit-keywords"
);
const headers = {
"Authorization": "Bearer 1|abc123...",
"Content-Type": "application/json",
"Accept": "application/json",
};
let body = {
"brand_name": "Salesforce"
};
fetch(url, {
method: "POST",
headers,
body: JSON.stringify(body),
}).then(response => response.json());Example response (200):
{
"brand_name": "Salesforce",
"top_variations": {
"exact": "Salesforce",
"misspellings": [
"Salesfoce",
"Salseforce",
"Saleforce"
],
"abbreviations": [
"SF",
"SFDC"
],
"nicknames": [
"The Force",
"SF CRM",
"Sales Cloud"
]
}
}
Example response (422, Validation error):
{
"message": "The brand name field is required.",
"errors": {
"brand_name": [
"The brand 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
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"
};
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"
};
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.
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.
Create Claim Profile (External)
Create a new claim profile from an external website. This endpoint does not require user authentication but requires a valid external API key.
Example request:
const url = new URL(
"https://backend-staging.subsig.com/api/external/claim-profiles"
);
const headers = {
"Authorization": "required The external API key. Example: Bearer your-secure-external-api-key",
"Content-Type": "application/json",
"Accept": "application/json",
};
let body = {
"scraper_product_id": 123,
"product_name": "Lovable",
"product_url": "https:\/\/lovable.dev",
"business_email": "john@company.com",
"job_title": "Product Manager",
"business_phone": "+1234567890",
"is_verified": true
};
fetch(url, {
method: "POST",
headers,
body: JSON.stringify(body),
}).then(response => response.json());Example response (201, Success):
{
"message": "Claim profile submitted successfully.",
"data": {
"uuid": "880e8400-e29b-41d4-a716-446655440000",
"product_name": "Lovable",
"business_email": "john@company.com",
"status": "pending",
"is_verified": false
}
}
Example response (401, Unauthorized):
{
"message": "Unauthorized."
}
Example response (422, Validation Error):
{
"message": "The business email is required.",
"errors": {
"business_email": [
"The business email 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.
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",
"review_platforms": {
"g2": {
"enabled": true,
"url": "https://g2.com/products/acme"
}
},
"reddit_keywords": [
"acme",
"acme app"
],
"negative_keywords": [],
"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",
"review_platforms": {
"g2": {
"enabled": true,
"url": "https:\/\/g2.com\/products\/acme"
},
"capterra": {
"enabled": false,
"url": null
}
},
"reddit_keywords": [
"acme",
"acme app"
],
"reddit_brand_name": "Acme",
"platforms": [
"reddit",
"github",
"x"
],
"social_platform": {
"twitter": {
"enabled": true,
"url": "https:\/\/twitter.com\/acme"
}
}
};
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",
"review_platforms": {},
"reddit_keywords": [
"acme"
],
"negative_keywords": [],
"reddit_brand_name": "Acme",
"platforms": [
"reddit",
"github"
],
"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/a5e1d9fe-c689-4c53-b7fa-007d75c94c9c"
);
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",
"review_platforms": {
"g2": {
"enabled": true,
"url": "https://g2.com/products/acme"
}
},
"reddit_keywords": [
"acme",
"acme app"
],
"negative_keywords": [],
"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/a5e1d9fe-c689-4c53-b7fa-007d75c94c9c"
);
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",
"review_platforms": {
"g2": {
"enabled": true,
"url": "https:\/\/g2.com\/products\/acme"
},
"capterra": {
"enabled": false,
"url": null
}
},
"reddit_keywords": [
"acme",
"acme pro"
],
"reddit_brand_name": "Acme",
"platforms": [
"reddit",
"github",
"x"
],
"social_platform": {
"twitter": {
"enabled": true,
"url": "https:\/\/twitter.com\/acme"
}
}
};
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",
"review_platforms": {},
"reddit_keywords": [
"acme",
"acme pro"
],
"negative_keywords": [],
"reddit_brand_name": "Acme",
"platforms": [
"reddit",
"github"
],
"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/a5e1d9fe-c689-4c53-b7fa-007d75c94c9c"
);
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.
Read status (reviews and mentions)
Mark and check read status per project using scraper identity (platform + scraper id).
Frontend uses scraper API directly; these endpoints store and return read state by project.
Identifiers match the scraper API: use data[].id as scraper_review_id/scraper_post_id and data[].platform as platform.
Mark a review as read for a project
requires authentication
Store that a review (identified by platform + scraper_review_id from the scraper API) is read for the given project.
Example request:
const url = new URL(
"https://backend-staging.subsig.com/api/organisations/660e8400-e29b-41d4-a716-446655440001/reviews/read"
);
const headers = {
"Authorization": "Bearer 1|abc123...",
"Content-Type": "application/json",
"Accept": "application/json",
};
let body = {
"project_uuid": "550e8400-e29b-41d4-a716-446655440002",
"platform": "G2",
"scraper_review_id": 987654
};
fetch(url, {
method: "POST",
headers,
body: JSON.stringify(body),
}).then(response => response.json());Example response (200):
{
"message": "Review marked as read."
}
Example response (403):
{
"message": "You do not have access to this organisation."
}
Example response (403):
{
"message": "You do not have access to this project."
}
Example response (404):
{
"message": "Organisation 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 read status for a review
requires authentication
Check whether a review (identified by platform + scraper_review_id) is marked as read for the given project.
Example request:
const url = new URL(
"https://backend-staging.subsig.com/api/organisations/660e8400-e29b-41d4-a716-446655440001/reviews/read-status"
);
const params = {
"project_uuid": "550e8400-e29b-41d4-a716-446655440002",
"platform": "G2",
"scraper_review_id": "987654",
};
Object.keys(params)
.forEach(key => url.searchParams.append(key, params[key]));
const headers = {
"Authorization": "Bearer 1|abc123...",
"Content-Type": "application/json",
"Accept": "application/json",
};
let body = {
"project_uuid": "6ff8f7f6-1eb3-3525-be4a-3932c805afed",
"platform": "g",
"scraper_review_id": 66
};
fetch(url, {
method: "GET",
headers,
body: JSON.stringify(body),
}).then(response => response.json());Example response (200):
{
"read": true
}
Example response (200):
{
"read": false
}
Example response (403):
{
"message": "You do not have access to this organisation."
}
Example response (403):
{
"message": "You do not have access to this project."
}
Example response (404):
{
"message": "Organisation not found."
}
Example response (404):
{
"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.
Get list of read review IDs for multiple projects
requires authentication
Returns scraper review IDs (data[].id) per project. Use with optional platform filter. Frontend can map by project_uuid to show read/unread state per brand.
Example request:
const url = new URL(
"https://backend-staging.subsig.com/api/organisations/660e8400-e29b-41d4-a716-446655440001/reviews/read-ids"
);
const params = {
"project_uuids[0]": "550e8400-e29b-41d4-a716-446655440002",
"project_uuids[1]": "660e8400-e29b-41d4-a716-446655440003",
"project_uuids[]": "550e8400-e29b-41d4-a716-446655440002",
"platform": "G2",
};
Object.keys(params)
.forEach(key => url.searchParams.append(key, params[key]));
const headers = {
"Authorization": "Bearer 1|abc123...",
"Content-Type": "application/json",
"Accept": "application/json",
};
let body = {
"project_uuids": [
"6ff8f7f6-1eb3-3525-be4a-3932c805afed"
],
"platform": "g"
};
fetch(url, {
method: "GET",
headers,
body: JSON.stringify(body),
}).then(response => response.json());Example response (200):
{
"data": [
{
"project_uuid": "550e8400-e29b-41d4-a716-446655440002",
"scraper_review_ids": [
987654,
987655
]
},
{
"project_uuid": "660e8400-e29b-41d4-a716-446655440003",
"scraper_review_ids": []
}
]
}
Example response (403):
{
"message": "You do not have access to this organisation."
}
Example response (403):
{
"message": "You do not have access to one or more projects."
}
Example response (404):
{
"message": "Organisation not found."
}
Example response (404):
{
"message": "One or more projects 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.
Mark a mention as read for a project
requires authentication
Store that a mention/social post (identified by platform + scraper_post_id from the scraper API) is read for the given project.
Get read status for a mention
requires authentication
Check whether a mention (identified by platform + scraper_post_id) is marked as read for the given project.
Get list of read mention IDs for multiple projects
requires authentication
Returns scraper post IDs (data[].id) per project. Use with optional platform filter. Frontend can map by project_uuid to show read/unread state per brand.
Review Views
APIs for managing saved review filter views (organization-scoped).
Get All Review Views
requires authentication
List all saved review views for the authenticated user in a specific organisation. Views are ordered by sort_order and creation date.
Example request:
const url = new URL(
"https://backend-staging.subsig.com/api/organisations/660e8400-e29b-41d4-a716-446655440001/review-views"
);
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": "a1b2c3d4-...",
"name": "Critical Reviews",
"sort_order": 0,
"filters": {
"brands": [
"uuid-1",
"uuid-2"
],
"platforms": [
"G2",
"Capterra"
],
"rating_buckets": [
"1.0-1.9",
"2.0-2.9"
],
"date_range": {
"type": "preset",
"value": "last_3_months"
}
},
"created_at": "2026-01-15T10:30:00.000Z",
"updated_at": "2026-01-20T14:22:00.000Z"
}
]
}
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 Review View
requires authentication
Create a new saved review view for the authenticated user.
Example request:
const url = new URL(
"https://backend-staging.subsig.com/api/organisations/660e8400-e29b-41d4-a716-446655440001/review-views"
);
const headers = {
"Authorization": "Bearer 1|abc123...",
"Content-Type": "application/json",
"Accept": "application/json",
};
let body = {
"name": "High Priority Reviews",
"filters": {
"platforms": [
"G2"
],
"rating_buckets": [
"1.0-1.9"
],
"brands": [
"uuid-1",
"uuid-2"
],
"date_range": {
"type": "preset",
"value": "last_3_months"
},
"date_range_custom": {
"start": "2026-01-01",
"end": "2026-01-24"
},
"languages": [
"en",
"de",
"es"
],
"read_status": "unread",
"search": "customer support",
"sort_by": "creation_date",
"sort_direction": "desc"
}
};
fetch(url, {
method: "POST",
headers,
body: JSON.stringify(body),
}).then(response => response.json());Example response (201):
{
"data": {
"uuid": "e5f6g7h8-...",
"name": "High Priority Reviews",
"sort_order": 1,
"filters": {...},
"created_at": "2026-01-24T10:00:00.000Z",
"updated_at": "2026-01-24T10:00:00.000Z"
}
}
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 Review View
requires authentication
Get a specific saved review view.
Example request:
const url = new URL(
"https://backend-staging.subsig.com/api/organisations/660e8400-e29b-41d4-a716-446655440001/review-views/a1b2c3d4-e5f6-g7h8-i9j0-k1l2m3n4o5p6"
);
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": "a1b2c3d4-...",
"name": "Critical Reviews",
"sort_order": 0,
"filters": {...},
"created_at": "2026-01-15T10:30:00.000Z",
"updated_at": "2026-01-20T14:22:00.000Z"
}
}
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 Review View
requires authentication
Update an existing saved review view. Can update name and/or filters.
Example request:
const url = new URL(
"https://backend-staging.subsig.com/api/organisations/660e8400-e29b-41d4-a716-446655440001/review-views/a1b2c3d4-e5f6-g7h8-i9j0-k1l2m3n4o5p6"
);
const headers = {
"Authorization": "Bearer 1|abc123...",
"Content-Type": "application/json",
"Accept": "application/json",
};
let body = {
"name": "Critical Reviews - Updated",
"filters": {
"platforms": [
"G2"
],
"brands": [
"a4855dc5-0acb-33c3-b921-f4291f719ca0"
],
"date_range": {
"type": "custom",
"value": "last_12_months"
},
"date_range_custom": {
"start": "2026-08-04T12:24:26",
"end": "2052-08-27"
},
"rating_buckets": [
"2.0-2.9"
],
"languages": [
"architecto"
],
"read_status": "read",
"search": "n",
"sort_by": "creation_date",
"sort_direction": "desc"
}
};
fetch(url, {
method: "PUT",
headers,
body: JSON.stringify(body),
}).then(response => response.json());Example response (200):
{
"data": {
"uuid": "a1b2c3d4-...",
"name": "Critical Reviews - Updated",
"sort_order": 0,
"filters": {...},
"created_at": "2026-01-15T10:30:00.000Z",
"updated_at": "2026-01-24T11:15:00.000Z"
}
}
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 Review View
requires authentication
Delete a saved review view.
Example request:
const url = new URL(
"https://backend-staging.subsig.com/api/organisations/660e8400-e29b-41d4-a716-446655440001/review-views/a1b2c3d4-e5f6-g7h8-i9j0-k1l2m3n4o5p6"
);
const headers = {
"Authorization": "Bearer 1|abc123...",
"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.
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 (display denominators) and global enforcement caps in limits.*_hard.
Usage is all-time across organisations owned by the billing user (same scope as hard-limit enforcement).
usage.reviews and usage.mentions are capped at soft+hard for display; reviews_raw and mentions_raw are uncapped totals.
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": {
"reviews_limit": 3000,
"mentions_limit": 4000,
"competitor_brands_limit": 3,
"universal_keywords_limit": 10,
"history_months": 6,
"mentions_history_months": 3,
"refresh_interval": "daily",
"users_limit": null,
"workspaces_limit": null,
"data_export_type": null,
"ai_visibility_prompts_limit": 20
},
"limits": {
"reviews_soft": 3000,
"mentions_soft": 4000,
"reviews_hard": 1000,
"mentions_hard": 500
},
"usage": {
"reviews": 900,
"mentions": 1000,
"reviews_raw": 900,
"mentions_raw": 1200,
"reviews_per_platform": {
"G2": 50,
"Capterra": 50
},
"mentions_per_platform": {
"reddit": 200
},
"competitor_brands": 0,
"universal_keywords": 4,
"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": {
"mentions": {
"price": 23,
"quantity": 64
},
"reviews": {
"price": 43,
"quantity": 7
},
"ai_visibility_prompts": {
"price": 79,
"quantity": 73
},
"keywords": {
"price": 13,
"quantity": 49
},
"review_platforms": {
"price": 43,
"quantity": 61
}
}
};
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 Mentions/Reviews 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": {
"mentions": {
"price": 14000,
"quantity": 7000
},
"reviews": {
"price": 13000,
"quantity": 11000
}
},
"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_mentions_123",
"stripe_price_id": "price_inline_mentions_1",
"price": 2000,
"mentions_quantity": 50,
"reviews_quantity": 0,
"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 Mentions/Reviews 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": {
"mentions": {
"price": 14000,
"quantity": 7000
},
"reviews": {
"price": 13000,
"quantity": 11000
}
},
"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.
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.
Get Platform Limits
requires authentication
Returns the maximum allocated review and mention counts across all platforms,
broken down per project. Pass project_uuids[] to scope to specific projects;
omit to return all projects in the organisation.
Allocated values come from the most recent settled scrape job per platform.
Example request:
const url = new URL(
"https://backend-staging.subsig.com/api/organisations/6ff8f7f6-1eb3-3525-be4a-3932c805afed/platform-limits"
);
const params = {
"project_uuids": "",
};
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": [
{
"project_uuid": "550e8400-e29b-41d4-a716-446655440000",
"project_name": "Slack",
"reviews_limit": [
{
"url": "https://www.trustpilot.com/review/slack.com",
"platform": "trustpilot",
"max_limit": 200
},
{
"url": "https://www.capterra.com/p/1234567890/slack",
"platform": "capterra",
"max_limit": 187
}
],
"mentions_limit": [
{
"brand_id": 1,
"platform": "X",
"max_limit": 200
},
{
"brand_id": 1,
"platform": "Reddit",
"max_limit": 150
}
]
}
]
}
Example response (403, No organisation access):
{
"message": "You do not have access to this organisation."
}
Example response (404, Organisation not found):
{
"message": "Organisation 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.
Universal Keywords
Browse endpoint for individual universal-keyword mentions. Pulls live from the scraper's own
filtered/paginated /v1/universal-keyword-mentions endpoint (ScraperService::
fetchUniversalKeywordMentions()) on every request -- the same pull-based pattern Reviews/Mentions
already use -- rather than the interim local universal_keyword_mentions table this previously
read from while that scraper endpoint didn't exist yet.
GET api/organisations/{organisation_uuid}/universal-keywords/mentions
Example request:
const url = new URL(
"https://backend-staging.subsig.com/api/organisations/205ae76c-bd73-426b-a36a-8338353b9542/universal-keywords/mentions"
);
const headers = {
"Content-Type": "application/json",
"Accept": "application/json",
};
let body = {
"keyword_uuids": [
"6ff8f7f6-1eb3-3525-be4a-3932c805afed"
],
"platforms": [
"hackernews"
],
"sentiments": [
"neutral"
],
"intents": [
"g"
],
"type": "post",
"search": "z",
"start_date": "2026-08-04T12:24:25",
"end_date": "2052-08-27",
"page": 22,
"per_page": 7
};
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.
List universal keywords.
requires authentication
Example request:
const url = new URL(
"https://backend-staging.subsig.com/api/organisations/205ae76c-bd73-426b-a36a-8338353b9542/universal-keywords"
);
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.
Create a universal keyword.
requires authentication
Example request:
const url = new URL(
"https://backend-staging.subsig.com/api/organisations/205ae76c-bd73-426b-a36a-8338353b9542/universal-keywords"
);
const headers = {
"Authorization": "Bearer 1|abc123...",
"Content-Type": "application/json",
"Accept": "application/json",
};
let body = {
"keyword": "b",
"context": "n",
"include_keywords_match": "any",
"include_keywords": [
"g"
],
"negative_terms": [
"z"
],
"platforms": [
"stackoverflow"
],
"is_active": 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.
Show a universal keyword.
requires authentication
Example request:
const url = new URL(
"https://backend-staging.subsig.com/api/organisations/205ae76c-bd73-426b-a36a-8338353b9542/universal-keywords/6ff8f7f6-1eb3-3525-be4a-3932c805afed"
);
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.
Update a universal keyword.
requires authentication
Example request:
const url = new URL(
"https://backend-staging.subsig.com/api/organisations/205ae76c-bd73-426b-a36a-8338353b9542/universal-keywords/6ff8f7f6-1eb3-3525-be4a-3932c805afed"
);
const headers = {
"Authorization": "Bearer 1|abc123...",
"Content-Type": "application/json",
"Accept": "application/json",
};
let body = {
"keyword": "b",
"context": "n",
"include_keywords_match": "all",
"include_keywords": [
"g"
],
"negative_terms": [
"z"
],
"platforms": [
"github"
],
"is_active": true
};
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 a universal keyword.
requires authentication
Example request:
const url = new URL(
"https://backend-staging.subsig.com/api/organisations/205ae76c-bd73-426b-a36a-8338353b9542/universal-keywords/6ff8f7f6-1eb3-3525-be4a-3932c805afed"
);
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.
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.