Skip to main content

lansenger-sdk

Framework-independent Python SDK for the Lansenger (蓝信) platform — supports Lansenger apps, organization bots, and personal bots.

License: MIT Python 3.10+ Tests: 341

💠 Zero framework dependencies — only httpx. Works with any async or sync Python codebase.

Supported Bot Types

Bot Type Auth WebSocket Inbound All APIs
Lansenger App appToken + userToken ✗ (uses webhook) ✓
Organization Bot appToken + userToken ✗ (uses webhook) ✓
Personal Bot appToken ✓ (WebSocket) ✓ (limited for non-bot APIs)

All three bot types use the same auth mechanism: appToken is required for every API call; userToken is only needed for specific user-level operations (user info, staff search, calendar, etc.).

Features

  • Async & Sync clients — LansengerClient (async) + LansengerSyncClient (blocking)
  • Credential & token persistence — CredentialStore saves app_id, app_secret, URLs, appToken, userToken to file (survives restarts)
  • OAuth2 user authentication — authorize URL, code exchange, token refresh
  • Organization & departments — org info, department detail/children/staff
  • Staff & contacts — basic/detailed info, ID mapping, department ancestors, search
  • Messaging — 3 private chat channels (bot, official account, user impersonate) + group chat, all message types, @mention, human/bot sender identity, urgent reminders
  • Rich cards — appCard (with dynamic status updates), oacard, linkCard, verifyCard, appArticles
  • Streaming messages — SSE-based real-time delivery for AI agents
  • Media upload/download — files, images, videos with auto type detection, media path fetch
  • Message management — revoke, dynamic card update
  • Groups — create, info, members, list, membership check, update settings & members, dismiss
  • Calendar & schedule — primary calendar, schedule CRUD + update, attendee management + attendee metadata
  • Unified todo — create, update, delete, query, executor management, status counts
  • Callback events — 25 event types, structured parsing, AES decryption (per 4.10.1.4), SHA1 signature verification

Quick Install

pip install lansenger-sdk

For development:

pip install -e ".[dev]"

1. Authentication

appToken — Required for all API calls

Every SDK method requires appToken. The client automatically obtains and refreshes it using your app_id + app_secret. You never need to manage appToken manually — the TokenManager handles the lifecycle:

  1. First call → GET /v1/apptoken/create with app_id + app_secret → returns appToken (valid 2 hours)
  2. Subsequent calls → reuse cached appToken until expiry
  3. Token expired → automatically refresh via the same endpoint
# appToken is managed automatically — just configure app_id + app_secret
client = LansengerClient(app_id="your-appid", app_secret="your-secret")

# You can also get/invalidate token manually
token = await client.get_token()
client.invalidate_token()  # force refresh on next call

userToken — Only needed for specific endpoints

userToken represents a specific Lansenger user's authorization (obtained via OAuth2). It's only required for:

  • User-level information (fetch_user_info, fetch_staff_detail, search_staff)
  • Calendar & schedule operations (fetch_primary_calendar, create_schedule, etc.)
  • Group operations as a human sender

Getting Credentials

Bot Type How to get app_id + app_secret
Personal Bot Lansenger desktop → Contacts → Smart Bots → Personal Bots → click ℹ️ icon (mobile client does NOT show credentials)
Lansenger App Create at Lansenger Developer Center — may require organization admin approval
Organization Bot Create at Lansenger Developer Center — may require organization admin approval

OAuth2 user-level auth

# Build authorize URL — redirect user to Lansenger passport
url = client.build_authorize_url(redirect_uri="https://myapp.com/callback")

# After user authorizes, exchange code for userToken + refreshToken
token_result = await client.exchange_code(code="auth_code_from_callback")

# Refresh expired userToken
new_token = await client.refresh_user_token(refresh_token=token_result.refresh_token)

# Fetch user profile
user_info = await client.fetch_user_info(user_token=token_result.user_token)

2. Organization & Departments

# Organization info
org = await client.fetch_org_info(org_id="orgId")

# Department hierarchy
detail = await client.fetch_department_detail(department_id="deptId")
children = await client.fetch_department_children(department_id="deptId")
staffs = await client.fetch_department_staffs(department_id="deptId")

3. Staff & Contacts

# Basic staff info
staff = await client.fetch_staff_basic_info(staff_id="staffOpenId")

# Detailed profile (userToken recommended)
detail = await client.fetch_staff_detail(staff_id="staffOpenId", user_token="ut")

# Map phone → staffId
mapping = await client.fetch_staff_id_mapping(
    org_id="orgId", id_type="mobile", id_value="13800138000"
)

