Metadata-Version: 2.4
Name: cylera-client
Version: 1.5.0
Summary: Python client for the Cylera Partner API
Author: Jason Chambers
License: MIT
Project-URL: Homepage, https://github.com/Cylera/cylera-client
Project-URL: Repository, https://github.com/Cylera/cylera-client
Project-URL: Issues, https://github.com/Cylera/cylera-client/issues
Keywords: cylera,iot,medical,security,api,client
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Classifier: Topic :: Security
Requires-Python: <3.14,>=3.9
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: requests>=2.31.0
Dynamic: license-file

[![Quality Gate Status](https://sonarcloud.io/api/project_badges/measure?project=Cylera_cylera-client&metric=alert_status)](https://sonarcloud.io/summary/new_code?id=Cylera_cylera-client) [![Bugs](https://sonarcloud.io/api/project_badges/measure?project=Cylera_cylera-client&metric=bugs)](https://sonarcloud.io/summary/new_code?id=Cylera_cylera-client) [![Code Smells](https://sonarcloud.io/api/project_badges/measure?project=Cylera_cylera-client&metric=code_smells)](https://sonarcloud.io/summary/new_code?id=Cylera_cylera-client) [![Coverage](https://sonarcloud.io/api/project_badges/measure?project=Cylera_cylera-client&metric=coverage)](https://sonarcloud.io/summary/new_code?id=Cylera_cylera-client) [![Duplicated Lines (%)](https://sonarcloud.io/api/project_badges/measure?project=Cylera_cylera-client&metric=duplicated_lines_density)](https://sonarcloud.io/summary/new_code?id=Cylera_cylera-client)

# cylera-client 

A Python client for the [Cylera Partner API](https://github.com/Cylera/cylera-partner-api)

## Installation

```bash
pip install cylera-client
```

Or with [uv](https://github.com/astral-sh/uv):

```bash
uv add cylera-client
```

## Usage

```python
from cylera_client import CyleraClient, Organization, Inventory, Utilization, Network, Risk, Threat

client = CyleraClient(
    username="you@example.com",
    password="your-password",
    base_url="https://partner.us1.cylera.com/",
)
```

Authentication is handled automatically. Tokens are cached and refreshed before expiry.

#### Using an already-obtained token

If you already have a valid Cylera token — for example, one an integration
obtained for its user from the Partner API — construct the client with
`token=` instead of `username`/`password`:

```python
client = CyleraClient(
    token="eyJ0eXA...",
    base_url="https://partner.us1.cylera.com/",
)
```

In this mode the client never calls `/auth/login_user` and never attempts
to re-authenticate itself — it trusts the token as-is until the API
rejects it with a 401, which is raised to you as a `CyleraAPIError` rather
than retried. `username`/`password`, `token` and `token_provider` (below)
are mutually exclusive; passing more than one (or none) raises a
`ValueError`.

#### Signing in through WorkOS

An integration that signs its users in through WorkOS, such as the Cylera
CLI or the Remote Cylera MCP Server, never sends the WorkOS token to the
Partner API's other routes. It exchanges it for a Cylera token with
`exchange_workos_token()`. The Cylera token expires with the WorkOS token,
within minutes, so pass a function that exchanges the current WorkOS
token as `token_provider=`:

```python
from cylera_client import CyleraClient, exchange_workos_token

base_url = "https://partner.us1.cylera.com/"
client = CyleraClient(
    token_provider=lambda: exchange_workos_token(base_url, current_workos_token()),
    base_url=base_url,
)
```

The client calls it at the first request, again after a 401 (the token
expired), and after `switch_organization()` or `reset_organization()`,
since a token from before a switch keeps acting on the old organization.
`exchange_workos_token()` raises `CyleraExchangeError` when the Partner API
refuses the WorkOS token (`status_code` 401) or has no token exchange
(`status_code` 404).

### Organization

```python
organization = Organization(client)

# Get the organization associated with your API credentials
org = organization.get_organization()
# {"organization_id": 17, "name": "Cylera", "internal_name": "cylera"}

# List organizations available to switch into
available = organization.get_available_organizations()

# Switch into a different organization
# (in username/password and token_provider modes, the token is invalidated
# after switch and the client gets a new one on the next call; in token mode
# the same token keeps working, but still acts on the organization it was
# issued for: get a new token to act in the one you switched into)
# Note that switch_organization is handled asynchronously
# So, whenever you switch, it is wise to sleep for a few seconds to give
# the back-end time to process the reset request.
organization.switch_organization(available[0]["id"])

# Reset back to your home organization
# Note that reset_organization is handled asynchronously
# So, whenever you reset, it is wise to sleep for a few seconds to give
# the back-end time to process the reset request.
organization.reset_organization()
```

### Inventory

```python
inventory = Inventory(client)

# Get a single device by MAC address
device = inventory.get_device("7f:14:22:72:00:e5")

# List devices with optional filters
devices = inventory.get_devices(
    vendor="Philips",
    device_class="Medical",
    page=0,
    page_size=50,
)

# Get device attributes
attrs = inventory.get_device_attributes("7f:14:22:72:00:e5")

# List excluded (unclassified) devices with optional filters
excluded_devices = inventory.get_excluded_devices(
    vendor="Philips",
    page=0,
    page_size=50,
)
```

### Utilization

```python
utilization = Utilization(client)

procedures = utilization.get_procedures(
    device_uuid="ffc20dfe-4c24-11ec-8a38-5eeeaabea551",
    completed_after="2024/01/01",
    page=0,
    page_size=100,
)
```

### Network

```python
network = Network(client)

subnets = network.get_subnets(vlan=477)
```

### Risk

```python
risk = Risk(client)

# Get vulnerabilities
vulns = risk.get_vulnerabilities(severity="CRITICAL", status="OPEN")

# Get mitigations for a specific CVE
mitigations = risk.get_mitigations(vulnerability="CVE-2017-2852")
```

### Threats

```python
threat = Threat(client)

threats = threat.get_threats(severity="HIGH", status="OPEN", page=0, page_size=50)
```

## Debugging

Set `DEBUG=1` to print request details to stderr (authorization headers are redacted):

