Skip to content

Instantly share code, notes, and snippets.

@mia-z
Last active October 7, 2025 20:52
Show Gist options
  • Select an option

  • Save mia-z/80b75564de359bd482f22bed269ccad2 to your computer and use it in GitHub Desktop.

Select an option

Save mia-z/80b75564de359bd482f22bed269ccad2 to your computer and use it in GitHub Desktop.
new api transition help
import axios, { type AxiosInstance } from "axios";
import type {
AddUserToTenantResponse,
GetAllDomainPagesResponse,
GetAllPageScansResponse,
GetAllUsersForTenantResponse,
GetDomainPageByIdResponse,
GetLatestScanForDomainPageResponse,
GetPageScanByIdResponse,
GetTenantSubscriptionResponse,
RemoveUserFromTenantResponse,
ScanDomainPageByIdResponse,
TenantNotificationTokenResponse,
UpdateTenantSubscriptionResponse,
UpdateUserForTenantResponse,
UserNotificationTokenResponse,
CreateDomainRequest,
CreateDomainResponse,
DeleteDomainResponse,
GetAllDomainsResponse,
GetDomainByIdResponse,
IntrospectDomainResponse,
UpdateDomainRequest,
UpdateDomainResponse,
UsersMeResponse,
AddUserToTenantRequest,
UpdateUserForTenantRequest,
UpdateTenantSubscriptionRequest
} from "./apiClient.types";
class SallyApiClient {
private _tenantIdentifier: string | undefined;
private _authToken: string | undefined;
private _client: AxiosInstance;
public constructor() {
this._client = axios.create();
this._client.interceptors.request.use((req) => {
req.headers.set("X-Sally-Tenant", this._tenantIdentifier);
req.headers.set("Authorization", `Bearer ${this._authToken}`);
req.headers.set("Content-Type", "application/json")
return req;
});
}
public setBaseUrl(baseUrl: string) {
this._client.defaults.baseURL = baseUrl;
}
public setAuthToken(authToken: string) {
this._authToken = authToken;
}
public setTenantIdentifier(tenantIdentifier: string) {
this._tenantIdentifier = tenantIdentifier;
}
// --- Auth Endpoints ---
// GET /auth/notifications/tenant
public getTenantNotificationToken() {
return this._client.get<TenantNotificationTokenResponse>("/auth/notifications/tenant");
}
// GET /auth/notifications/user
public getUserNotificationToken() {
return this._client.get<UserNotificationTokenResponse>("/auth/notifications/user");
}
// --- Domains Endpoints ---
// GET /api/domains
public getAllDomains() {
return this._client.get<GetAllDomainsResponse>("/api/domains");
}
// GET /api/domains/:domain_id
public getDomainById(domainId: number) {
return this._client.get<GetDomainByIdResponse>(`/api/domains/${domainId}`);
}
// POST /api/domains
public createDomain(data: CreateDomainRequest) {
return this._client.post<CreateDomainResponse>("/api/domains", data);
}
// PATCH /api/domains/:domain_id
public updateDomain(domainId: number, data: Partial<UpdateDomainRequest>) {
return this._client.patch<UpdateDomainResponse>(`/api/domains/${domainId}`, data);
}
// DELETE /api/domains/:domain_id
public deleteDomain(domainId: number) {
return this._client.delete<DeleteDomainResponse>(`/api/domains/${domainId}`);
}
// POST /api/domains/:domain_id/introspect
public introspectDomain(domainId: number) {
return this._client.post<IntrospectDomainResponse>(`/api/domains/${domainId}/introspect`);
}
// --- Domain Pages Endpoints ---
// GET /api/domains/:domain_id/pages
public getAllDomainPages(domainId: number) {
return this._client.get<GetAllDomainPagesResponse>(`/api/domains/${domainId}/pages`);
}
// GET /api/domains/:domain_id/pages/:domain_page_id
public getDomainPageById(domainId: number, domainPageId: number) {
return this._client.get<GetDomainPageByIdResponse>(`/api/domains/${domainId}/pages/${domainPageId}`);
}
// POST /api/domains/:domain_id/pages/:domain_page_id/scan
public scanDomainPageById(domainId: number, domainPageId: number) {
return this._client.post<ScanDomainPageByIdResponse>(`/api/domains/${domainId}/pages/${domainPageId}/scan`);
}
// DELETE /api/domains/:domain_id/pages/:domain_page_id
public deleteDomainPage(domainId: number, domainPageId: number) {
return this._client.delete<DeleteDomainResponse>(`/api/domains/${domainId}/pages/${domainPageId}`);
}
// GET /api/domains/:domain_id/pages/:domain_page_id/latest-scan
public getLatestScanForPage(domainId: number, domainPageId: number) {
return this._client.get<GetLatestScanForDomainPageResponse>(`/api/domains/${domainId}/pages/${domainPageId}/latest-scan`);
}
// --- Page Scans Endpoints ---
// GET /api/domains/:domain_id/pages/:domain_page_id/page-scans
public getAllPageScans(domainId: number, domainPageId: number) {
return this._client.get<GetAllPageScansResponse>(`/api/domains/${domainId}/pages/${domainPageId}/page-scans`);
}
// GET /api/domains/:domain_id/pages/:domain_page_id/page-scans/:page_scan_id
public getPageScanById(domainId: number, domainPageId: number, pageScanId: number) {
return this._client.get<GetPageScanByIdResponse>(`/api/domains/${domainId}/pages/${domainPageId}/page-scans/${pageScanId}`);
}
// --- Users Endpoints ---
// GET /api/users/me
public getMe() {
return this._client.get<UsersMeResponse>("/api/users/me");
}
// --- Tenants Endpoints ---
// GET /api/tenants/users
public getTenantUsers() {
return this._client.get<GetAllUsersForTenantResponse>("/api/tenants/users");
}
// POST /api/tenants/users
public addTenantUser(data: AddUserToTenantRequest) {
return this._client.post<AddUserToTenantResponse>("/api/tenants/users", data);
}
// PATCH /api/tenants/users/:user_identifier
public updateTenantUser(userIdentifier: string, data: UpdateUserForTenantRequest) {
return this._client.patch<UpdateUserForTenantResponse>(`/api/tenants/users/${userIdentifier}`, data);
}
// DELETE /api/tenants/users/:user_identifier
public removeTenantUser(userIdentifier: string) {
return this._client.delete<RemoveUserFromTenantResponse>(`/api/tenants/users/${userIdentifier}`);
}
// GET /api/tenants/subscription
public getTenantSubscription() {
return this._client.get<GetTenantSubscriptionResponse>("/api/tenants/subscription");
}
// PATCH /api/tenants/subscription
public updateTenantSubscription(data: UpdateTenantSubscriptionRequest) {
return this._client.patch<UpdateTenantSubscriptionResponse>("/api/tenants/subscription", data);
}
}
const globalClient = new SallyApiClient();
export {
globalClient as default,
SallyApiClient
};
export type UsersMeResponse = {
identifier: string,
name: string,
pictureUrl: string,
tenants: {
role: string,
tenantIdentifier: string,
name: string
}[]
}
export type TenantNotificationTokenResponse = {
token: string
}
export type UserNotificationTokenResponse = {
token: string
}
export type GetAllDomainsResponse = {
id: number,
name: string,
url: string,
introspectionOptions: {
mode: "bredth",
maxPages: number,
maxDepth: number
} | {
mode: "sitemap",
sitemapEndpoint: string,
maxPages: number
},
consentBoxOptions: {
mode: "none",
} | {
mode: "auto"
} | {
mode: "selector",
selector: string,
isIframe: false,
iframeHost: never
} | {
mode: "selector",
selector: string,
isIframe: true,
iframeHost: string
},
introspectionStatus: string,
introspectionStatusError: string,
tenantIdentifier: string
}[]
export type GetDomainByIdResponse = {
id: number,
name: string,
url: string,
introspectionOptions: {
mode: "bredth",
maxPages: number,
maxDepth: number
} | {
mode: "sitemap",
sitemapEndpoint: string,
maxPages: number
},
introspectionStatus: string,
introspectionStatusError: string,
tenantIdentifier: string,
pages: {
id: number,
lastScanned: string,
path: string
}[]
}
export type CreateDomainRequest = {
newDomainUrl: string,
newDomainName: string,
introspectionOptions: {
mode: "bredth",
maxPages: number,
maxDepth: number
} | {
mode: "sitemap",
sitemapEndpoint: string,
maxPages: number
},
consentBoxOptions: {
mode: "none",
} | {
mode: "auto"
} | {
mode: "selector",
selector: string,
isIframe: false,
iframeHost: never
} | {
mode: "selector",
selector: string,
isIframe: true,
iframeHost: string
}
}
export type CreateDomainResponse = {
createdDomainId: number,
createdDomainUrl: string,
createdDomainName: string,
createdDomainIntrospectionOptions: {
mode: "bredth",
maxPages: number,
maxDepth: number
} | {
mode: "sitemap",
sitemapEndpoint: string,
maxPages: number
},
createdDomainConsentBoxOptions: {
mode: "none",
} | {
mode: "auto"
} | {
mode: "selector",
selector: string,
isIframe: false,
iframeHost: never
} | {
mode: "selector",
selector: string,
isIframe: true,
iframeHost: string
}
createdDomainTenantIdentifier: string
}
export type UpdateDomainRequest = {
updatedDomainName: string,
updatedDomainIntrospectionOptions: Partial<{
mode: "bredth",
maxPages: number,
maxDepth: number
} | {
mode: "sitemap",
sitemapEndpoint: string,
maxPages: number
}>,
updatedDomainConsentBoxOptions: Partial<{
mode: "none",
} | {
mode: "auto"
} | {
mode: "selector",
selector: string,
isIframe: false,
iframeHost: never
} | {
mode: "selector",
selector: string,
isIframe: true,
iframeHost: string
}>
}
export type UpdateDomainResponse = {
id: number,
name: string,
introspectionOptions: {
mode: "bredth",
maxPages: number,
maxDepth: number
} | {
mode: "sitemap",
sitemapEndpoint: string,
maxPages: number
}
}
export type DeleteDomainResponse = {
deletedDomainId: number
}
export type IntrospectDomainResponse = {
domainId: number
}
export type GetAllDomainPagesResponse = {
id: number,
path: string,
lastScanned: string
}[]
export type GetDomainPageByIdResponse = {
id: number,
path: string,
lastScanned: string,
scans: {
id: number,
status: string,
statusDescription: string,
updatedAt: string,
createdAt: string
}[]
}
export type ScanDomainPageByIdResponse = {
domainPageId: number,
pageScanId: number,
conflictReason: string | undefined
}
export type DeleteDomainPageByIdResponse = {
deletedDomainPageId: number
}
export type GetLatestScanForDomainPageResponse = {
id: number,
status: string,
statusDescription: string,
updatedAt: string,
createdAt: string,
}[]
export type GetAllPageScansResponse = {
id: number,
status: string,
statusDescription: string,
updatedAt: string,
createdAt: string,
issues: {
id: string,
title: string,
ruleDescription: string,
level: string,
impact: string,
principle: string,
guideline: string,
criterion: string,
affectedHtml: string,
selector: string,
message: string
}[]
}
export type GetPageScanByIdResponse = {
id: number,
status: string,
statusDescription: string,
updatedAt: string,
createdAt: string,
issues: {
id: string,
title: string,
ruleDescription: string,
level: string,
impact: string,
principle: string,
guideline: string,
criterion: string,
affectedHtml: string,
selector: string,
message: string
}[]
}
export type GetAllUsersForTenantResponse = {
user: string,
role: string,
identifier: string
}[]
export type AddUserToTenantRequest = {
userIdentifier: string,
tenantIdentifier: string,
role: "user" | "editor" | "admin" | "external_user" | "external_editor"
}
export type AddUserToTenantResponse = {
userIdentifier: string,
tenantIdentifier: string,
role: "user" | "editor" | "admin" | "external_user" | "external_editor"
}
export type UpdateUserForTenantRequest = {
updatedRole: "user" | "editor" | "admin" | "external_user" | "external_editor"
}
export type UpdateUserForTenantResponse = {
userIdentifier: string,
tenantIdentifier: string,
role: "user" | "editor" | "admin" | "external_user" | "external_editor"
}
export type RemoveUserFromTenantResponse = {
removedUserIdentifier: string,
tenantIdentifier: string
}
export type GetTenantSubscriptionResponse = {
level: string,
expiresAt: string
}
export type UpdateTenantSubscriptionRequest = {
updatedLevel: "enterprise" | "free" | "premium"
}
export type UpdateTenantSubscriptionResponse = {
level: string,
expiresAt: string
}

