TypeScript SDK for working with HighLevel API v1 and v2 endpoints. Works with any server-side JS runtime including Node.js, Bun, Cloudflare Workers, etc.
The HighLevel API changes often. Each week, a GitHub Action checks the schemas in the official HighLevel API docs repository. It uses openapi-typescript to generate endpoint types.
When the schemas change, the action checks the package. If the checks pass, it publishes a new minor version to npm and creates a GitHub release. The action run summary and release notes list added, changed, and removed operations and schemas. New types are available after this release.
- Support for OAuth and Private Integrations
- Fully typed client SDK (using native
fetch) built with openapi-fetch - OAuth2 helpers for working with HighLevel's OAuth2 implementation
- Scopes builder for easily adding the appropriate scopes to your app
- Webhooks client with typed event handlers
- Support for both v1 (legacy API keys) and v2 (OAuth & Private Integrations)
- API v2 Documentation (OAuth & Private Integrations)
- API v1 Documentation (Legacy API Keys)
bun add @gnosticdev/highlevel-sdk
# or
pnpm add @gnosticdev/highlevel-sdk
# or
npm add @gnosticdev/highlevel-sdkThe HighLevel client uses the v2 API by default (see below for v1 client). It can be created with different configurations:
-
Basic Client: Pass an
openapi-fetchclient config as the first argument. Set a base URL and headers that the client will send by default.import { createHighLevelClient } from '@gnosticdev/highlevel-sdk' const client = createHighLevelClient({ baseUrl: 'https://services.leadconnectorhq.com', headers: { Authorization: `Bearer ${process.env.HIGHLEVEL_API_TOKEN!}`, Version: '2021-07-28', }, })
-
Client with OAuth: Requires OAuth configuration.
// The first argument is the openapi-fetch client config. const client = createHighLevelClient({ baseUrl: 'https://services.leadconnectorhq.com', }, 'oauth', { clientId: 'your-client-id', clientSecret: 'your-client-secret', redirectUri: 'http://localhost:3000/callback', accessType: 'Sub-Account', scopes: ['contacts.readonly'], })
-
Client with Private Integration: Requires private integration configuration.
const client = createHighLevelClient({ baseUrl: 'https://services.leadconnectorhq.com', }, 'integration', { privateToken: process.env.HIGHLEVEL_PRIVATE_TOKEN!, accessType: 'Agency', })
Use a Private Integration Token (PIT) for a custom integration that you run for one HighLevel agency or sub-account. An admin creates the token in HighLevel settings and selects its scopes there. Keep the token on your server. This SDK adds the Authorization and Version headers to each request. You do not need to add these headers to each call.
HighLevel's current docs show how to create a PIT in the account settings. They do not document an API that creates a PIT. The SDK cannot create one for each customer. HighLevel describes a PIT as a fixed token and recommends that you rotate it every 90 days. See Private Integrations.
Use OAuth when you build an app that users install and approve. HighLevel issues the tokens after the user approves the app. This works for both Private Apps and Public Apps. A Private App is an OAuth app type. It is not a Private Integration. OAuth is the right choice when each customer must approve access to their own account. Save each new refresh token after a token refresh. See OAuth 2.0.
HighLevel's docs do not say that a PIT is the preferred choice for every app. Choose the token type that fits how the app gets access.
The SDK uses openapi-fetch under the hood, which returns both data and error properties for type-safe error handling.
import { createHighLevelClient } from '@gnosticdev/highlevel-sdk'
// Create client with OAuth2 support
const client = createHighLevelClient({
baseUrl: 'https://services.leadconnectorhq.com',
}, 'oauth', {
clientId: process.env.HIGHLEVEL_CLIENT_ID!,
clientSecret: process.env.HIGHLEVEL_CLIENT_SECRET!,
redirectUri: 'http://localhost:3000/oauth/callback',
accessType: 'Sub-Account',
scopes: ['contacts.readonly'],
// Optional: store tokens in your database
storageFunction: async (tokenData) => {
await db.saveTokenResponse({
access_token: tokenData.access_token,
expiresAt: tokenData.expiresAt,
refresh_token: tokenData.refresh_token,
locationId: tokenData.locationId,
userId: tokenData.userId,
})
return tokenData
},
})
async function handleOAuthCallback(request: Request) {
const authCode = new URL(request.url).searchParams.get('code')
if (!authCode) {
throw new Error('HighLevel did not return an auth code')
}
// Exchange the code and store the token data.
const accessToken = await client.oauth.getAccessToken(authCode)
if (!accessToken) {
throw new Error('HighLevel did not return an access token')
}
// OAuth endpoint calls need the current access token and API version.
const { data, error } = await client.contacts.GET('/contacts/', {
params: {
header: {
Authorization: `Bearer ${accessToken}`,
Version: '2021-07-28',
},
query: {
locationId: '1234567890',
query: 'John Doe',
limit: 10,
},
},
})
if (error) {
console.error('Error fetching contacts:', error.message)
return
}
console.log(data.contacts)
}The OAuth client is available on the HighLevelClient instance when created with OAuth configuration:
// Generate authorization URL
const authUrl = client.oauth.getAuthorizationUrl()
// Exchange the auth code and store the token data
const accessToken = await client.oauth.getAccessToken(authCode)
// Later calls refresh the access token when needed
const currentAccessToken = await client.oauth.getAccessToken()Pass the access token in the Authorization header for each OAuth API call. The OAuth client stores tokens in memory by default. Use storageFunction to save tokens in a database. When you create a new client, load the saved token data from your database and pass it to client.oauth.storeTokenData() before you call getAccessToken(). Save the new refresh token after each refresh.
The v1 client requires an API key and automatically adds the authorization header to all requests.
import { createHighLevelV1Client } from '@gnosticdev/highlevel-sdk/v1'
const v1Client = createHighLevelV1Client({
apiKey: process.env.HIGHLEVEL_API_KEY!,
})
const { data, error } = await v1Client.GET('/v1/contacts', {
params: {
query: {
locationId: '1234567890',
},
// No need to add Authorization header - it's added automatically
},
})
if (error) {
console.error('Error fetching contacts:', error)
return
}
console.log(data.contacts)The webhooks client lets you register handlers with typed payloads:
import { createWebhooksClient } from '@gnosticdev/highlevel-sdk/webhooks'
const webhooks = createWebhooksClient()
webhooks.on('ContactCreate', async (payload) => {
// TypeScript knows the fields in this ContactCreate payload.
console.log('Contact created:', payload.firstName, payload.lastName)
})In your HTTP route, verify the signature and parse the request body. Check the event name, then call webhooks.handle('ContactCreate', payload) with that event's payload. The SDK checks that the payload is an object and that a handler exists. It does not verify signatures or validate every payload field.
HighLevel's current webhook guide uses the X-GHL-Signature header with Ed25519. The client exposes the current public key as webhooks.WEBHOOK_GHL_PUBLIC_KEY_PEM. The older webhooks.WEBHOOK_PUBLIC_KEY_PEM property is the legacy RSA key. See the webhook signature guide.
For a Node.js example of webhook signature verification and payload parsing, see the Webhook Signature Verification and Payload Parsing guide. Signature verification code is not included in the SDK so it can support runtimes with different crypto APIs.
If you just want to get types for the API endpoints, you can use them like this:
import type * as Locations from '@gnosticdev/highlevel-sdk/types/locations'
// Example: the response type for `GET /locations/{locationId}/customValues`
type LocationCustomValues = NonNullable<
Locations.operations['get-custom-values']['responses']['200']['content']['application/json']['customValues']
>
const customValues: LocationCustomValues = [{
fieldKey: 'contact.lead_source',
id: 'lead_source_id',
locationId: 'my_location_id',
name: 'Lead Source',
value: 'Google',
}]You can create a custom client using the createClient function from openapi-fetch with specific endpoint types from this package. This is useful when you only need a subset of the API endpoints or want to create a more focused client.
import { createClient } from '@gnosticdev/highlevel-sdk'
import type * as CustomMenus from '@gnosticdev/highlevel-sdk/types/custom-menus'
// Create a client with only custom menu endpoints
type CustomMenuPaths = CustomMenus.paths
const client = createClient<CustomMenuPaths>({
headers: {
Authorization: `Bearer ${process.env.HIGHLEVEL_API_TOKEN!}`,
Version: '2021-07-28',
},
})
// Use the client with full type safety
const { data, error } = await client.GET('/custom-menus/', {
params: {
header: {
Version: '2021-07-28',
},
query: {
locationId: '1234567890',
},
},
})
if (error) {
console.error('Error:', error)
return
}
console.log(data)You can also combine multiple endpoint types to create a client with a custom set of endpoints:
import { createClient } from '@gnosticdev/highlevel-sdk'
import type * as CustomMenus from '@gnosticdev/highlevel-sdk/types/custom-menus'
import type * as Contacts from '@gnosticdev/highlevel-sdk/types/contacts'
// Combine multiple endpoint types
type CustomMenuPaths = CustomMenus.paths & Contacts.paths
const client = createClient<CustomMenuPaths>({
headers: {
Authorization: `Bearer ${process.env.HIGHLEVEL_API_TOKEN!}`,
Version: '2021-07-28',
},
})Note: The createClient function is exported from this package and is the same openapi-fetch client. Headers passed in the config are request defaults. An operation can still require a header in params.header for TypeScript, as shown above. You can override default headers there.
The SDK provides several module exports for better organization and tree-shaking:
// Main client
import { createHighLevelClient } from '@gnosticdev/highlevel-sdk'
// OAuth client implementation
import { OauthClientImpl } from '@gnosticdev/highlevel-sdk/oauth'
// Scopes builder
import { ScopesBuilder } from '@gnosticdev/highlevel-sdk/scopes'
// V1 API client
import { createHighLevelV1Client } from '@gnosticdev/highlevel-sdk/v1'
// Webhooks client
import { createWebhooksClient } from '@gnosticdev/highlevel-sdk/webhooks'
// Types
import type * as Locations from '@gnosticdev/highlevel-sdk/types/locations'For OAuth, add the scopes to your app in the HighLevel Marketplace. Use ScopesBuilder to collect scopes that are valid for an agency or sub-account app:
import { createHighLevelClient } from '@gnosticdev/highlevel-sdk'
import { ScopesBuilder } from '@gnosticdev/highlevel-sdk/scopes'
const scopeBuilder = new ScopesBuilder('Sub-Account')
scopeBuilder.add(['contacts.readonly', 'locations.readonly'])
const client = createHighLevelClient({
baseUrl: 'https://services.leadconnectorhq.com',
}, 'oauth', {
clientId: process.env.HIGHLEVEL_CLIENT_ID!,
clientSecret: process.env.HIGHLEVEL_CLIENT_SECRET!,
redirectUri: 'http://localhost:3000/callback',
accessType: 'Sub-Account',
scopes: [...scopeBuilder.collection],
})TypeScript checks scope names against the selected access type. The builder does not add scopes to your HighLevel app or grant access. HighLevel sets Private Integration Token scopes when an admin creates the token.
Check out our example projects in the examples directory:
examples/bun-auth: Example of OAuth2 authentication flow using Bun and Hono- More examples coming soon!
Contributions are welcome! Please feel free to submit a Pull Request.
This project is licensed under the MIT License - see the LICENSE file for details.