hfortix-forticare 0.5.162
FortiCare Asset Management API Client
License: MIT
Requires Python: >=3.9
Keywords: fortinet, forticare, api, client, asset-management

# hfortix-forticare

Python SDK for Fortinet FortiCare Asset Management V3 API.

## Overview

`hfortix-forticare` provides a fully typed Python interface to the FortiCare Asset Management API, enabling programmatic access to:

- **Product Management**: Register, list, update, and decommission products
- **License Management**: Register, list, and download licenses  
- **Contract Management**: View contract information
- **Folder Management**: Organize assets into folders
- **Service Management**: Register subscription services

## Features

✨ **Fully Typed** - Complete type hints with TypedDict for request/response structures
📝 **Auto-completion** - IDE auto-completion for all parameters and return values
🔒 **OAuth 2.0** - Built-in OAuth Bearer token authentication
🚀 **Modern HTTP** - HTTP/2 support via httpx
🎯 **Explicit Parameters** - All API parameters exposed as function arguments
📚 **Rich Documentation** - Comprehensive docstrings from Swagger specs

## Installation

```bash
pip install hfortix-forticare
```

## Quick Start

### 1. Authentication

You have multiple options for authentication:

#### Option A: CloudSession (Recommended for Multi-Service)

```python
from hfortix_core.session import CloudSession
from hfortix_forticare import FortiCare

# CloudSession manages tokens for multiple services
with CloudSession(api_id="your_api_id", password="your_password") as session:
    fc = FortiCare(session=session)  # Auto-uses "assetmanagement" client_id
    products = fc.api.products.list.post()
    # Token is automatically managed and shared efficiently
```

**NEW: CloudSession with Global Rate Limiting**

```python
from hfortix_core.session import CloudSession
from hfortix_forticare import FortiCare
from hfortix_fortiztp import FortiZTP

# Configure rate limiting once at session level
session = CloudSession(
    api_id="your_api_id",
    password="your_password",
    # Global rate limiting - inherited by all clients
    rate_limit=True,
    rate_limit_max_requests=20,       # 20 requests per minute
    rate_limit_window_seconds=60.0,
    circuit_breaker=True,
)

# Both clients inherit session's rate limiting
fc = FortiCare(session=session)    # Inherits 20 req/min
fz = FortiZTP(session=session)     # Inherits 20 req/min

# Or override for specific client
fc_fast = FortiCare(
    session=session,
    rate_limit_max_requests=50,    # Override session's 20 req/min
)
```

#### Option B: Auto-Login with Credentials

```python
from hfortix_forticare import FortiCare

# Auto-login with API credentials
fcc = FortiCare(
    api_id="your_api_id",
    password="your_password",
    client_id="assetmanagement"
)
# Token is automatically obtained and managed
```

#### Option C: Use Pre-Obtained OAuth Token

```python
# Get token from FortiAuthenticator OAuth endpoint first
# https://customerapiauth.fortinet.com/api/v1/oauth/token

fcc = FortiCare(oauth_token="your_oauth_token_here")
```

### 2. Use the API

```python
# List products with entitlements
products = fcc.api.products.list.post(
    serial_number="FGT*",
    status="Registered"
)

# Get product details
details = fcc.api.products.details.post(
    serial_number="FGT90D1234567890"
)

# Register a product
result = fcc.api.products.register.post(
    registration_units=[
        {
            "serialNumber": "FGT90D1234567890",
            "description": "My FortiGate",
            # ... other fields
        }
    ]
)

# List contracts
contracts = fcc.api.contracts.list.post()

# List licenses  
licenses = fcc.api.licenses.list.post()

# Manage folders
folders = fcc.api.folders.list.post()

# Always clean up when done
fcc.logout()
```

## API Structure

The SDK mirrors the FortiCare Asset Management V3 API structure:

```
fcc.api.
  ├── products.
  │   ├── list          - List products with entitlements
  │   ├── register      - Register products and contracts
  │   ├── details       - Get product details
  │   ├── description   - Update product description
  │   ├── location      - Update product location
  │   ├── folder        - Update product folder
  │   ├── decommission  - Decommission products
  │   └── transfer      - Transfer products between accounts
  ├── licenses.
  │   ├── list          - List licenses
  │   ├── register      - Register a license
  │   └── download      - Download VM license file
  ├── contracts.
  │   └── list          - List contracts
  ├── folders.
  │   ├── list          - List asset folders
  │   ├── create        - Create new folder
  │   └── delete        - Delete folder
  └── services.
      └── register      - Register subscription service
```