Usage

Import via import client from "apiClient" to use a global instance

  • set base url with .setBaseUrl(<base_url>)
  • set tenant identifier with .setTenantIdentifier(<tenant_identifier>)
  • set user auth with .setAuthToken(<bearer_token>)

if you need to create separate instances, import the client class and make new isntances, ie

import { SallyApiClient } from "./apiClient";

const myClient = new SallyApiClient();
myClient.setBaseUrl(<base_url>)
myClient.setTenantIdentifier(<tenant_identifier>)
myClient.setAuthToken(<bearer_token>)

Functions

getMe()

Returns the current logged in user, according to the sent bearer token


getAllDomains()

Needs X-Sally-Tenant header, set via setTenantIdentifier(<tenant_identifier>)

Returns all the domains associated with the tenant set in the X-Sally-Tenant header


getDomainById(<domain_id>)

Needs X-Sally-Tenant header, set via setTenantIdentifier(<tenant_identifier>)

Returns the domain with the given id


createDomain(<json_body>)

json body

{
   newDomainUrl: string, 
   newDomainName: string, 
   
   one of:
   introspectionOptions: {
     mode: "sitemap",
     maxPages: number,
     sitemapEndpoint: string //RELATIVE URL
   }

   or,

   introspectionOptions: {
     mode: "bredth",
     maxPages: number,
     maxDepth: number
   }
}