# Department ancestors for a staff member
ancestors = await client.fetch_department_ancestors(staff_id="staffOpenId")

# Search staff (requires userToken or userId)
results = await client.search_staff(keyword="Zhang San", user_token="ut")

# Org extra field IDs
fields = await client.fetch_org_extra_field_ids(org_id="orgId")

4. Messaging & Media

Bot private chat — most common

result = await client.send_text(chat_id="staff123", content="Hello!")
result = await client.send_markdown(chat_id="staff123", content="**Bold**")
result = await client.send_file(chat_id="staff123", file_path="/path/to/report.pdf")

Public account channel

result = await client.send_account_message(
    msg_type="text", msg_data={"text": {"content": "System notice"}},
    chat_ids=["staff1", "staff2"], account_id="524288-xxxx",
)

User impersonate channel (requires userToken)

result = await client.send_user_message(
    receiver_id="staff456", msg_type="text",
    msg_data={"text": {"content": "Hello"}},
    user_token="ut",  # required
)

Group chat

# Bot → group
result = await client.send_text(chat_id="group123", content="Notice", is_group=True)

# Human → group (with userToken)
result = await client.send_group_message(
    group_id="group123", msg_type="text",
    msg_data={"text": {"content": "I'll handle it"}},
    user_token="ut",
)

# Group chat supports ALL message types (text, formatText, oacard, appCard, linkCard, etc.)
result = await client.send_group_message(
    group_id="group123", msg_type="appCard",
    msg_data={"appCard": {"bodyTitle": "Approval", "isDynamic": True}},
    user_token="ut",
)

# @mention in group
result = await client.send_text(
    chat_id="group123", content="Important!", is_group=True, reminder_all=True,
)

Rich cards

result = await client.send_app_card(chat_id="staff123", body_title="Approval", is_dynamic=True)
result = await client.send_link_card(chat_id="staff123", title="Article", link="https://...")
result = await client.send_app_articles(chat_id="staff123", articles=[...])

# Update dynamic card status
result = await client.update_dynamic_card(msg_id="msg123", is_last_update=True)

Streaming messages (for AI agents)

result = await client.create_stream_message(receiver_id="staff1", receiver_type="staff", stream_id="s1")
result = await client.fetch_stream_message(msg_id="msg123")

Media

# Upload
upload = await client.upload_media(file_path="/path/to/file.pdf")

# Download
download = await client.download_media(media_id="media123")

# Get download URL path (4.5.3)
path_result = await client.fetch_media_path(media_id="media123")

# Revoke messages
result = await client.revoke_message(message_ids=["msg1", "msg2"])

Urgent reminders (4.6.14)

from lansenger_sdk import REMINDER_TYPE_POPUP, REMINDER_TYPE_SMS, REMINDER_TYPE_PHONE

result = await client.send_reminder(
    msg_id="msg123",
    reminder_types=[REMINDER_TYPE_POPUP, REMINDER_TYPE_SMS],
    user_id_list=["staff1", "staff2"],
)

5. Groups

# Create group
group = await client.create_group(name="Project Chat", org_id="orgId", staff_id_list=["s1","s2","s3"])

# Fetch info & members
info = await client.fetch_group_info(group_id="groupOpenId")
members = await client.fetch_group_members(group_id="groupOpenId")
groups = await client.fetch_group_list()

# Check membership
result = await client.check_is_in_group(group_id="groupOpenId", staff_id="staff1")

# Update settings
await client.update_group_info(group_id="groupId", name="New Name", manage_mode=1)

# Add/remove members
await client.update_group_members(
    group_id="groupId", add_user_list=["staff4"], del_user_list=["staff3"],
)

# Dismiss/delete group (owner only, 4.28.6)
await client.dismiss_group(group_id="groupId")

6. Calendar & Schedule

# Get primary calendar (requires userToken or userId)
cal = await client.fetch_primary_calendar(user_token="ut")

# Create schedule
schedule = await client.create_schedule(
    calendar_id=cal.calendar_id, summary="Team Meeting",
    start_time={"date": "2024-01-15", "time": "10:00", "timeZone": "Asia/Shanghai"},
    end_time={"date": "2024-01-15", "time": "11:00", "timeZone": "Asia/Shanghai"},
    attendees=[{"staffId": "staff1", "attendeeFlag": "required"}],
    user_token="ut",
)

# Fetch/delete schedule
info = await client.fetch_schedule(calendar_id="cal1", schedule_id="sch1", user_token="ut")
await client.delete_schedule(calendar_id="cal1", schedule_id="sch1", user_token="ut")

# Schedule list in time range (max 42 days)
schedules = await client.fetch_schedule_list(
    calendar_id="cal1", start_time=1705276800000, end_time=1707940800000, user_token="ut",
)