## Type Safety

All endpoints use TypedDict for request and response structures:

```python
# IDE will show all available parameters
products = fcc.api.products.list.post(
    serial_number="FGT*",     # str
    expire_before="2024...",   # str (ISO 8601)
    status="Registered",       # str
    product_model="...",       # str  
    account_id=12345          # float
)

# Response is also typed
assert products['status'] == 0  # int
assert isinstance(products['message'], str)
assert isinstance(products['assets'], list)
```

## Rate Limits

FortiCare Asset Management API enforces the following limits:

- **100 calls per minute**
- **1000 calls per hour**
- **10 errors per hour**
- **Batch operations**: Max 10 units, max 5 errors per batch

### Rate Limit Enforcement (NEW)

**NEW in 0.5.162+**: Enforce rate limits using token bucket algorithm with queue support:

```python
from hfortix_forticare import FortiCare

# Option 1: Direct client with rate limiting
fcc = FortiCare(
    api_id="your_api_id",
    password="your_password",
    rate_limit=True,
    rate_limit_max_requests=100,      # FortiCare: 100/min
    rate_limit_window_seconds=60.0,   # 60 second window
    rate_limit_strategy="queue",      # Queue excess requests
    rate_limit_queue_size=50,         # Max 50 queued requests
)

# Requests automatically queued when limit exceeded
for i in range(150):
    products = fcc.api.products.list.post()  # First 100 immediate, next 50 queued

# Option 2: CloudSession with global rate limiting (recommended)
from hfortix_core.session import CloudSession

session = CloudSession(
    api_id="your_api_id",
    password="your_password",
    rate_limit=True,
    rate_limit_max_requests=100,
    rate_limit_window_seconds=60.0,
)

# FortiCare inherits session's rate limiting
fcc = FortiCare(session=session)
```

**Rate Limiting Parameters:**
- `rate_limit: bool = False` - Enable/disable rate limiting
- `rate_limit_strategy: str = "queue"` - Strategy: `queue`, `drop`, or `raise`
- `rate_limit_max_requests: int = 100` - Max requests per window
- `rate_limit_window_seconds: float = 60.0` - Time window in seconds
- `rate_limit_queue_size: int = 100` - Max queued requests
- `rate_limit_queue_timeout: float = 30.0` - Max wait time in queue
- `rate_limit_queue_overflow: str = "block"` - Overflow behavior: `block`, `drop`, or `raise`
- `circuit_breaker: bool = False` - Enable circuit breaker
- `circuit_breaker_threshold: int = 5` - Failures before opening
- `circuit_breaker_timeout: float = 60.0` - Seconds before retry
- `circuit_breaker_half_open_calls: int = 3` - Test calls in half-open state

### Rate Limit Tracking

Track your API usage to stay within limits (tracking only, no enforcement):

```python
from hfortix_forticare import FortiCare

# Configure custom limits (all optional, defaults to None)
fcc = FortiCare(
    api_id="...",
    password="...",
    rate_limit_calls_per_min=100,      # FortiCare: 100/min
    rate_limit_calls_per_hour=1000,    # FortiCare: 1000/hour
    rate_limit_errors_per_hour=10      # FortiCare: 10 errors/hour
)

# Make API calls
products = fcc.api.products.list.post()

# Check rate limit status
status = fcc.get_rate_limit_status()
print(f"Calls last min: {status['calls_last_min']}/{status['limits']['calls_per_min']}")
print(f"Calls last 5min: {status['calls_last_5min']}")
print(f"Calls last hour: {status['calls_last_hour']}/{status['limits']['calls_per_hour']}")
print(f"Errors last hour: {status['errors_last_hour']}/{status['limits']['errors_per_hour']}")
print(f"Within limits: {status['within_limits']}")
```

**Note**: Rate limiting counters are **informational only** and do not enforce limits. They help you monitor usage and implement your own rate limiting logic.

## Error Handling

```python
try:
    result = fcc.api.products.list.post(serial_number="FGT*")
    
    if result['status'] == 0:
        print("Success!")
    else:
        print(f"API Error: {result['message']}")
        
except httpx.HTTPStatusError as e:
    print(f"HTTP Error: {e.response.status_code}")
except httpx.TimeoutException:
    print("Request timed out")
except httpx.RequestError as e:
    print(f"Network error: {e}")
```