Needs X-Sally-Tenant header, set via setTenantIdentifier(<tenant_identifier>)

Creates a new domain associated to the tenant, along with introspection settings


updateDomain(<domain_id>, <json_body>)

json body

{
   updatedName: "Update value", 
   
   one of:
   updatedIntrospectionOptions: {
     mode: "sitemap",
     maxPages: number,
     sitemapEndpoint: string //RELATIVE URL
   }

   or,

   updatedIntrospectionOptions: {
     mode: "bredth",
     maxPages: number,
     maxDepth: number
   }
}

Needs X-Sally-Tenant header, set via setTenantIdentifier(<tenant_identifier>)

Updates a domain with the provided changes


deleteDomain(<domain_id>)

Needs X-Sally-Tenant header, set via setTenantIdentifier(<tenant_identifier>)

Deletes a domain with associated tenant


introspectDomain(<domain_id>)

Needs X-Sally-Tenant header, set via setTenantIdentifier(<tenant_identifier>)

Starts introspection process for a domain


getAllDomainPages(<domain_id>)

Needs X-Sally-Tenant header, set via setTenantIdentifier(<tenant_identifier>)

Gets all domain pages associated with the given domain


getDomainPageById(<domain_id>, <domain_page_id>)

Needs X-Sally-Tenant header, set via setTenantIdentifier(<tenant_identifier>)

