> For the complete documentation index, see [llms.txt](https://docs.plenit.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.plenit.com/docs-en/referencia/cliente-sdk/cliente-sdk-python.md).

# Python SDK Client

This section details the Jotelulu Python SDK client for simplified access to the Jotelulu Public API.

### 📖 API Documentation

#### Main Endpoints

| Service             | Endpoint                                              | Description              |
| ------------------- | ----------------------------------------------------- | ------------------------ |
| **Core**            | `/core/v1/organizations`                              | Organization management  |
| **Core**            | `/core/v1/organizations/{id}/users`                   | User management          |
| **Servers**         | `/servers/v1/subscriptions/{id}/instances`            | Server instances         |
| **Servers**         | `/servers/v1/subscriptions/{id}/networks`             | Network management       |
| **Remote Desktops** | `/remote-desktops/v1/subscriptions/{id}/instances`    | Remote desktop instances |
| **Remote Desktops** | `/remote-desktops/v1/subscriptions/{id}/applications` | Applications             |

#### Main Models

* **Organization**: Represents an organization
* **User**: System user
* **Subscription**: Service subscription
* **Instance**: Server or remote desktop instance
* **Application**: Installable application
* **NetworkInterface**: Network interface
* **FirewallInboundRule**: Firewall rule

#### 🚀 Installation in a Virtual Environment (Recommended)

```bash
# Create virtual environment
python -m venv jotelulu-env

# Activate virtual environment
# On Windows:
jotelulu-env\Scripts\activate
# On Linux/macOS:
source jotelulu-env/bin/activate

# Install the SDK
pip install generated-client-python
```

### 🔐 Authentication with JWT

#### Basic Configuration

```python
import openapi_client
from openapi_client.configuration import Configuration
from openapi_client.api_client import ApiClient

# Configure the client with JWT
configuration = Configuration(
    host="https://connect-eu1.jotelulu.com",  # Production
    access_token="your_jwt_token_here"
)

# Create API client
api_client = ApiClient(configuration)
```

#### Environment Configuration

```python
# Available environments
ENVIRONMENTS = {
    'production': 'https://connect-eu1.jotelulu.com'
}

# Configuration by environment
def create_jotelulu_client(environment='production', token=None):
    configuration = Configuration(
        host=ENVIRONMENTS[environment],
        access_token=token
    )
    return ApiClient(configuration)
```

#### Secure Token Handling

```python
import os
from openapi_client import Configuration, ApiClient

class JoteluluClient:
    def __init__(self, environment='production'):
        # Get token from environment variables
        token = os.getenv('JOTELULU_JWT_TOKEN')
        if not token:
            raise ValueError("JOTELULU_JWT_TOKEN environment variable is required")
        
        self.configuration = Configuration(
            host=ENVIRONMENTS.get(environment, ENVIRONMENTS['production']),
            access_token=token
        )
        self.api_client = ApiClient(self.configuration)
    
    def get_organizations_api(self):
        from openapi_client.api.organizations_api import OrganizationsApi
        return OrganizationsApi(self.api_client)
    
    def get_instances_api(self):
        from openapi_client.api.instances_api import InstancesApi
        return InstancesApi(self.api_client)
```

### 🐍 Django Integration

#### 1. Installation in Django Project

```bash
# Create new Django project
django-admin startproject mi_proyecto_jotelulu
cd mi_proyecto_jotelulu

# Create application
python manage.py startapp jotelulu_integration

# Install dependencies
pip install django
pip install generated-client-python
```

#### 2. Configuration in settings.py

```python
# settings.py
import os

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'jotelulu_integration',  # Your application
]

# Jotelulu configuration
JOTELULU_SETTINGS = {
    'ENVIRONMENT': os.getenv('JOTELULU_ENV', 'production'),
    'JWT_TOKEN': os.getenv('JOTELULU_JWT_TOKEN'),
    'API_ENDPOINTS': {
        'production': 'https://connect-eu1.jotelulu.com'
    }
}
```

#### 3. Django service for Jotelulu

```python
# jotelulu_integration/services.py
from django.conf import settings
import openapi_client
from openapi_client.api.organizations_api import OrganizationsApi
from openapi_client.api.instances_api import InstancesApi
from openapi_client.api.applications_api import ApplicationsApi
from openapi_client.exceptions import ApiException

class JoteluluService:
    def __init__(self):
        jotelulu_config = settings.JOTELULU_SETTINGS
        environment = jotelulu_config['ENVIRONMENT']
        
        self.configuration = openapi_client.Configuration(
            host=jotelulu_config['API_ENDPOINTS'][environment],
            access_token=jotelulu_config['JWT_TOKEN']
        )
        self.api_client = openapi_client.ApiClient(self.configuration)
    
    def get_user_organizations(self):
        """Get the current user's organizations"""
        try:
            api_instance = OrganizationsApi(self.api_client)
            response = api_instance.list_user_organizations()
            return response.data
        except ApiException as e:
            print(f"Error getting organizations: {e}")
            return []
    
    def get_organization_users(self, organization_id):
        """Get users from an organization"""
        try:
            api_instance = OrganizationsApi(self.api_client)
            response = api_instance.list_organization_users(organization_id)
            return response.data
        except ApiException as e:
            print(f"Error getting users: {e}")
            return []
    
    def get_remote_desktop_instances(self, subscription_id):
        """Get remote desktop instances"""
        try:
            api_instance = InstancesApi(self.api_client)
            response = api_instance.list_remote_desktop_subscription_instances(subscription_id)
            return response.data
        except ApiException as e:
            print(f"Error getting instances: {e}")
            return []
```

#### 4. Django Views

```python
# jotelulu_integration/views.py
from django.shortcuts import render
from django.http import JsonResponse
from django.views.decorators.csrf import csrf_exempt
from .services import JoteluluService

def dashboard(request):
    """Main dashboard view"""
    jotelulu = JoteluluService()
    organizations = jotelulu.get_user_organizations()
    
    context = {
        'organizations': organizations,
        'user': request.user
    }
    return render(request, 'jotelulu_integration/dashboard.html', context)

def organization_detail(request, organization_id):
    """Organization details"""
    jotelulu = JoteluluService()
    users = jotelulu.get_organization_users(organization_id)
    
    return JsonResponse({
        'organization_id': organization_id,
        'users': [{'id': user.id, 'name': user.name, 'email': user.email} for user in users]
    })

@csrf_exempt
def create_organization_user(request, organization_id):
    """Create user in organization"""
    if request.method == 'POST':
        import json
        from openapi_client.model.create_organization_user_request import CreateOrganizationUserRequest
        from openapi_client.model.create_organization_user_request_data import CreateOrganizationUserRequestData
        
        data = json.loads(request.body)
        jotelulu = JoteluluService()
        
        try:
            api_instance = jotelulu.get_organizations_api()
            user_request = CreateOrganizationUserRequest(
                data=CreateOrganizationUserRequestData(
                    email=data['email'],
                    name=data['name'],
                    last_name=data['lastName'],
                    password=data.get('password')
                )
            )
            
            response = api_instance.create_organization_user(organization_id, user_request)
            return JsonResponse({'success': True, 'user_id': response.data.user_id})
            
        except Exception as e:
            return JsonResponse({'success': False, 'error': str(e)})
    
    return JsonResponse({'error': 'Method not allowed'}, status=405)
```

#### 5. Django URLs

```python
# jotelulu_integration/urls.py
from django.urls import path
from . import views

app_name = 'jotelulu_integration'

urlpatterns = [
    path('', views.dashboard, name='dashboard'),
    path('organization/<str:organization_id>/', views.organization_detail, name='organization_detail'),
    path('organization/<str:organization_id>/users/', views.create_organization_user, name='create_user'),
]

# Main project urls.py
from django.contrib import admin
from django.urls import path, include

urlpatterns = [
    path('admin/', admin.site.urls),
    path('jotelulu/', include('jotelulu_integration.urls')),
]
```

### 📚 Usage Examples

#### Organization Management

```python
from openapi_client.api.organizations_api import OrganizationsApi
from openapi_client.model.create_organization_user_request import CreateOrganizationUserRequest

# Initialize client
client = JoteluluClient()
org_api = client.get_organizations_api()

# List user organizations
organizations = org_api.list_user_organizations()
print(f"Organizations found: {len(organizations.data)}")

# List organization users
org_id = "ORG-123"
users = org_api.list_organization_users(org_id)
for user in users.data:
    print(f"User: {user.name} ({user.email})")

# Create new user
new_user_request = CreateOrganizationUserRequest(
    data={
        'email': 'nuevo@ejemplo.com',
        'name': 'Nuevo',
        'lastName': 'Usuario',
        'password': 'password123',
        'twoFactorAuthRequired': True
    }
)
result = org_api.create_organization_user(org_id, new_user_request)
print(f"User created with ID: {result.data.user_id}")
```

#### Management of Instances and Servers

```python
from openapi_client.api.instances_api import InstancesApi
from openapi_client.api.instance_api import InstanceApi

# Instance APIs
instances_api = InstancesApi(api_client)
instance_api = InstanceApi(api_client)

# List remote desktop instances
subscription_id = "SUB-456"
rd_instances = instances_api.list_remote_desktop_subscription_instances(subscription_id)

for instance in rd_instances.data:
    print(f"Instance: {instance.name} - State: {instance.state}")
    
    # Get deployment progress
    if instance.state == 'creating':
        progress = instance_api.get_instance_progress(subscription_id, instance.id)
        print(f"Progress: {progress.data.progress}")

# List server instances
server_instances = instance_api.list_server_subscription_instances(subscription_id)
print(f"Server instances: {len(server_instances.data)}")
```

#### Application Management

```python
from openapi_client.api.applications_api import ApplicationsApi
from openapi_client.model.create_instance_application_request import CreateInstanceApplicationRequest

apps_api = ApplicationsApi(api_client)

# List available applications
available_apps = apps_api.list_applications()
print("Available applications:")
for app in available_apps.data:
    print(f"- {app.name} (ID: {app.id})")

# List subscription applications
subscription_apps = apps_api.list_subscription_applications(subscription_id)
print(f"Installed applications: {len(subscription_apps.data)}")

# Install new application
install_request = CreateInstanceApplicationRequest(
    data={
        'instanceId': 'INST-789',
        'applicationId': 'APP-123'
    }
)
result = apps_api.create_instance_application(subscription_id, install_request)
print(f"Application installed: {result.data[0].id}")
```

#### Network and Firewall Management

```python
from openapi_client.api.networks_api import NetworksApi
from openapi_client.api.firewall_api import FirewallApi
from openapi_client.model.create_isolated_network_request import CreateIsolatedNetworkRequest
from openapi_client.model.create_firewall_inbound_rule_request import CreateFirewallInboundRuleRequest

networks_api = NetworksApi(api_client)
firewall_api = FirewallApi(api_client)

# Create isolated network
network_request = CreateIsolatedNetworkRequest(
    data={
        'name': 'My Private Network',
        'network': '192.168.100.0',
        'netmask': '255.255.255.0',
        'gateway': '192.168.100.1'
    }
)
network = networks_api.create_isolated_network(subscription_id, network_request)
print(f"Network created: {network.data.name}")

# Create firewall rule
firewall_rule = CreateFirewallInboundRuleRequest(
    data={
        'protocol': 'TCP',
        'publicNetworkId': 'NET-456',
        'privateNicAddressIpId': 'NIC-789',
        'publicPort': 80,
        'privatePort': 8080,
        'origin': '0.0.0.0/0'
    }
)
rule = firewall_api.create_firewall_inbound_rule(
    subscription_id, 
    network.data.main_network_id, 
    firewall_rule
)
print(f"Firewall rule created: {rule.data.id}")
```

### 🛠️ Error Handling

```python
from openapi_client.exceptions import ApiException
import logging

# Configure logging
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)

def safe_api_call(api_function, *args, **kwargs):
    """Wrapper for safe API calls"""
    try:
        return api_function(*args, **kwargs)
    except ApiException as e:
        logger.error(f"API error: {e.status} - {e.reason}")
        logger.error(f"Response body: {e.body}")
        
        # Specific handling by error code
        if e.status == 401:
            logger.error("Invalid or expired JWT token")
        elif e.status == 403:
            logger.error("Insufficient permissions")
        elif e.status == 404:
            logger.error("Resource not found")
        elif e.status >= 500:
            logger.error("Internal server error")
        
        return None
    except Exception as e:
        logger.error(f"Unexpected error: {str(e)}")
        return None

# Usage example
organizations = safe_api_call(org_api.list_user_organizations)
if organizations:
    print(f"Organizations retrieved: {len(organizations.data)}")
else:
    print("Could not obtain the organizations")
```

### 🔧 Advanced Configuration

#### Custom Client with Retry

```python
import time
from functools import wraps

def retry_on_failure(max_retries=3, delay=1):
    """Decorator to retry failed calls"""
    def decorator(func):
        @wraps(func)
        def wrapper(*args, **kwargs):
            for attempt in range(max_retries):
                try:
                    return func(*args, **kwargs)
                except ApiException as e:
                    if e.status >= 500 and attempt < max_retries - 1:
                        time.sleep(delay * (2 ** attempt))  # Exponential backoff
                        continue
                    raise
            return None
        return wrapper
    return decorator

class RobustJoteluluClient(JoteluluClient):
    @retry_on_failure(max_retries=3)
    def get_organizations_with_retry(self):
        api = self.get_organizations_api()
        return api.list_user_organizations()
```

#### Timeout Configuration

```python
from openapi_client.configuration import Configuration
from openapi_client.api_client import ApiClient

# Configuration with custom timeouts
configuration = Configuration(
    host="https://connect-eu1.jotelulu.com",
    access_token="your_token"
)

# Set up timeouts
api_client = ApiClient(configuration)
api_client.rest_client.pool_manager.connection_pool_kw['timeout'] = 30
```

***


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://docs.plenit.com/docs-en/referencia/cliente-sdk/cliente-sdk-python.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