## Context Manager

Use as a context manager for automatic cleanup:

```python
with FortiCare(oauth_token="...") as fcc:
    products = fcc.api.products.list.post(...)
    # ... use the client ...
# Automatically logged out
```

## Configuration

```python
fcc = FortiCare(
    oauth_token="your_token",
    base_url="https://support.fortinet.com",  # Default
    verify=True,                               # SSL verification
    max_retries=3,                            # Retry attempts
    connect_timeout=10.0,                     # Connection timeout (seconds)
    read_timeout=300.0                        # Read timeout (seconds)
)
```

## OAuth Token Management

The SDK provides comprehensive OAuth token lifecycle management for long-running applications:

### View Login Response

```python
# See the full OAuth response
fcc = FortiCare(api_id="...", password="...")
response = fcc.login(force_refresh=True)

print(f"Access Token: {response['access_token']}")
print(f"Refresh Token: {response['refresh_token']}")
print(f"Expires In: {response['expires_in']} seconds")
```

### Refresh Token

```python
# Efficiently refresh token without re-entering credentials
# (faster than full login)
refresh_response = fcc.refresh_token()
print(f"New token expires in: {refresh_response['expires_in']}s")
```

### Track Token Expiration

```python
# Check time remaining
remaining = fcc.get_token_time_remaining()
print(f"Token valid for {remaining} more seconds")

# Check if expired or expiring soon
if fcc.is_token_expired(buffer_seconds=300):  # 5 min buffer
    print("Token expiring soon, refreshing...")
    fcc.refresh_token()

# Get comprehensive token info
info = fcc.get_token_info()
print(f"Created: {info['created_at_iso']}")
print(f"Expires: {info['expires_at_iso']}")
print(f"Time Remaining: {info['time_remaining']}s")
print(f"Expires Soon: {info['expires_soon']}")
```

### Session Manager Pattern

For long-running applications, implement automatic token refresh:

```python
import time
from threading import Thread

def keep_session_alive(fcc: FortiCare):
    """Background task to maintain valid token"""
    while True:
        if fcc.is_token_expired(buffer_seconds=300):  # 5 min buffer
            try:
                fcc.refresh_token()
                print("Token refreshed successfully")
            except Exception as e:
                print(f"Refresh failed, re-logging in: {e}")
                fcc.login(force_refresh=True)
        time.sleep(60)  # Check every minute

# Start background thread
fcc = FortiCare(api_id="...", password="...")
Thread(target=keep_session_alive, args=(fcc,), daemon=True).start()

# Your application continues...
while True:
    folders = fcc.api.folders.list.post()
    # ... process data ...
    time.sleep(300)
```

## Examples

See [EXAMPLES.py](./EXAMPLES.py) for more comprehensive examples.

## Development

### Regenerating the SDK

If Fortinet updates the API, regenerate the SDK:

```bash
cd packages/forticare/dev/generator
python generate.py --clean
```

This will:
1. Parse updated Swagger JSON files
2. Generate all endpoint classes with type hints
3. Create .pyi stub files for IDE support
4. Generate category and main __init__.py files

### Project Structure

```
packages/forticare/
├── src/
│   └── hfortix_forticare/
│       ├── __init__.py           # Main FortiCare client
│       └── api/
│           └── v3/
│               ├── __init__.py   # V3API class
│               ├── products/     # Product endpoints
│               ├── licenses/     # License endpoints
│               ├── contracts/    # Contract endpoints
│               ├── folders/      # Folder endpoints
│               └── services/     # Service endpoints
├── dev/
│   └── generator/
│       ├── generate.py           # Code generator
│       ├── parsers/              # Swagger parsers
│       ├── generators/           # Code generators
│       ├── templates/            # Code templates
│       └── swagger/              # Swagger JSON files
└── README.md
```

## Requirements

- Python 3.10+
- httpx
- hfortix-core

## License

Copyright © 2024 Fortinet, Inc.

## Links

- [FortiCare API Documentation](http://docs.fortinet.com/document/forticloud/latest/asset-management-api/)
- [OAuth Token Guide](http://docs.fortinet.com/document/fortiauthenticator/6.1.2/rest-api-solution-guide/498666/oauth-server-token-oauth-token)
- [IAM Portal](https://support.fortinet.com/)