Python SDK for the SmartLink workflow API and webhook verification.
Project description
SmartLink SDK
Table of Contents
- Introduction
- Examples
- Testing
- SDK Functions
- getAppByClientId
- postApp
- putAppByClientId
- deleteAppByClientId
- getApps
- postApps
- getEvents
- getFolderById
- getFolderByIdApps
- getFolderByIdMemberships
- deleteFolderById
- postFolder
- getFolders
- postImportApps
- postImportMemberships
- postMembershipByIdDeactivate
- postMembershipByIdRegister
- getMembershipById
- putMembershipById
- deleteMembershipById
- postMembership
- getMemberships
- postMemberships
- getMembershipsSearch
- getOrganization
- Schemas
- Webhooks
Introduction
The SmartLink SDK allows you to make requests to the SmartLink API. To use the SDK, you need to obtain an API key from your SmartLink account via the Workflow administrator menu.
Examples
Example 1: Init client locally
from smartlink_sdk import SmartLinkClient, getApps
def fetch():
client = SmartLinkClient(
base_url="https://<your_smartlink_url>/api/workflow",
api_key="<your_secret_api_key>",
)
apps = getApps({"client": client})
print("apps", apps.data)
fetch()
Example 2: Configure client globally
from smartlink_sdk import client, getApps
client.set_config(
base_url="https://<your_smartlink_url>/api/workflow",
headers={
"x-api-key": "<your_secret_api_key>",
},
)
def fetch():
apps = getApps()
print("apps", apps.data)
fetch()
Testing
The SDK ships with end-to-end Jest tests that expect a local SmartLink API.
Create ts/.env with:
API_BASE_URL=http://localhost:3003/api/workflow
API_KEY=<your api key>
Then run:
npm run test
SDK Functions
getAppByClientId
Fetches an app by its unique ID.
getAppByClientId({
"path": {
"clientId": "app_id",
},
})
Parameters
| Parameters | type | required | default |
|---|---|---|---|
| clientId | string | true |
Response
200: { data: The App }
401: { error: "Not authorized" }
404: { error: "App not found" }
500: { error: "Internal server error" }
postApp
Creates a new app for your organization
postApp({
"body": {
"title": "App title",
"url": "https://app.com",
"iconUrl": "https://app.com/icon.png",
"description": "App description",
"slug": "my_unique_slug",
},
})
Body
| Parameters | type | required | default |
|---|---|---|---|
| title | string | true | |
| url | string | false | null |
| iconUrl | string | false | null (icons are automatically retrieved by smartlink) |
| description | string | false | null |
| slug | string | false | auto generated (randomUUID) |
Response
200: { data: The added App }
401: { error: "Not authorized" }
500: { error: "Internal server error" }
putAppByClientId
Updates an app by its client ID.
putAppByClientId({
"path": {
"clientId": "your_client_id",
},
"body": {
"title": "Updated title",
"url": "https://updated-url.com",
"iconUrl": "https://updated-url.com/icon.png",
"description": "Updated description",
"slug": "updated_slug",
},
})
Parameters
| Parameters | type | required | default |
|---|---|---|---|
| clientId | string | true |
Body
| Parameters | type | required | default |
|---|---|---|---|
| title | string | true | |
| url | string | false | null |
| iconUrl | string | false | null |
| description | string | false | null |
| slug | string | false | null |
Response
200: { data: The updated App }
401: { error: "Not authorized" }
404: { error: "App not found" }
500: { error: "Internal server error" }
deleteAppByClientId
Deletes an app by its client ID.
deleteAppByClientId({
"path": {
"clientId": "your_client_id",
},
})
Parameters
| Parameters | type | required | default |
|---|---|---|---|
| clientId | string | true |
Response
200: { data: The deleted App }
401: { error: "Not authorized" }
404: { error: "App not found" }
500: { error: "Internal server error" }
getApps
Get all apps of your organization.
apps = getApps()
Response
200: { data: Array of created Apps }
401: { error: "Not authorized" }
500: { error: "Internal server error" }
postApps
Creates multiple apps for your organization.
Note: this endpoint is defined by the SDK contract, but the current backend returns an Internal server error in this environment. For now, prefer calling postApp multiple times when you need to create several apps.
postApps({
"body": {
"apps": [
{
"title": "App title 1",
"url": "https://app1.com",
"iconUrl": "https://app1.com/icon.png",
"description": "App description 1",
"slug": "unique_slug_1",
},
{
"title": "App title 2",
"url": "https://app2.com",
"iconUrl": "https://app2.com/icon.png",
"description": "App description 2",
"slug": "unique_slug_2",
},
],
},
})
Body
Array of:
| Parameters | type | required | default |
|---|---|---|---|
| title | string | true | |
| url | string | false | null |
| iconUrl | string | false | null |
| description | string | false | null |
| slug | string | false | null |
Response
200: { data: Array of created Apps }
401: { error: "Not authorized" }
500: { error: "Internal server error" }
getEvents
Retrieve log events with various filters.
events = getEvents({
"query": {
"fromDate": 1733995830,
"toDate": 1733995840,
"type": 0,
"membershipId": 1,
"folderId": 1,
"userId": 1,
"deviceId": 1,
"appClientId": "1",
},
})
Query
| Parameters | type | required | default |
|---|---|---|---|
| fromDate | integer | false | null |
| toDate | integer | false | null |
| type | integer (see Events type) | false | null |
| membershipId | integer | false | null |
| folderId | integer | false | null |
| userId | integer | false | null |
| deviceId | integer | false | null |
| appClientId | string | false | null |
Events type
| type | description |
|---|---|
| 0 | An app was opened |
| 1 | Someone is successfully connected on SmartLink |
| 2 | A connection has failed on SmartLink |
| 3 | A connection was denied on SmartLink |
| 4 | Someone is successfully connected from an extension |
| 5 | A connection from an extension has failed |
| 6 | A connection from an extension was denied |
| 7 | A new password was set |
| 8 | A login from was blocked by anti-phishing feature |
Response
200: { data: Array of events }
401: { error: "Not authorized" }
500: { error: "Internal server error" }
getFolderById
Fetches a folder by its ID.
getFolderById({
"path": {
"id": "your_folder_id",
},
})
Parameters
| Parameters | type | required | default |
|---|---|---|---|
| id | string | true |
Response
200: { data: The Folder }
401: { error: "Not authorized" }
404: { error: "Folder not found" }
500: { error: "Internal server error" }
deleteFolderById
Deletes a folder resource identified by the given ID.
deleteFolderById({
"path": {
"id": "your_folder_id",
},
})
Parameters
| Parameters | type | required | default |
|---|---|---|---|
| id | string | true |
Response
200: { data: The deleted Folder }
401: { error: "Not authorized" }
404: { error: "Folder not found" }
500: { error: "Internal server error" }
postFolder
Creates a new folder within your organization.
postFolder({
"body": {
"name": "Folder name",
"parentId": 2,
},
})
Body
| Parameters | type | required | default |
|---|---|---|---|
| name | string | true | |
| parentId | integer | false | null |
Response
200: { data: The created Folder }
401: { error: "Not authorized" }
500: { error: "Internal server error" }
getFolders
Get all folders of your organization.
folders = getFolders()
Response
200: { data: Array of Folder }
401: { error: "Not authorized" }
500: { error: "Internal server error" }
postImportApps
Import multiple apps with folders, users assignment and configuration.
postImportApps({
"body": {
"apps": [
{
"title": "Imported app",
"url": "https://app.example.com",
"type": "SMARTLINK",
"folders": ["/Sales/"],
},
],
},
})
Body
| Parameters | type | required | default |
|---|---|---|---|
| apps | array | false | null |
Response
200: { data: { count: number } }
401: { error: "Not authorized" }
500: { error: "Internal server error" }
postImportMemberships
Import multiple users/memberships with folders assignment and registration.
postImportMemberships({
"body": {
"users": [
{
"firstName": "John",
"name": "Doe",
"email": "john.doe@example.com",
"folders": ["/Sales/"],
},
],
},
})
Body
| Parameters | type | required | default |
|---|---|---|---|
| users | array | false | null |
Response
200: { data: { count: number } }
401: { error: "Not authorized" }
500: { error: "Internal server error" }
getFolderByIdApps
Get all apps in a specific folder with pagination, search and filtering.
apps = getFolderByIdApps({
"path": {
"id": 1,
},
"query": {
"page": 1,
"pageSize": 10,
},
})
Parameters
| Parameters | type | required | default |
|---|---|---|---|
| id | integer | true |
Query
| Parameters | type | required | default |
|---|---|---|---|
| page | integer | false | null |
| pageSize | integer | false | null |
| provisioningFilter | string | false | null |
| search | string | false | null |
| sortBy | string | false | null |
| sortOrder | string | false | null |
| typeFilter | string | false | null |
Response
200: { data: { apps: Array of App; total: number } }
401: { error: "Not authorized" }
500: { error: "Internal server error" }
getFolderByIdMemberships
Get all memberships that have access to a specific folder with pagination, search and filtering.
memberships = getFolderByIdMemberships({
"path": {
"id": 1,
},
"query": {
"page": 1,
"pageSize": 10,
},
})
Parameters
| Parameters | type | required | default |
|---|---|---|---|
| id | integer | true |
Query
| Parameters | type | required | default |
|---|---|---|---|
| page | integer | false | null |
| pageSize | integer | false | null |
| roleFilter | string | false | null |
| search | string | false | null |
| sortBy | string | false | null |
| sortOrder | string | false | null |
| stateFilter | string | false | null |
| statusFilter | string | false | null |
Response
200: { data: { memberships: Array of User; total: number } }
401: { error: "Not authorized" }
500: { error: "Internal server error" }
postMembershipByIdDeactivate
Updates a membership's status to "INACTIVE" by its ID.
postMembershipByIdDeactivate({
"path": {
"id": "your_membership_id",
},
})
Parameters
| Parameters | type | required | default |
|---|---|---|---|
| id | string | true |
Response
200: { data: The updated User }
401: { error: "Not authorized" }
404: { error: "User not found" }
500: { error: "Internal server error" }
postMembershipByIdRegister
Generate a registration link for an existing membership and optionally send an invitation email.
postMembershipByIdRegister({
"path": {
"id": 1,
},
"body": {
"sendMail": True,
},
})
Parameters
| Parameters | type | required | default |
|---|---|---|---|
| id | integer | true |
Body
| Parameters | type | required | default |
|---|---|---|---|
| sendMail | boolean | false | false |
Response
200: { data: { registerLink: string; mailSent: boolean } }
401: { error: "Not authorized" }
404: { error: "User not found" }
500: { error: "Internal server error" }
getMembershipById
Fetches a membership by its ID.
getMembershipById({
"path": {
"id": "your_membership_id",
},
})
Parameters
| Parameters | type | required | default |
|---|---|---|---|
| id | string | true |
Response
200: { data: The User }
401: { error: "Not authorized" }
404: { error: "User not found" }
500: { error: "Internal server error" }
putMembershipById
Updates a membership by its ID.
putMembershipById({
"path": {
"id": "<your_membership_id>",
},
"body": {
"firstName": "John",
"name": "Doe",
"phone": "+336...",
"email": "john.doe@fake.com",
"role": "ADMIN", # or "USER"
"status": "ACTIVE", # or "INACTIVE"
"language": "fr",
},
})
Parameters
| Parameters | type | required | default |
|---|---|---|---|
| id | string | true |
Body
| Parameters | type | required | default |
|---|---|---|---|
| firstName | string | false | keep actual value |
| name | string | false | keep actual value |
| phone | string | false | keep actual value |
| string | false | keep actual value | |
| role | "ADMIN" or "USER" | false | keep actual value |
| status | "ACTIVE" or "INACTIVE" | false | keep actual value |
| language | string ("fr", "en", "es", "zh", "de") | false | keep actual value |
Response
200: { data: The updated User }
401: { error: "Not authorized" }
404: { error: "User not found" }
500: { error: "Internal server error" }
deleteMembershipById
Deletes a membership resource identified by the given ID.
deleteMembershipById({
"path": {
"id": "<your_membership_id>",
},
})
Parameters
| Parameters | type | required | default |
|---|---|---|---|
| id | integer | true |
Response
200: { data: The deleted User }
401: { error: "Not authorized" }
404: { error: "User not found" }
500: { error: "Internal server error" }
postMembership
Creates a new membership for your organization.
postMembership({
"body": {
"firstName": "John",
"name": "Doe",
"phone": "+336...",
"email": "john.doe@fake.com",
"sendMail": True, # invite user by email
"sender": {
# sender will be displayed in the invitation email
"firstName": "Admin",
"name": "istrator",
},
"isAdmin": False, # administrator membership?
},
})
Body
| Parameters | type | required | default |
|---|---|---|---|
| firstName | string | true | |
| name | string | true | |
| string | true | ||
| phone | string | true | null |
| sendMail | boolean | false | false |
| sender | {firsName: string; name: string} | false | null |
| isAdmin | boolean | false | false |
Response
200: { data: The created User }
401: { error: "Not authorized" }
500: { error: "Internal server error" }
getMemberships
Get all memberships of your organization.
memberships = getMemberships()
Response
200: { data: Array of User }
401: { error: "Not authorized" }
500: { error: "Internal server error" }
postMemberships
Creates multiple memberships for your organization.
postMemberships({
"body": [
{
"firstName": "John",
"name": "Doe",
"phone": "+336...",
"email": "john.doe@fake.com",
"sendMail": True, # invite user by email
"sender": {
# sender will be displayed in the invitation email
"firstName": "Admin",
"name": "istrator",
},
"isAdmin": False, # administrator membership?,
},
{
"firstName": "Jane",
"name": "Doe",
"phone": "+336...",
"email": "jane.doe@fake.com",
"sendMail": False,
"isAdmin": True, # administrator membership?
},
],
})
Body
Array of:
| Parameters | type | required | default |
|---|---|---|---|
| firstName | string | true | |
| name | string | true | |
| string | true | ||
| phone | string | true | null |
| sendMail | boolean | false | false |
| sender | {firsName: string; name: string} | false | null |
| isAdmin | boolean | false | false |
Response
200: { data: {memberships: Array of created Users, count: number (total of users for this search)} }
401: { error: "Not authorized" }
500: { error: "Internal server error" }
getMembershipsSearch
Search memberships with various filters. Responses are paginated so you need to specify the page you want.
memberships = getMembershipsSearch({
"query": {
"page": 1,
"pageSize": 10,
"roles": "ADMIN,USER",
"search": "john", # search in names, firstNames, emails and phones
"statusFilter": "all",
},
})
Query
| Parameters | type | required | default |
|---|---|---|---|
| page | integer | true | |
| pageSize | integer | false | 10 |
| roles | string (comma separated ) | false | null (all roles) |
| search | string | false | null |
| statusFilter | string ("all", "registered" or "unregistered") | false | all |
Response
200: { data: Array of User }
401: { error: "Not authorized" }
500: { error: "Internal server error" }
getOrganization
Retrieve public information about the organization associated with the workflow API key.
organization = getOrganization()
Response
200: { data: Organization }
401: { error: "Not authorized" }
500: { error: "Internal server error" }
Schemas
User
| Field | type | description |
|---|---|---|
| id | integer | Membership unique ID |
| role | string | Membership role (OWNER, USER, ADMIN) |
| organizationId | integer | Unique ID of the organization |
| userId | integer | Unique ID of the user |
| createdAt | Date | Creation date |
| updatedAt | Date | Last update date |
| status | string | Membership status (ACTIVE, INACTIVE) |
| language | string | Preferred language (ex 'en') |
| name | string | User's name |
| firstName | string | User's first name |
| string | User's email address | |
| phone | string | User's phone number |
Folder
| Field | type | description |
|---|---|---|
| id | integer | Folder unique ID |
| name | string | Folder name |
| forAllUsers | boolean | Indicates if folder is for all users |
| organizationId | integer | Unique ID of the organization |
| parentId | integer | Unique ID of the parent folder |
| path | string | Full path of the folder (/NewYork/HR) |
App
| Field | type | description |
|---|---|---|
| clientId | string | App client ID |
| type | string | App type (SMARTLINK, SAML2) |
| title | string | App title |
| url | string | App URL |
| iconUrl | string | App icon URL |
| ping | integer | How many times the app was opened |
| slug | string | Unique slug for the app |
| description | string | App description |
| organizationId | integer | Unique ID of the organization |
Organization
| Field | type | description |
|---|---|---|
| id | integer | Organization unique ID |
| name | string | Organization name |
| maxUser | integer | Maximum number of users |
| maxApp | integer | Maximum number of apps |
| maxDomain | integer | Maximum number of domains |
| maxFolder | integer | Maximum number of folders |
| createdAt | Date | Creation date |
| updatedAt | Date | Last update date |
| admin | boolean | Organization is admin managed |
| allowCreation | boolean | Allow creation |
| createOnly | boolean | Create only |
| licenseExpiration | Date | License expiration date |
| stripeCustomerId | string | Stripe customer ID |
| language | string | Default language |
| license | string | License type (FREE, PRO, ENTERPRISE) |
| visibility | string | Visibility (PUBLIC, PRIVATE) |
Event
| Field | type | description |
|---|---|---|
| id | integer | Log event unique ID |
| createdAt | DateTime | Creation date of the log event |
| message | string | Log event message |
| type | integer | Log event type type |
| organizationId | integer | Unique ID of the organization (nullable) |
| membershipId | integer | Unique ID of the membership (nullable) |
| folderId | integer | Unique ID of the folder (nullable) |
| deviceId | integer | Unique ID of the device (nullable) |
| userId | integer | Unique ID of the user (nullable) |
| appClientId | string | Unique client ID of the app (nullable) |
Webhooks
Verify
import asyncio
from smartlink_sdk import Webhook
async def handle_webhook():
body = await request.json() # assuming request is an async request object
if await Webhook.verify(body, "<YOUR API KEY>"):
if body["event"] == "membership.created":
# handle membership created webhook
# ...
# ....
else:
# wrong signature or timestamp expired
pass
asyncio.run(handle_webhook())
Webhook Types
The following webhook types are available:
-
Membership Webhooks
membership.created: Triggered when a membership is created.membership.deleted: Triggered when a membership is deleted.membership.updated: Triggered when a membership is updated.membership.connected: Triggered when a membership connection is established.membership.connected.fail: Triggered when a membership connection fails.membership.connected.denied: Triggered when a membership connection is denied.membership.extension.connected: Triggered when a membership is connected from an extension.membership.extension.connected.fail: Triggered when a membership connection fails from an extension.membership.extension.connected.denied: Triggered when a membership connection is denied from an extension.
-
Folder Webhooks
folder.created: Triggered when a folder is created.folder.deleted: Triggered when a folder is deleted.folder.updated: Triggered when a folder is updated.
-
App Webhooks
app.created: Triggered when an app is created.app.deleted: Triggered when an app is deleted.app.updated: Triggered when an app is updated.app.opened: Triggered when an app is opened.
-
Event Webhooks
event: Triggered when a log event occurs.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file smartlink_sdk-0.1.0.tar.gz.
File metadata
- Download URL: smartlink_sdk-0.1.0.tar.gz
- Upload date:
- Size: 22.2 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.3
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
8487daa371dab91a0241d51b21ff6be05fd27d08a5b54425c288c10a2d09e077
|
|
| MD5 |
91693ea1e420e4c125024ced98288173
|
|
| BLAKE2b-256 |
322c7d5ef0ed60e244a6072c3fbfab955d65d23009453bfd13115ee9ca4abc17
|
File details
Details for the file smartlink_sdk-0.1.0-py3-none-any.whl.
File metadata
- Download URL: smartlink_sdk-0.1.0-py3-none-any.whl
- Upload date:
- Size: 15.4 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.3
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
63eaa2617c6d2f2cef26f8f3e1099def29a0454ddaf2854aa2f2d648c834849c
|
|
| MD5 |
4ee76de8212c871414d6314f4a65fdb4
|
|
| BLAKE2b-256 |
a664ff7619e904b21836ba2975f8f6ce390643616bc49e5406a2c731b258b870
|