# Attendee management
attendees = await client.fetch_schedule_attendees(calendar_id="cal1", schedule_id="sch1", user_token="ut")
await client.add_schedule_attendees(calendar_id="cal1", schedule_id="sch1", attendees=["staff2"], user_token="ut")
await client.delete_schedule_attendees(calendar_id="cal1", schedule_id="sch1", attendees=["staff2"], user_token="ut")

# Update schedule (4.23.12)
await client.update_schedule(
    calendar_id="cal1", schedule_id="sch1",
    summary="Updated Meeting", operation_type="modify_all",
    user_token="ut",
)

# Update attendee metadata (4.23.17) — RSVP, color, busy/free, reminders
await client.update_schedule_attendee_meta(
    calendar_id="cal1", schedule_id="sch1",
    rsvp_status="accept", busy_free_state="busy",
    remind_times=[5, 15], user_token="ut",
)

7. Unified Todo

from lansenger_sdk import TODO_TYPE_APPROVAL, TODO_TODO_STATUS_DONE

# Create todo task
todo = await client.create_todo_task(
    title="Approval Request", link="https://app.com/a/1", pc_link="https://pc.app.com/a/1",
    executor_ids=["staff1"], org_id="org1", type=TODO_TYPE_APPROVAL,
)

# Update status (11=pending-read, 12=read, 21=pending-do, 22=done)
await client.update_todo_task_status(todotask_id="taskId", status=TODO_TODO_STATUS_DONE, org_id="org1")

# Update content
await client.update_todo_task(todotask_id="taskId", title="Updated", link="l", pc_link="p", org_id="org1")

# Delete (sender only)
await client.delete_todo_task(todotask_id="taskId", org_id="org1")

# Query
list_result = await client.fetch_todo_task_list(org_id="org1")
task = await client.fetch_todo_task_by_id(todotask_id="taskId", org_id="org1")
task = await client.fetch_todo_task_by_source_id(source_id="src1", org_id="org1")
counts = await client.fetch_todo_task_status_counts(staff_id="staff1", org_id="org1")

# Executor management
await client.add_executors(executor_ids=["staff2"], org_id="org1", todotask_id="taskId")
await client.delete_executors(executor_ids=["staff2"], org_id="org1", todotask_id="taskId")
executors = await client.fetch_executor_list(todotask_id="taskId", org_id="org1")
await client.update_executor_status(
    executor_status_list=[{"executorId": "staff1", "todotaskId": "taskId", "status": "22"}],
    org_id="org1",
)

8. Callback Events

The SDK supports both plain JSON and AES-encrypted callback payloads (per Lansenger API spec 4.10.1.4).

Configuration

Set encoding_key and callback_token (from Lansenger Developer Center callback settings):

client = LansengerClient(
    app_id="your-appid", app_secret="your-secret",
    encoding_key="BASE64_AES_KEY",
    callback_token="CALLBACK_TOKEN",
)

Or via environment variables: LANSENGER_ENCODING_KEY, LANSENGER_CALLBACK_TOKEN.

Parse callback payload (auto-detects encrypted vs plain JSON)

from lansenger_sdk import parse_callback_payload, decrypt_callback_payload

# Plain JSON webhook
events = parse_callback_payload('{"events": [...]}')

# AES-encrypted payload (auto-decrypts with encoding_key)
events = parse_callback_payload(
    encrypted_data,
    encoding_key="BASE64_AES_KEY",
    known_app_id="your-appid",  # helps split orgId/appId in decrypted buffer
)

Verify signature

from lansenger_sdk import verify_callback_signature

# sha1(sort(token, timestamp, nonce, dataEncrypt))
is_valid = verify_callback_signature(
    timestamp, nonce, signature, encoding_key,
    data_encrypt=encrypted_data,
    callback_token="CALLBACK_TOKEN",  # falls back to encoding_key if empty
)

Direct decryption

result = decrypt_callback_payload(encrypted_data, encoding_key="KEY", known_app_id="APPID")
# result = {"orgId": "...", "appId": "...", "events": [...], "length": N}

Event types

types = client.get_callback_event_types()  # 25 event types across 13 categories

AES decryption requires pycryptodome or cryptography package (auto-detected).

Message Type Capability Matrix

