Base URL
https://api.sbl.so
/auth/tokenAuthenticates a user with email/password and returns a bearer token valid for 30 days. If a 401 Unauthorized is returned on any other endpoint, regenerate the token and retry.
Request Body Fields
| Name | Type | Required | Description |
|---|---|---|---|
| string | Yes | Your account email address | |
| password | string | Yes | Your account password |
Request Body Example
{
"email": "user@example.com",
"password": "your_password"
}Response (200 OK)
{
"token": "eyJhbGciOiJIUzI1NiIsInR5..."
}Error Responses
| Status | Description |
|---|---|
| 400 | Missing or invalid parameters |
| 401 | Invalid email or password |
| 429 | Too many requests — check Retry-After header and retry |
/campaignsReturns a list of campaigns for a company.
Authentication
Authorization: Bearer <token>
Query Parameters
| Name | Type | Required | Description |
|---|---|---|---|
| companyId | string | Yes | The ID of the company |
| statuses | string | string[] | No | Filter by campaign status(es) |
| types | string | string[] | No | Filter by campaign type(s) |
| isArchived | boolean | No | Filter archived campaigns (default: false) |
| searchQuery | string | No | Search campaigns by name |
| lastCampaignId | string | No | Pagination cursor — ID of the last campaign from previous page |
Response (200 OK)
[
{
"id": "123",
"name": "My Campaign",
"status": "running",
"type": "outbound",
"communicationChannelToUse": 1,
"statistics": {},
"createdAt": "2024-01-01T00:00:00.000Z"
}
]Error Responses
| Status | Description |
|---|---|
| 401 | Token expired or invalid |
| 403 | Company does not belong to the logged-in user |
| 429 | Too many requests — check Retry-After header and retry |
/campaigns/:campaignIdReturns full details for a single campaign.
Authentication
Authorization: Bearer <token>
Path Parameters
| Name | Type | Required | Description |
|---|---|---|---|
| campaignId | string | Yes | The ID of the campaign |
Query Parameters
| Name | Type | Required | Description |
|---|---|---|---|
| companyId | string | Yes | The ID of the company |
Response (200 OK)
{
"id": "123",
"name": "My Campaign",
"status": "running",
"type": "outbound",
"objective": "...",
"initialMessage": "Hello!",
"communicationChannelToUse": 1,
"statistics": {},
"analytics": {},
"batches": [],
"createdAt": "2024-01-01T00:00:00.000Z"
}Error Responses
| Status | Description |
|---|---|
| 401 | Token expired or invalid |
| 403 | Company does not belong to the logged-in user |
| 429 | Too many requests — check Retry-After header and retry |
/campaigns/:campaignIdUpdates a campaign. Send only the fields you want to change — everything else is left untouched. Unknown fields are rejected. Concurrency is handled for you: the API reads the campaign's current revision before applying the update.
Authentication
Authorization: Bearer <token>
Path Parameters
| Name | Type | Required | Description |
|---|---|---|---|
| campaignId | string | Yes | The ID of the campaign |
Request Body Fields
| Name | Type | Required | Description |
|---|---|---|---|
| companyId | string | Yes | The ID of the company |
| name | string | No | Campaign name |
| objective | string | No | Campaign objective |
| targetUsers | object | No | { title, description } — the ICP |
| communicationChannelToUse | number | No | Required when updating initialMessage or followupMessages |
| initialMessage | string | No | First-touch message, validated per channel |
| initialMessageDelayInMs | number | No | Delay before the first message (max 90 days) |
| followupMessages | array | No | [{ id, message, followupAfterInMs, asVoiceNote? }] |
| smartFollowupsConfig | object | No | { delayInMs[], followupMessageReasoning[] } — equal lengths, min 5 minutes each |
| nonConnectedLeadsMessage | string | No | Message for leads who are not connections yet |
| sendInviteWithoutMessage | boolean | No | Send LinkedIn invites without a note |
| productLinks | array | No | [{ link, description }] |
| icpFilter | object | No | { criteria, requirements, networkDistances }. criteria and networkDistances are each optional but at least one is required; requirements is required with criteria. networkDistances: FIRST_DEGREE | SECOND_DEGREE | THIRD_DEGREE | OUT_OF_NETWORK |
| userTagsToRunOn | array | No | Tag IDs to target |
| leadListSetsToRunOn | array | No | Lead list set IDs to target |
| campaignsToRunOn | array | No | Retargeting source campaigns |
| commentToDM | array | No | [{ postUrl, trigger }] — LinkedIn post URLs, must be unique |
| likeToDM | array | No | [{ postUrl }] — LinkedIn post URLs, must be unique |
| pausedItem | number | No | Pause a specific part of the campaign |
| isArchived | boolean | No | Archive or unarchive the campaign |
| webhooksEnabled | boolean | No | Toggle webhooks for this campaign |
| customAnalyticsFields | array | No | [{ name, description }] — unique, JSON-key-safe names |
Request Body Example
{
"companyId": "456",
"name": "Q3 Founders Outreach",
"objective": "Book demos with seed-stage founders",
"communicationChannelToUse": 1,
"initialMessage": "Hi {{name}}, saw you're building in devtools..."
}Response (200 OK)
{
"updated": true,
"status": 200,
"payload": {}
}Error Responses
| Status | Description |
|---|---|
| 400 | Invalid or unknown fields in the body |
| 401 | Token expired or invalid |
| 403 | Company does not belong to the logged-in user |
| 404 | Campaign not found |
| 409 | Campaign changed while the update was being applied |
| 429 | Too many requests — check Retry-After header and retry |
/campaigns/:campaignId/usersReturns a paginated list of users in a campaign.
Authentication
Authorization: Bearer <token>
Path Parameters
| Name | Type | Required | Description |
|---|---|---|---|
| campaignId | string | Yes | The ID of the campaign |
Query Parameters
| Name | Type | Required | Description |
|---|---|---|---|
| companyId | string | Yes | The ID of the company |
| userChatStatus | number | No | Filter by user chat status |
| searchQuery | string | No | Search users by name |
| lastUserId | string | No | Pagination cursor — ID of the last user from previous page |
Response (200 OK)
[
{
"id": "456",
"name": "John Doe",
"phoneNumber": "+1234567890",
"email": null,
"linkedinChannelDetails": null,
"tags": [],
"aiChatOff": false,
"status": 1,
"lastMessageCreatedAt": "2024-01-01T00:00:00.000Z",
"createdAt": "2024-01-01T00:00:00.000Z"
}
]Error Responses
| Status | Description |
|---|---|
| 401 | Token expired or invalid |
| 403 | Company does not belong to the logged-in user |
| 429 | Too many requests — check Retry-After header and retry |
/campaigns/:campaignId/users/:userId/messagesReturns messages in a user's conversation within a campaign, ordered oldest first.
Authentication
Authorization: Bearer <token>
Path Parameters
| Name | Type | Required | Description |
|---|---|---|---|
| campaignId | string | Yes | The ID of the campaign |
| userId | string | Yes | The ID of the company user |
Query Parameters
| Name | Type | Required | Description |
|---|---|---|---|
| companyId | string | Yes | The ID of the company |
| lastMessageId | string | No | Pagination cursor — ID of the last message from previous page |
Response (200 OK)
[
{
"id": "789",
"senderType": "bot",
"content": "Hello, welcome!",
"type": "text",
"status": 2,
"communicationChannelUsed": 1,
"trigger": "initial",
"scheduledFor": null,
"createdAt": "2024-01-01T00:00:00.000Z"
}
]Error Responses
| Status | Description |
|---|---|
| 401 | Token expired or invalid |
| 403 | Company does not belong to the logged-in user |
| 429 | Too many requests — check Retry-After header and retry |
/campaigns/:campaignId/human-interventionReturns users in the campaign that require human intervention.
Authentication
Authorization: Bearer <token>
Path Parameters
| Name | Type | Required | Description |
|---|---|---|---|
| campaignId | string | Yes | The ID of the campaign |
Query Parameters
| Name | Type | Required | Description |
|---|---|---|---|
| companyId | string | Yes | The ID of the company |
| lastUserId | string | No | Pagination cursor — ID of the last user from previous page |
Response (200 OK)
[
{
"id": "456",
"name": "John Doe",
"phoneNumber": "+1234567890",
"email": null,
"linkedinChannelDetails": null,
"tags": [],
"aiChatOff": false,
"status": 1,
"lastMessageCreatedAt": "2024-01-01T00:00:00.000Z",
"createdAt": "2024-01-01T00:00:00.000Z"
}
]Error Responses
| Status | Description |
|---|---|
| 401 | Token expired or invalid |
| 403 | Company does not belong to the logged-in user |
| 429 | Too many requests — check Retry-After header and retry |
/campaigns/generateAI-generates a new campaign based on a natural language description.
Authentication
Authorization: Bearer <token>
Request Body Fields
| Name | Type | Required | Description |
|---|---|---|---|
| companyId | string | Yes | The ID of the company |
| description | string | Yes | Natural language description of the campaign goal |
| communicationChannel | number | Yes | 1 = WhatsApp, 3 = LinkedIn |
Request Body Example
{
"companyId": "123",
"description": "Reach out to CTOs at fintech companies about our new product",
"communicationChannel": 1
}Response (200 OK)
{
"id": "456",
"pendingCommentToDM": false,
"pendingMetaAdId": false,
"pendingProductLinks": []
}Error Responses
| Status | Description |
|---|---|
| 400 | Missing or invalid parameters |
| 401 | Token expired or invalid |
| 403 | Company does not belong to the logged-in user |
| 429 | Too many requests — check Retry-After header and retry |
/campaigns/:campaignId/send-messageSends a message to a specific user in a campaign.
Authentication
Authorization: Bearer <token>
Path Parameters
| Name | Type | Required | Description |
|---|---|---|---|
| campaignId | string | Yes | The ID of the campaign |
Request Body Fields
| Name | Type | Required | Description |
|---|---|---|---|
| userId | string | Yes | The ID of the company user |
| message | string | Yes | The message text to send |
Request Body Example
{
"userId": "789",
"message": "Hello, following up on our conversation!"
}Response (200 OK)
{
"messageId": "xxxx"
}Error Responses
| Status | Description |
|---|---|
| 400 | User not in campaign, campaign not running, channel not connected, or user in invalid chat state |
| 401 | Token expired or invalid — regenerate using /auth/token |
| 403 | Campaign does not belong to the logged-in user |
| 404 | Campaign not found |
| 429 | Too many requests — check Retry-After header and retry |
/campaigns/:campaignId/add-userAdds a new user to a campaign. WhatsApp campaigns require phoneNumber; LinkedIn campaigns require linkedinProfileUrl. LinkedIn campaigns can also include customMessage for templates that use [custom message].
Authentication
Authorization: Bearer <token>
Path Parameters
| Name | Type | Required | Description |
|---|---|---|---|
| campaignId | string | Yes | The ID of the campaign |
Request Body Fields
| Name | Type | Required | Description |
|---|---|---|---|
| name | string | Yes | Full name of the user |
| phoneNumber | string | No | Required for WhatsApp campaigns |
| linkedinProfileUrl | string | No | Required for LinkedIn campaigns |
| customVariables | object | No | Key-value pairs of custom data to attach to the user. Keys and values must be non-empty strings. |
| customMessage | string | No | LinkedIn-only custom text used to replace [custom message] in campaign messages. |
Request Body Example
{
"name": "John Doe",
"linkedinProfileUrl": "https://www.linkedin.com/in/johndoe",
"customVariables": {
"company": "Acme Inc",
"role": "CTO"
},
"customMessage": "I noticed Acme recently expanded its engineering team."
}Response (200 OK)
This endpoint returns an empty response body (HTTP 200).
Error Responses
| Status | Description |
|---|---|
| 400 | Campaign not in a valid state, contact field does not match campaign channel, or customMessage is used outside a LinkedIn campaign |
| 401 | Token expired or invalid — regenerate using /auth/token |
| 403 | Campaign does not belong to the logged-in user |
| 404 | Campaign not found |
| 409 | User is already part of this campaign |
| 429 | Too many requests — check Retry-After header and retry |