lansenger-sdk
Framework-independent Python SDK for the Lansenger (蓝信) platform — supports Lansenger apps, organization bots, and personal bots.
💠 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 —
CredentialStoresaves 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
- Notices (通知系统) — send official-account notices (text/link content, phone/staff targeting, confirm/forward/reply flags, reminder policies, attachments), query org official accounts
- Questionnaires (问卷系统) — create/update/publish/withdraw/finish/delete questionnaires, batch question management, office-account & created/participated lists (paged), answer records and export, presigned upload URL
- Boardroom (会议室预定 V2) — room lookup with area/floor/equipment/time filters, room detail & daily schedule, reserve/edit (single & repeating), cancel, scan-code confirmation, my reservations (paged), grading & office-area lists
- Personal todos (个人待办) — create/edit/list user-owned personal todos and manage attachments; separate from application-identity todos
- 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:
- First call →
GET /v1/apptoken/createwith app_id + app_secret → returnsappToken(valid 2 hours) - Subsequent calls → reuse cached appToken until expiry
- 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": "yes"}], # yes/option/no — "required" is rejected by the server
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).
9. Notice (通知系统)
Send official-account notices and query the official accounts of an organization.
Paths carry a
/serversegment (production stage; dev/test environments omit it). The module has no revoke/delete interface. At least one ofcreate_mobile/create_user_idis required;user_tokendoes not replace the creator identity.
# 1) Find official accounts — the "code" field is the accountCode used for sending
accounts = await client.fetch_notice_accounts(org_id="org-001")
account_code = accounts.accounts[0]["code"]
# 2) Send a text notice targeted by phone (max 10 receivers / 10 cc)
result = await client.send_notice(
title="关于系统升级的通知",
content_type=1, # 1=text (content required), 2=link (notice_link required)
account_code=account_code,
user_type=1, # 1=phone, 2=staffId/department
content="系统将于本周六进行升级维护",
release_phones=["13800138000", "13800138001"],
cc_phones=["13800138002"],
create_mobile="13800138000", # required: create_mobile or create_user_id
confirm_flag=1, # require read confirmation (1=yes, 0=no)
remind_status=1, remind_msg_type="mobile", at_once_flag=1,
)
print(result.success, result.notice_code, result.notice_status) # status: 1=draft, 2=sent, 3=revoked
# 3) Or target staff/departments (max 200)
result = await client.send_notice(
title="部门通知", content_type=1, account_code=account_code,
user_type=2,
content="请及时填写本周周报",
release_range=[{"objId": "dept-1", "objName": "研发部", "objType": 2}], # objType: 1=staff, 2=department
cc_staff_ids=["staff-002"],
create_user_id="staff-001",
)
10. Questionnaire (问卷系统)
Create, publish, and analyze questionnaires. Create/publish endpoints require a valid
official-account accountCode (missing → errCode 3104); question structures are
passed through as camelCase dicts; validation errors may arrive concatenated.
# 1) Find manageable office accounts — the "code" field is the accountCode
accounts = await client.fetch_questionnaire_office_accounts()
account_code = accounts.accounts[0]["code"]
# 2) Create a questionnaire, add questions, publish
r = await client.save_questionnaire(
title="2026年度员工满意度调查", account_code=account_code,
welcome_speech="欢迎参加本次调查",
)
qn = r.questionnaire_code
await client.save_questionnaire_questions(qn, [
{
"questionName": "您对当前工作环境是否满意?",
"questionType": "radio", # 16 types: radio/checkbox/fillblank/multiScore/...
"requiredFlag": 1,
"questionOptionList": [{"optionName": "非常满意", "optionOrder": 1}],
},
])
await client.publish_questionnaire(qn, scope_type=1, staff_ids=["U10001"])
# 3) Analyze answers
page = await client.fetch_answer_records(account_code, qn)
detail = await client.fetch_questionnaire_answer_detail(account_code, page.items[0]["code"])
print(detail.answer_user_name, detail.answers) # answers: {questionCode: {context}}
11. Boardroom (会议室预定 V2)
Meeting-room lookup and reservation. gradingId (分区ID) is required by most
endpoints — fetch visible gradings first. Several doc fields use the historical
spelling Fooler (= Floor). Reserve times use yyyy-MM-dd HH:mm:ss.
# 1) Find visible gradings, then office areas, then rooms
gradings = await client.fetch_boardroom_gradings()
grading_id = gradings.gradings[0]["id"]
areas = await client.fetch_boardroom_area_offices(grading_id)
rooms = await client.fetch_boardroom_list(grading_id=grading_id, query_date="2026-07-22")
# 2) Check a room's schedule for the day, then reserve
schedule = await client.fetch_boardroom_schedule(rooms.items[0]["id"], "2026-07-22", grading_id)
print(schedule.reserves, schedule.deactivations)
r = await client.reserve_boardroom(
boardroom_id=rooms.items[0]["id"], name="项目周会", grading_id=grading_id,
reserve_time_start="2026-07-22 09:00:00", reserve_time_end="2026-07-22 10:00:00",
notice_time="会前15分钟", people_number="10",
)
print(r.reserve_code, r.status) # status: 0审批中 1待扫码确认 5预定成功 ...
# 3) Cancel (status 0/1/5 only) or confirm
await client.cancel_boardroom_reserve(r.reserve_id, cancel_reason="改期")
12. Personal Todo (个人待办)
Personal todos use /xtra/tdtask/server/openapi/v3/ and are separate from the
application-identity Unified Todo API. orgId must be supplied explicitly;
the API currently does not expose complete/delete operations.
r = await client.save_personal_todo(
subject="完成项目方案", start_time=1719792000000, due_time=1720195200000,
priority=1, create_user_id="staff-001", org_id="org-001", appid="app-001",
executors=[{"staffId": "staff-001", "opt": 1}],
)
await client.update_personal_todo(
r.todo_code, "org-001", ["subject"], subject="完成项目最终方案",
)
page = await client.fetch_personal_todo_list("org-001", "staff-001", status=0)
print(page.total, page.items)
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 # 42+ 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
│ ├── notices.py # Notice (通知系统)
│ ├── questionnaires.py # Questionnaire (问卷系统)
│ ├── boardrooms.py # Boardroom (会议室预定 V2)
│ ├── personal_todos.py # Personal 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/ # 530 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.10.1
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | |
|---|---|---|---|---|
| lansenger_sdk-1.10.1.tar.gz | 198.7 kB | With provenance | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| lansenger_sdk-1.10.1-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 348.8 kB
Release files / lansenger_sdk-1.10.1.tar.gz
| Download URL | lansenger_sdk-1.10.1.tar.gz |
|---|---|
| Size | 198.7 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
5b53a17fb4a37f8b11060fcb946e909bd2e8bace213f4286f97d5969202cb12b
|
|
BLAKE2b-256 checksum How to use checksums |
32dc7ec87eb2a9f1815333ec8894f9003251f74cea5b1c43a326aed0541c7b5d
|
| 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 Sep 24, 2026.
Transparency logRelease files / lansenger_sdk-1.10.1-py3-none-any.whl
| Download URL | lansenger_sdk-1.10.1-py3-none-any.whl |
|---|---|
| Size | 150.2 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
2496f7851ec966656b312f4d2839eb0f64e209ae28646600749aa3cb1a2bdb2e
|
|
BLAKE2b-256 checksum How to use checksums |
a6a978f3730dc91960d601befcaf594b6e0728c91ea5a3b91ada1b6bb28730e4
|
| 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 Sep 24, 2026.
Transparency logSecurity for lansenger-sdk 1.10.1
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.
Attestation for lansenger_sdk-1.10.1.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 Sep 24, 2026.
Transparency logA 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.