msgType Markdown @mention Attachments Private Channels Group Chat Notes
text ✗ ✓ (group) ✓ Bot, Official Account, User Impersonate ✓ Up to 6000 bytes
formatText ✓ ✗ ✗ User Impersonate only ✓ Markdown via formatType=1
oacard ✗ ✗ ✗ Bot, Official Account, User Impersonate ✓ Simple card with fields
appCard ✓ (div tags) ✗ ✗ Bot, Official Account, User Impersonate ✓ Rich card, dynamic updates
linkCard ✗ ✗ ✗ Bot, Official Account ✓ Link preview card
appArticles ✗ ✗ ✗ Bot private only ✓ Article list (1+ articles)
verifyCard ✗ ✗ ✗ Bot, Official Account ✓ Verification card with buttons
i18nAppCard ✓ (div tags) ✗ ✗ Bot, Official Account, User Impersonate ✓ Multilingual appCard (zhHans/zhHant/zhHantHK/en/fr)
i18nSystemAction ✗ ✗ ✗ Platform internal ✓ Multilingual systemAction
i18nSystem ✗ ✗ ✗ Platform internal ✓ Multilingual system message

Group chat supports all message types. Only group chat supports @mention.

Configuration

Environment Variables

Variable Required Description Default
LANSENGER_APP_ID ✓ App/Bot ID —
LANSENGER_APP_SECRET ✓ App/Bot Secret —
LANSENGER_API_GATEWAY_URL ✓ API Gateway URL —
LANSENGER_PASSPORT_URL ✗ Passport URL (for OAuth2) —
LANSENGER_ENCODING_KEY ✗ Callback AES encryption key (Base64) —
LANSENGER_CALLBACK_TOKEN ✗ Callback signature token —

Credential & Token Persistence

By default, credentials and tokens stay in memory only (lost on process exit). Enable file persistence with store_path:

from lansenger_sdk import LansengerClient, CredentialStore

# Auto-persist to ~/.lansenger/sdk_state.json (0600 permissions)
client = LansengerClient(
    app_id="...", app_secret="...",
    encoding_key="BASE64_AES_KEY", callback_token="CALLBACK_TOKEN",
    store_path="~/.lansenger/sdk_state.json",
)

# Or from env with persistence
client = LansengerClient.from_env(store_path="~/.lansenger/sdk_state.json")

# Manual store operations
store = CredentialStore(path="~/.lansenger/sdk_state.json")
store.save_credentials("app_id", "app_secret", api_gateway_url="...", passport_url="...")
store.save_identity_type("personal-bot")  # optional: credential identity type — personal-bot / org-app / org-bot
store.save_user_token("user_token", refresh_token="refresh_token")
token = store.load_app_token()  # None if expired

When persistence is enabled:

  • appToken is saved after each API fetch and restored on restart (skips redundant API calls)
  • userToken + refreshToken are saved after OAuth2 exchange
  • Credentials + URLs are saved together for full config recovery

Sync Client

All methods available on LansengerSyncClient with identical signatures (blocking):

from lansenger_sdk import LansengerSyncClient

client = LansengerSyncClient.from_env()
result = client.send_text(chat_id="staff123", content="Hello!")
org = client.fetch_org_info(org_id="orgId")

Identity & Permissions

Identity Capability Matrix

The Lansenger platform has three identity types with different API access:

Command Domain Personal Bot Org App (Self-built) Org App + Bot Notes
message send-text/markdown/file/... (bot DM) Y N Y Only bots can send bot DMs
message send-text --group (group chat) Y N Y Personal bot now supports group messaging
message send-group-message Y N Y Same as above
message send-account-message (public account) N Y Y Requires public account capability
message send-user-message (user-to-user) N Y Y Requires userToken + OAuth2
message revoke Y Y Y Revoke own messages
staff * (contacts read-only) N Y Y search additionally requires userToken
department * N Y Y Org-level apps only
calendar * N Y Y With userToken = user identity; without = bot identity
todo * N Y Y Org-level apps only
chat list/messages N Y Y Org-level apps only
group * (group management V2) N N Y Requires bot to be in group
media upload Y Y Y General upload
media upload-app Y Y Y Self-built apps only (not ISV)
media download/path Y Y Y General download
oauth * N Y Y Org-level apps only
streaming * N Y Y Org-level apps only
callback * (event parsing) N/A N/A N/A Pure data operation, no identity required

Personal Bot can only send/receive messages and upload/download files. Cannot access contacts, groups, calendars, or OAuth2.

Org App vs Org App + Bot: Same appID/appSecret. The only difference is messaging channels — only bots can send bot DMs and group messages (because only bots can join groups). All other APIs (contacts, calendar, todo, chat, OAuth2, streaming) work identically for both. Currently only self-built apps support bot capability.

Developer Center Permissions

Beyond identity type, specific API calls also depend on permission toggles in the Lansenger Developer Center. The organization may restrict developer access, requiring admin assistance.

