A clean, slim, synchronous Python 3.12+ client for the NinjaOne RMM API v2.
- Minimal dependencies: Uses only
httpxfor HTTP. - OAuth2 authentication: Client Credentials flow (server-to-server). Tokens are cached and automatically refreshed.
- Clean API: snake_case method names and raw dict/list responses. No bloated wrapper objects.
- Type-safe: Full type hints and mypy strict mode support.
- Python 3.12 or higher
httpx >= 0.27
Install from the repository:
pip install git+https://github.com/ak9999/ninjaonepy.gitfrom ninjaonepy import Client
# Create a client using OAuth2 credentials
with Client(
client_id="your_client_id",
client_secret="your_client_secret",
europe=False, # Use True for EU data centre
) as client:
# Get all organizations
orgs = client.get_organizations()
print(orgs)
# Get devices
devices = client.get_devices()
print(devices)import os
from ninjaonepy import Client
client_id = os.environ.get("NINJA_CLIENT_ID")
client_secret = os.environ.get("NINJA_CLIENT_SECRET")
if not client_id or not client_secret:
raise ValueError("NINJA_CLIENT_ID and NINJA_CLIENT_SECRET are required")
with Client(client_id=client_id, client_secret=client_secret) as client:
orgs = client.get_organizations()The client is organized into logical mixins:
get_organizations()— List all organizationsget_devices()— List all devices with basic infoget_devices_detailed()— List devices with full detailsget_antivirus_status()— Get AV status for all devicesget_antivirus_threats()— Get detected threats- And more...
get_organization(id)— Get a specific organizationcreate_organization(data)— Create an organizationupdate_organization(id, data)— Update an organization- And more...
get_device(id)— Get a specific deviceget_device_details(id)— Get full device details- And more...
reset_alert(...)— Reset an alertapprove_alert(...)— Approve an alert- And more...
query_devices(...)— Run a bulk device query- And more...
- See the source code and API documentation for complete endpoint lists.
All API methods return raw dictionaries or lists—no wrapper objects. For example:
orgs = client.get_organizations()
# orgs is a list of dicts:
# [
# {"id": 1, "name": "Acme Corp", "description": "..."},
# {"id": 2, "name": "Globex", "description": "..."},
# ]The client raises typed exceptions for common errors:
from ninjaonepy._exceptions import AuthError, NotFoundError, RateLimitError, NinjaError
try:
with Client(client_id="bad", client_secret="creds") as client:
client.get_organizations()
except AuthError:
print("Invalid credentials")
except NotFoundError:
print("Resource not found")
except RateLimitError:
print("Rate limited; try again later")
except NinjaError as e:
print(f"API error: {e}")uv sync --group devuv run pytestuv run ruff check src tests
uv run ruff format src testsuv run mypy srcuv buildThe client uses OAuth2 Client Credentials flow:
- You provide
client_idandclient_secretfrom the NinjaOne admin portal. - The client automatically fetches a Bearer token on first use.
- Tokens are cached and refreshed 30 seconds before expiry.
- Tokens are requested from:
- US:
https://app.ninjarmm.com/oauth/token - EU:
https://eu.ninjarmm.com/oauth/token
- US:
The old ninjarmmpy package used HMAC-SHA1 signing, which is no longer supported by NinjaOne. This rewrite uses OAuth2.
| Old API | New API |
|---|---|
ninjarmmpy |
ninjaonepy |
AccessKeyID |
client_id |
SecretAccessKey |
client_secret |
getOrganizations() |
get_organizations() |
Europe=False |
europe=False |
The old code will not work without updating credentials and method names.
MIT — See LICENSE for details.
Contributions welcome! Please ensure all tests pass and code is linted with ruff and type-checked with mypy before submitting a PR.