An unofficial async Python API wrapper for Webtop (the SmartSchool educational platform used by Israeli schools). This library gives you easy access to Webtop's student portal API endpoints for retrieving the student dashboard, homework, schedules, messages, notifications, discipline events, and more.
Unofficial project. pywebtop is not affiliated with, endorsed by, or supported by SmartSchool or Webtop. It talks to the same private web API that the Webtop site uses, which can change without notice. Use it only with your own account or your own children's accounts. See Security & Privacy.
- Features
- Installation
- Quick Start
- API Reference
- Examples
- Error Handling
- Authentication Details
- Endpoints Used
- Configuration
- Logging
- Security & Privacy
- Troubleshooting
- Project Structure
- Development
- License
- 🔐 Async/Await Support - Built on
httpxfor modern async Python - 📚 Student Portal Access - Log in and retrieve the student dashboard
- 📖 Homework & Assignments - Get homework details by class
- 📅 Schedule/Timetable - Retrieve pupil schedules for any week
- 💬 Messaging System - Access the message inbox with paging, read-status filter, and search
- 🔔 Notifications - Get unread notifications and notification settings
- 📊 Discipline Events - Retrieve behavior/discipline records
- 👥 Multi-Student Support - Discover and switch between siblings/linked accounts
- 🧰 Raw Request Helper - Call any other Webtop endpoint with the authenticated
request()method - ⚙️ Configurable - Custom base URL, timeout, and auto-login support
Install via pip:
pip install pywebtopOr from source:
git clone https://github.com/t0mer/pywebtop.git
cd pywebtop
pip install -e .The package is installed as pywebtop, but it is imported as webtop:
from webtop import WebtopClient- Python 3.8+
httpx>=0.25,<1.0(the only runtime dependency)- A Webtop (SmartSchool) username and password
import asyncio
from webtop import WebtopClient
async def main():
# Create the client; the connection is closed when the block exits
async with WebtopClient(username="your_username", password="your_password") as client:
# With auto_login=True (the default) the first API call logs in for you.
# Calling login() explicitly gives you the session object right away.
session = await client.login()
print(f"Logged in as: {session.first_name} {session.last_name}")
# Get the student dashboard (raw JSON)
dashboard = await client.get_students()
print(dashboard)
asyncio.run(main())If you prefer to control login manually:
async with WebtopClient(
username="your_username",
password="your_password",
auto_login=False
) as client:
# Manually call login; any API call before this raises WebtopLoginError
session = await client.login()
# Now make requests
dashboard = await client.get_students()All methods are coroutines (await them). Apart from login(), switch_student(), get_linked_students(), and request(), every endpoint method returns the parsed JSON response as-is (usually a dict with status, data, and error fields). The library does not model the response contents, so inspect the returned data for the fields your school returns.
The parameters of the data endpoint methods (get_homework(), get_discipline_events(), get_notification_settings(), get_messages_inbox(), get_pupil_schedule()) are keyword-only. switch_student() also accepts positional arguments.
WebtopClient(
username: str,
password: str,
*,
data: str = "+Aabe7FAdVluG6Lu+0ibrA==",
remember_me: bool = False,
biometric_login: str = "",
base_url: str = "https://webtopserver.smartschool.co.il",
timeout: float = 20.0,
auto_login: bool = True,
)Parameters:
| Parameter | Default | Description |
|---|---|---|
username |
(required) | Webtop username |
password |
(required) | Webtop password |
data |
"+Aabe7FAdVluG6Lu+0ibrA==" |
Value sent as the Data field of the login request (see Obtaining the data Parameter) |
remember_me |
False |
Sent as the RememberMe field of the login request |
biometric_login |
"" |
Sent as the BiometricLogin field of the login request |
base_url |
"https://webtopserver.smartschool.co.il" |
Webtop server URL (a trailing / is stripped) |
timeout |
20.0 |
Request timeout in seconds |
auto_login |
True |
Log in automatically before the first authenticated request |
Perform login and establish the session. Returns a WebtopSession.
session = await client.login()
# session.token - auth token
# session.user_id - user ID
# session.student_id - student ID
# session.school_id - school ID
# session.school_name - school name
# session.first_name, session.last_name - user names
# session.raw_login_data - the full "data" object from the login responseCalling login() again performs a fresh login and replaces the token.
Log in only if there is no session yet. Raises WebtopLoginError if there is no session and auto_login=False. The endpoint methods call this for you.
await client.ensure_logged_in()If a parent account is linked to multiple students (e.g., siblings, possibly in different schools), you can discover and switch between them.
Get a list of all students linked to the current account. Returns the data list from the response, and raises WebtopRequestError if the response has status other than true.
students = await client.get_linked_students()
for student in students:
print(f"Login: {student.get('studentLogin')}, ID: {student.get('studentId')}")Switch the active session to a specific student. This replaces client.session (and the webToken cookie, if a new token is returned) for all subsequent requests. Returns the new WebtopSession.
# Switch to a student using the studentId from get_linked_students()
new_session = await client.switch_student(student_id="<student-id>")
print(f"Switched to school: {new_session.school_name}")Parameters:
student_id- ThestudentIdvalue of a linked student (string)saved_user- Optional, sent assavedUser(default:"")
switch_student() does not log in on its own, so call it after login() (or after any other API call). It raises WebtopLoginError when the server returns HTTP 400 or higher, a status that is not true, or no data. Timeouts, network errors, and non-JSON responses are not wrapped: they surface as the raw httpx or JSON decoding exception. After a switch, session.student_id is taken from the id field of the switch response.
Get the student dashboard (InitDashboard). Returns the raw JSON response. Depending on the account, data may be a list or a dictionary (for example, the multi-student example reads the child's name from data["childrens"]).
dashboard = await client.get_students()Get homework for a specific class.
homework = await client.get_homework(
encrypted_student_id="<encrypted-student-id>",
class_code=3,
class_number=3,
)Parameters:
encrypted_student_id- Encrypted student ID (theidfield of the login data, i.e.client.session.raw_login_data["id"])class_code- Class code (int)class_number- Class number (int)
Get the student schedule/timetable for a specific week.
schedule = await client.get_pupil_schedule(
week_index=0, # 0 = current week
view_type=0, # schedule view type
study_year=2026, # school year (required)
encrypted_student_id="<encrypted-student-id>",
class_code=3,
module_id=10,
)Parameters:
week_index- Week offset (0 = current week, 1 = next week, etc.; default: 0)view_type- Schedule view type (usually 0; default: 0)study_year- School year, e.g. 2026 (required)encrypted_student_id- Encrypted student ID from the login data (required)class_code- Class code (required)module_id- Module ID (default: 10)
Get messages from the inbox with pagination and filtering.
messages = await client.get_messages_inbox(
page_id=1,
label_id=0,
has_read=None, # None, True, or False to filter
search_query="",
)Parameters:
page_id- Page number (1-based, default: 1)label_id- Message label/category (default: 0)has_read- Filter by read status:True,False, orNonefor all (default:None)search_query- Free-text search (default:"")
Get a preview of unread notifications.
notifications = await client.get_preview_unread_notifications()Get notification settings for the user.
settings = await client.get_notification_settings(
encrypted_student_id="<encrypted-student-id>",
)Parameters:
encrypted_student_id- Encrypted student ID from the login data
Get student behavior/discipline events.
discipline = await client.get_discipline_events(
encrypted_student_id="<encrypted-student-id>",
class_code=3,
)Parameters:
encrypted_student_id- Encrypted student ID from the login dataclass_code- Class code
Send an authenticated request to any Webtop path. It logs in first if needed (see auto_login), raises WebtopRequestError on timeouts, network errors, and HTTP status codes of 400 or higher, and returns the httpx.Response.
resp = await client.request("POST", "/server/api/dashboard/InitDashboard", json={})
print(resp.json())Parameters:
method- HTTP method, e.g."POST"path- Path relative tobase_urlheaders- Optional extra headers (keyword-only)**kwargs- Passed tohttpx.AsyncClient.request()(e.g.json=,params=)
After login, access session information via client.session (a frozen WebtopSession dataclass). Accessing client.session before logging in raises WebtopLoginError.
session = client.session
print(session.token) # Auth token
print(session.user_id) # User ID
print(session.student_id) # Student ID
print(session.school_id) # School ID
print(session.school_name) # School name
print(session.first_name) # First name
print(session.last_name) # Last name
print(session.raw_login_data) # Raw login response dataif client.is_logged_in:
print("Already logged in")await client.close() # or use 'async with' for auto-closeThe class_code and class_number values below are placeholders. Where they come from depends on your school's data, so inspect client.session.raw_login_data and the get_students() response to find them.
import asyncio
from webtop import WebtopClient
async def get_homework_example():
async with WebtopClient(
username="your_username",
password="your_password"
) as client:
session = await client.login()
# The encrypted student ID is the "id" field of the login data
encrypted_id = session.raw_login_data["id"]
# Get homework
homework = await client.get_homework(
encrypted_student_id=encrypted_id,
class_code=3,
class_number=3,
)
print(homework)
asyncio.run(get_homework_example())import asyncio
from datetime import datetime
from webtop import WebtopClient
async def check_schedule():
async with WebtopClient(
username="your_username",
password="your_password"
) as client:
session = await client.login()
# Get this week's schedule
schedule = await client.get_pupil_schedule(
week_index=0,
study_year=2026,
encrypted_student_id=session.raw_login_data["id"],
class_code=3,
)
print(f"Schedule for week {datetime.now().isocalendar()[1]}:")
print(schedule)
asyncio.run(check_schedule())import asyncio
from webtop import WebtopClient
async def check_messages():
async with WebtopClient(
username="your_username",
password="your_password"
) as client:
# Get unread messages
messages = await client.get_messages_inbox(
page_id=1,
has_read=False, # Only unread
)
items = messages.get("data") or []
print(f"Found {len(items)} unread messages")
for msg in items:
# Field names as used in examples/multi_student_messages.py
sender = msg.get("senderName") or "Unknown"
print(f" - {sender}: {msg.get('subject')}")
asyncio.run(check_messages())examples/multi_student_messages.py logs in, lists every linked student with get_linked_students(), switches to each one with switch_student(), reads the child's name from the dashboard, and prints up to 20 inbox messages per student.
To run it from a clone, replace the <username>, <password>, and <data> placeholders in the script, then:
python examples/multi_student_messages.pyThe script prints student names and message subjects to the terminal.
The library provides specific exceptions for error handling:
import asyncio
from webtop import WebtopClient, WebtopLoginError, WebtopRequestError
async def main():
try:
async with WebtopClient(username="your_username", password="your_password") as client:
dashboard = await client.get_students()
except WebtopLoginError as e:
print(f"Login failed: {e}")
except WebtopRequestError as e:
print(f"API request failed: {e}")
except Exception as e:
print(f"Unexpected error: {e}")
asyncio.run(main())Exception Types:
WebtopError- Base exception classWebtopLoginError- Raised when login fails (including timeouts, network errors, and a login response that is not JSON), when the login response has no token, when you are not logged in andauto_login=False, whenclient.sessionis read before login, and whenswitch_student()gets HTTP 400 or higher, astatusthat is nottrue, or nodataWebtopRequestError- Raised when an API request times out, fails at the network level, or returns HTTP 400 or higher; also raised byget_linked_students()when the responsestatusis nottrue
Good to know:
- Apart from
get_linked_students(), the endpoint methods do not check thestatusfield of the JSON body. A response with"status": falseand HTTP 200 is returned to you as-is, so checkstatusyourself. login()wraps a non-JSON response inWebtopLoginError.switch_student()and the endpoint methods do not: a non-JSON body raises the underlying JSON decoding error, andswitch_student()also letshttpxtimeout and network errors through unwrapped.- Exception messages can include the raw server response body.
- Login Request -
POST /server/api/user/LoginByUserNameAndPasswordwithUserName,Password,Data,RememberMe, andBiometricLogin - Status Check - The response must have
"status": true; otherwiseWebtopLoginErroris raised with the server'serrorDescriptionanderrorId - Token Response - The server returns
data.tokenin the response - Cookie-Based Auth - The token is set as a cookie:
webToken=<token> - Subsequent Requests - All API calls automatically include the
webTokencookie
Only username/password login is implemented. There is no captcha, 2FA, or SSO handling, and the token is not refreshed automatically. If the token expires, requests may fail with WebtopRequestError, or they may return a "status": false body without raising an exception; in either case call await client.login() again or create a new client.
Authentication is handled automatically via httpx's cookie jar. The token is stored as a cookie and included in all subsequent requests. Every request is also sent with Content-Type: application/json; charset=utf-8, and redirects are followed.
The data parameter is an opaque value that the Webtop login page sends in the Data field of the login request. To find your specific data value:
-
Use Browser DevTools:
- Open your browser and navigate to the Webtop login page
- Open Developer Tools (F12)
- Go to the Network tab
- Log in to Webtop
- Find the
LoginByUserNameAndPasswordrequest - Check the request payload - the
Datafield contains the value to use
-
Default Value:
+Aabe7FAdVluG6Lu+0ibrA==is the library's default value- If login fails, capture your own
datavalue using the method above
Note: The data parameter appears to be institution-specific. Once you find the correct value for your school, it should remain constant.
All requests go to base_url (default https://webtopserver.smartschool.co.il) and use POST with a JSON body.
| Method | Path | Request body |
|---|---|---|
login() |
/server/api/user/LoginByUserNameAndPassword |
UserName, Password, Data, RememberMe, BiometricLogin |
switch_student() |
/server/api/user/ChangeUser |
StudentId, institutionCode (null), savedUser, userType (null) |
get_linked_students() |
/server/api/user/GetMultipleUsersForUser |
{} |
get_students() |
/server/api/dashboard/InitDashboard |
{} |
get_homework() |
/server/api/dashboard/GetHomeWork |
id, ClassCode, ClassNumber |
get_discipline_events() |
/server/api/dashboard/GetPupilDiciplineEvents |
id, ClassCode |
get_preview_unread_notifications() |
/server/api/Menu/GetPreviewUnreadNotifications |
{} |
get_notification_settings() |
/server/api/Notification/GetNotificationsSettings |
id |
get_messages_inbox() |
/server/api/messageBox/GetMessagesInbox |
PageId, LabelId, HasRead, SearchQuery |
get_pupil_schedule() |
/server/api/PupilCard/GetPupilScheduale |
weekIndex, viewType, studyYear, studentID, classCode, moduleID |
The spellings GetPupilDiciplineEvents and GetPupilScheduale are the server's own endpoint names.
pywebtop reads no environment variables or config files. Everything is set through the WebtopClient constructor (see Client Initialization).
If you use a custom Webtop server (it must expose the same /server/api/... paths):
async with WebtopClient(
username="your_username",
password="your_password",
base_url="https://custom-webtop-server.example.com"
) as client:
# Use custom server
dashboard = await client.get_students()Adjust the request timeout:
async with WebtopClient(
username="your_username",
password="your_password",
timeout=30.0 # 30 seconds
) as client:
dashboard = await client.get_students()Enable remember-me login:
async with WebtopClient(
username="your_username",
password="your_password",
remember_me=True
) as client:
await client.login()pywebtop logs through the standard logging module under the webtop.client logger and does not configure any handlers itself. To see its output:
import logging
logging.basicConfig(level=logging.INFO)
# or only this library:
logging.getLogger("webtop").setLevel(logging.DEBUG)Warning: the logs contain personal data. At
INFOlevel the library logs the username, the logged-in user's first and last name and school name, the names after a student switch, and inbox search queries.switch_student()also logs the first characters of the student ID atINFO. AtERRORlevel it logs raw server response bodies and fullChangeUser/GetMultipleUsersForUserresponses. Passwords and the token are not logged. Keep thewebtoplogger atWARNINGor higher in production, and don't ship these logs to shared or third-party log services.
logging.getLogger("webtop").setLevel(logging.WARNING)Webtop holds data about students, most of whom are minors: names, schools, class details, schedules, homework, messages, and behavior records. Treat everything this library returns as sensitive personal data.
- Responsible use: only use pywebtop with your own account or your own children's accounts. Don't use it to access other people's data, and follow your school's and SmartSchool's terms of use.
- Credentials: don't hard-code usernames and passwords in scripts you commit. Load them from environment variables or a secrets manager instead.
- Token:
session.token(andraw_login_data) grants access to the account until it expires. Don't print, log, or store it. - Stored data: if you save responses (for example, to feed a bot or a dashboard), keep them private, limit who can read them, and delete them when you no longer need them.
- Logs and errors: see Logging. Exception messages can include raw response bodies too.
- Request volume: poll at a modest rate. The API is private and not meant for heavy automated use.
WebtopLoginError: Login returned status=false ...- The server rejected the login. Check the username and password; if they are correct, capture your school'sdatavalue (see Obtaining thedataParameter).WebtopLoginError: Not logged in and auto_login=False- Callawait client.login()before other methods, or leaveauto_login=True.- Methods return
"status": false- The endpoint methods return the JSON body as-is. Check theerrorDescriptionin the response and the parameters you passed (for example,encrypted_student_idmust be the encryptedidfrom the login data, notstudentId). - Requests start failing after a while - The session token may have expired. Call
await client.login()again. - Empty or unexpected dashboard data -
get_students()can returndataas a list or a dictionary depending on the account. Inspect the raw response.
pywebtop/
├── webtop/
│ ├── __init__.py # Package exports
│ ├── client.py # Main WebtopClient class
│ ├── models.py # Data models (WebtopSession)
│ └── exceptions.py # Custom exceptions
├── examples/
│ └── multi_student_messages.py # Linked-student discovery and messages
├── .github/workflows/
│ └── python-publish.yml # Builds and publishes to PyPI
├── setup.py # Package setup
├── LICENSE # License text
└── README.md # This file
# Clone repository
git clone https://github.com/t0mer/pywebtop.git
cd pywebtop
# Install in development mode
pip install -e .The repository does not include an automated test suite yet. To check changes by hand, run the example script against your own account (see Multi-Student Example Script).
pip install build
python -m build # creates dist/*.tar.gz and dist/*.whlThe Publish pypi package workflow builds the package and uploads it to PyPI when a GitHub release is published, or when it is started manually. It uses the PYPI_API_TOKEN repository secret. The version is set in setup.py.
This project is licensed under the Apache License 2.0 - see the LICENSE file for details.
Note: the package metadata on PyPI (from
setup.py) declares the MIT license, while the LICENSE file in this repository is Apache License 2.0.
Tomer Klein - GitHub - tomer.klein@gmail.com
This is an unofficial wrapper for the Webtop API. It is not affiliated with or endorsed by SmartSchool or Webtop. The API it uses is private and undocumented and may change or stop working at any time. Use at your own risk, only with accounts you are entitled to access, and ensure compliance with Webtop's Terms of Service.
Contributions are welcome! Please feel free to submit a Pull Request. Don't include real credentials, tokens, student IDs, or captured responses containing personal data in issues, PRs, or examples.
For issues, feature requests, or questions:
- Open an issue on GitHub Issues
- Contact the author
- Linked-student discovery (
get_linked_students()) - Student switching (
switch_student()) get_students()handles both list and dictionary dashboard payloads- Multi-student example script
- Initial release
- Login functionality
- Dashboard access
- Homework retrieval
- Schedule/timetable access
- Messaging system
- Notifications
- Discipline events tracking
- pymashov - Async Python wrapper for Mashov, another Israeli school portal
Last Updated: September 2026