Basic Permissions (enabled by default):

Permission Description
Get basic user info Get personnel basic info for system/app login
Send notification messages Get org message channels to send messages to people/groups

Advanced Permissions (disabled by default, must be manually enabled):

Permission Description
Contacts read-only Read access to contacts
Contacts edit Edit access to contacts (create/update/delete staff)
Sensitive info - Phone Access user phone numbers
Sensitive info - Email Access user emails
Sensitive info - ID number Access user ID numbers
Sensitive info - Employee ID Access user employee IDs
Map unique attribute to staff ID Map phone/email/employee ID to staff ID
App edit Create and update apps
Groups read-only Read access to groups
Groups edit Edit access to groups
Calendar read-only Read access to calendar & schedules
Calendar edit Edit access to calendar & schedules
Upload media Upload media file permission
Workbench template read Read access to workbench templates
Workbench template write Write access to workbench templates

When encountering permission errors, first verify the identity type supports the operation, then prompt the user to enable the corresponding advanced permission in the Developer Center (contact org admin if unable to access).

Project Structure

lansenger-sdk-python/
├── src/lansenger_sdk/
│   ├── __init__.py          # All exports
│   ├── client.py            # LansengerClient (async)
│   ├── sync_client.py       # LansengerSyncClient (sync)
│   ├── config.py            # LansengerConfig
│   ├── auth.py              # TokenManager — appToken lifecycle
│   ├── oauth.py             # OAuth2 helpers
│   ├── constants.py         # API endpoints, media types, OAuth scopes
│   ├── exceptions.py        # LansengerError hierarchy
│   ├── models.py            # 38+ dataclass result types
│   ├── contacts.py          # Staff & org info APIs
│   ├── departments.py       # Department APIs
│   ├── account_messages.py  # Public account channel
│   ├── user_messages.py     # User impersonate channel
│   ├── group_messages.py    # Group chat channel
│   ├── media.py             # Upload/download
│   ├── streaming.py         # SSE streaming
│   ├── persistence.py       # CredentialStore — file-based token & credential persistence
│   ├── callbacks.py         # Callback events — 25 event types, structured parsing, AES decryption (4.10.1.4), SHA1 signature verification
│   ├── groups.py            # Group APIs (including dismiss 4.28.6)
│   ├── todos.py             # Unified Todo
│   ├── calendars.py         # Calendar & Schedule (including update 4.23.12, attendee-meta 4.23.17)
│   ├── reminders.py         # Urgent message reminders (4.6.14)
│   └── users.py             # User info
├── tests/                   # 341 tests, all passing
├── pyproject.toml
└── README*.md               # 5-language READMEs

Development

pip install -e ".[dev]"
pytest tests/ -v

License

MIT — see LICENSE.

Metadata

Release files for lansenger-sdk 1.7.4

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for lansenger-sdk 1.7.4
File Size Uploaded Provenance
lansenger_sdk-1.7.4.tar.gz 128.7 kB With provenance Details

Built distribution (wheel)

Table of built distributions (wheels) for lansenger-sdk 1.7.4
File Interpreter ABI Platform
lansenger_sdk-1.7.4-py3-none-any.whl Python 3 none any Details

Total release size: 230.0 kB

Release files / lansenger_sdk-1.7.4.tar.gz

Download URL lansenger_sdk-1.7.4.tar.gz
Size 128.7 kB
Tags Source
SHA-256 checksum
How to use checksums
5f6a936e32e149ee240d2cae529a625b2c4eeb152ac8e2fc8cd5878f09d612dd
BLAKE2b-256 checksum
How to use checksums
dfb1679dcad6e32c0c90dc01604c068bd29dcd33e58e50429bfface30e5c54db
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Aug 28, 2026.

Transparency log

Release files / lansenger_sdk-1.7.4-py3-none-any.whl

Download URL lansenger_sdk-1.7.4-py3-none-any.whl
Size 101.3 kB
Tags Python 3
SHA-256 checksum
How to use checksums
fe40eb22196942462b23a034870ebad61431485150c094549bf3500f67ce2ef6
BLAKE2b-256 checksum
How to use checksums
ef40cc43ec5487dc8d27bbee048db6331b9c5f52c945fea2302b4045a12d80f1
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Aug 28, 2026.

Transparency log

Security for lansenger-sdk 1.7.4

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

Full provenance

All 2 files in this release have been uploaded with provenance metadata.

Select from 2 files

Attestation for lansenger_sdk-1.7.4.tar.gz

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Aug 28, 2026.

Transparency log

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Release history Release notifications | RSS feed

This release

1.7.4 This release

2 release files Full provenance

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page