Gets domain page by the given domain id and domain page id


scanDomainPageById(<domain_id>, <domain_page_id>)

Needs X-Sally-Tenant header, set via setTenantIdentifier(<tenant_identifier>)

Starts a scan for the given domain id and domain page id


getLatestScanForPage(<domain_id>, <domain_page_id>)

Needs X-Sally-Tenant header, set via setTenantIdentifier(<tenant_identifier>)

Gets the latest scan for the given domain id and domain page id - WILL RETURN FAILED SCANS IF ITS THE MOST RECENT

This will also fetch the issues associated with the scan


getAllPageScans(<domain_id>, <domain_page_id>)

Needs X-Sally-Tenant header, set via setTenantIdentifier(<tenant_identifier>)

Gets all of the page scans for a given domain id and domain page id

doesnt include all the related scan issues


getPageScanById(<domain_id>, <domain_page_id>, <page_scan_id>)

Needs X-Sally-Tenant header, set via setTenantIdentifier(<tenant_identifier>)

Gets page scan for a given domain id, domain page id and page scan id

This will also fetch the issues associated with the scan


getTenantUsers()

Needs X-Sally-Tenant header, set via setTenantIdentifier(<tenant_identifier>)

Gets all the users for the currently associated tenant, and their roles


addTenantUser(<json_body>)

json body

{
   userIdentifier: string, 
   role: "admin" | "editor" | "user" | "external_editor" | "external_user", 
   tenantIdentifier: string
}

Needs X-Sally-Tenant header, set via setTenantIdentifier(<tenant_identifier>)

Adds a new user to the given tenant, by their user_identifier with the given role. More ways to be added !


updateTenantUser(<user_identifier>, <json_body>)

json body

{
   updatedRole: "admin" | "editor" | "user" | "external_editor" | "external_user"
}

Needs X-Sally-Tenant header, set via setTenantIdentifier(<tenant_identifier>)

Updates the given user in the current tenant with the provided role


removeTenantUser(<user_identifier>)

Needs X-Sally-Tenant header, set via setTenantIdentifier(<tenant_identifier>)

Removes a user with the provided user identifier from the current tenant


getTenantSubscription()

Needs X-Sally-Tenant header, set via setTenantIdentifier(<tenant_identifier>)

Get the subscription information for the current tenant

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment