This SDK provides three live APIs for solar installation data:
✅ Working Features:
- Inverter Telemetry — query historical and stream live inverter data (5-min and daily resolution)
- OODA Terminal Alerts — query historical and stream live OODA fault/diagnostic alerts from terminal devices
- Battery Health & Warranty Tracking — monitor battery State of Health (SOH), capacity, and track warranty expiry via date or throughput limits
- Partner API — fetch pre-computed JSON snapshots (KPIs, maintenance signals, forecasts, and preventive-maintenance schedules) with sub-100ms response times via ETag caching
- Data Ingestion Validation (Python) — client-side ODSE record validation with 100% service parity for pre-upload checks
- Resumable streaming with cursor tokens for telemetry and alerts
- Built-in rate limiting and cost protection
🚧 Planned Features:
- Solar Energy Forecasting (Working)
- Energy Policy Analysis
- Edge Device Management (Working)
- Data Collection integrations
Contact support@asoba.org to get an API key.
JavaScript:
git clone https://github.com/AsobaCloud/sdk.git
cd sdk/javascript
npm installPython:
git clone https://github.com/AsobaCloud/sdk.git
cd sdk/python
pip3 install -e .Inverter Telemetry:
export INVERTER_TELEMETRY_ENDPOINT=https://af5jy5ob3e.execute-api.af-south-1.amazonaws.com/prod
export INVERTER_TELEMETRY_API_KEY=<your_api_key>OODA Terminal Alerts:
export OODA_TERMINAL_ENDPOINT=https://3lpq00xevg.execute-api.af-south-1.amazonaws.com/prod
export OODA_TERMINAL_API_KEY=<your_api_key>Partner API:
export PARTNER_API_ENDPOINT=https://8el3o25tc1.execute-api.af-south-1.amazonaws.com/prod
export PARTNER_API_KEY=<your_api_key>The same API key works for all endpoints — just set it in the respective variables.
JavaScript:
cd javascript
node examples/inverter-telemetry-example.js
node examples/ooda-terminal-example.js
node examples/partner-api-example.jsPython:
cd python
python3 examples/inverter_telemetry_example.py
python3 examples/ooda_terminal_example.py
python3 examples/partner_api_example.pyQuery and stream live power output, energy, temperature, and state data from solar inverters.
const { OnaSDK } = require('./src/index');
const sdk = new OnaSDK({
endpoints: {
inverterTelemetry: process.env.INVERTER_TELEMETRY_ENDPOINT,
},
inverterTelemetryApiKey: process.env.INVERTER_TELEMETRY_API_KEY,
});
// Query historical data
const records = await sdk.inverterTelemetry.getInverterTelemetry({
asset_id: 'INV-1000000054495190',
site_id: 'Sibaya',
time_range: { start: '2025-11-01T00:00:00', end: '2025-11-01T12:00:00' },
resolution: '5min',
limit: 100,
});
// Stream live data
for await (const record of sdk.inverterTelemetry.streamInverter({
asset_id: 'INV-1000000054495190',
site_id: 'Sibaya',
polling_interval: 30,
})) {
console.log(`${record.timestamp}: ${record.power} kW`);
}from ona_platform import OnaClient
client = OnaClient()
# Query historical data
records = client.inverter_telemetry.get_inverter_telemetry(
asset_id='INV-1000000054495190',
site_id='Sibaya',
time_range={'start': '2025-11-01T00:00:00', 'end': '2025-11-01T12:00:00'},
resolution='5min',
limit=100,
)
# Stream live data
for record in client.inverter_telemetry.stream_inverter(
asset_id='INV-1000000054495190',
site_id='Sibaya',
polling_interval=30,
):
print(f"{record.timestamp}: {record.power} kW")Query and stream OODA (Observe, Orient, Decide, Act) fault detection and diagnostic alerts from terminal devices.
const { OnaSDK } = require('./src/index');
const sdk = new OnaSDK({
endpoints: {
oodaTerminal: process.env.OODA_TERMINAL_ENDPOINT,
},
oodaTerminalApiKey: process.env.OODA_TERMINAL_API_KEY,
});
// Query historical alerts
const alerts = await sdk.oodaTerminal.getTerminalAlerts({
terminal_device_id: 'TERM-1000000054495190',
site_id: 'Sibaya',
time_range: { start: '2025-11-01T00:00:00', end: '2025-11-01T12:00:00' },
resolution: '5min',
limit: 100,
});
// Query all terminal devices at a site
const siteAlerts = await sdk.oodaTerminal.getSiteAlerts({
site_id: 'Sibaya',
time_range: { start: '2025-11-01T00:00:00', end: '2025-11-01T12:00:00' },
});
// Discover available data range
const period = await sdk.oodaTerminal.getDataPeriod({ site_id: 'Sibaya' });
console.log(`Data from ${period.first_record} to ${period.last_record}`);
// Stream live alerts
for await (const alert of sdk.oodaTerminal.streamTerminal({
terminal_device_id: 'TERM-1000000054495190',
site_id: 'Sibaya',
polling_interval: 30,
})) {
console.log(`${alert.timestamp}: [${alert.alert_severity}] ${alert.message}`);
}from ona_platform import OnaClient
from ona_platform.models.ooda import TimeRange
client = OnaClient()
# Query historical alerts
alerts = client.ooda_terminal.get_terminal_alerts(
terminal_device_id='TERM-1000000054495190',
site_id='Sibaya',
time_range=TimeRange(start='2025-11-01T00:00:00', end='2025-11-01T12:00:00'),
resolution='5min',
limit=100,
)
# Query all terminal devices at a site
site_alerts = client.ooda_terminal.get_site_alerts(
site_id='Sibaya',
time_range=TimeRange(start='2025-11-01T00:00:00', end='2025-11-01T12:00:00'),
)
# Discover available data range
period = client.ooda_terminal.get_data_period(site_id='Sibaya')
print(f"Data from {period.first_record} to {period.last_record}")
# Stream live alerts
for alert in client.ooda_terminal.stream_terminal(
terminal_device_id='TERM-1000000054495190',
site_id='Sibaya',
polling_interval=30,
):
print(f"{alert.timestamp}: [{alert.alert_severity}] {alert.message}")Trigger model training, detect data gaps, and manage edge devices directly via the SDK.
const results = await sdk.gapDetection.detectGaps({ customer_id: 'Sibaya' });
if (results.needs_backfill) {
console.log(`Missing intervals: ${results.total_missing_intervals}`);
}# Trigger a new training job
client.training.trigger_training(customer_id='Sibaya', promote=True)
# Check status
status = client.training.get_training_status(customer_id='Sibaya')
print(f"Training status: {status['status']}")Fetch pre-computed JSON snapshots for embedding and partner integrations. This API is optimized for speed using ETag-based conditional GETs and in-memory caching.
const { OnaSDK } = require('./src/index');
const sdk = new OnaSDK({
endpoints: {
partnerApi: process.env.PARTNER_API_ENDPOINT,
},
partnerApiKey: process.env.PARTNER_API_KEY,
});
// 1. KPI rollup (first call: full fetch; second call: returns cached if ETag matches)
const kpis = await sdk.partnerApi.getKpiRollup({ site_id: 'Sibaya' });
const cachedKpis = await sdk.partnerApi.getKpiRollup({ site_id: 'Sibaya' });
// 2. Maintenance signals (detected anomalies) — optional `since` and `severity` filters
const signals = await sdk.partnerApi.getMaintenanceSignals({
site_id: 'Sibaya',
since: '2025-11-01T00:00:00',
severity: 'high',
});
// 3. Forecast snapshot — pre-computed 24h solar forecast (optional `horizon`)
const forecast = await sdk.partnerApi.getForecastSnapshot({ site_id: 'Sibaya' });
console.log(`Forecast horizon: ${forecast.horizon_hours}h, intervals: ${forecast.intervals.length}`);
// 4. Maintenance schedule (90-day preventive tasks) — SEP-062
const schedule = await sdk.partnerApi.getMaintenanceSchedule({ site_id: 'Sibaya' });
console.log(`Tasks: ${schedule.summary.total_tasks}`);
for (const task of schedule.tasks) {
console.log(` ${task.recommended_date} — ${task.asset_id} — ${task.task_type} (${task.priority})`);
}from ona_platform import OnaClient
client = OnaClient()
# 1. KPI rollup (first call: full fetch; second call: returns cached if ETag matches)
kpis = client.partner_api.get_kpi_rollup(site_id='Sibaya')
cached_kpis = client.partner_api.get_kpi_rollup(site_id='Sibaya')
# 2. Maintenance signals (detected anomalies) — optional `since` and `severity` filters
signals = client.partner_api.get_maintenance_signals(
site_id='Sibaya',
since='2025-11-01T00:00:00',
severity='high',
)
# 3. Forecast snapshot — pre-computed 24h solar forecast (optional `horizon`)
forecast = client.partner_api.get_forecast_snapshot(site_id='Sibaya')
print(f"Forecast horizon: {forecast['horizon_hours']}h, intervals: {len(forecast['intervals'])}")
# 4. Maintenance schedule (90-day preventive tasks) — SEP-062
schedule = client.partner_api.get_maintenance_schedule(site_id='Sibaya')
print(f"Tasks: {schedule['summary']['total_tasks']}")
for task in schedule['tasks']:
print(f" {task['recommended_date']} — {task['asset_id']} — {task['task_type']} ({task['priority']})")Validate records locally against the ODSE schema before uploading to catch issues early.
from ona_platform import OnaClient
from ona_platform.models.odse import ODSE_REQUIRED_FIELDS, ODSE_ERROR_TYPES
client = OnaClient()
# Records to validate
records = [
{"timestamp": "2025-01-01T00:00:00Z", "kWh": 100.5, "error_type": "normal", "asset_id": "INV001"},
{"timestamp": "invalid-date", "kWh": "not-a-number", "error_type": "unknown"},
]
# Validate locally (no service call)
result = client.data_ingestion.validate_local_records(records)
print(f"Valid: {result['summary']['valid']}/{result['summary']['total']}")
# Access valid records for upload
for record in result['valid_records']:
print(f"Ready for upload: {record}")
# Review invalid records
for item in result['invalid_records']:
print(f"Errors: {item['errors']}")Validation checks include: required fields, allowed field whitelist, numeric bounds, timestamp format, and error type enum matching. This provides 100% parity with service-side validation.
The Terminal API provides advanced intelligence for asset health, soiling analysis, and battery warranty tracking.
Get high-level KPIs and automated analysis for a site.
const summary = await sdk.terminal.getSiteSummary({ site_id: 'Sibaya' });
console.log(`Fleet PR: ${summary.fleet_pr_pct}%`);
// Soiling Analysis
if (summary.soiling) {
console.log(`Soiling Rate: ${summary.soiling.soiling_rate_pct_day}%/day`);
console.log(`Last Wash Gain: ${summary.soiling.recovery_gain_kwh_last_event} kWh`);
}
// Asset Prognostics
if (summary.prognostics) {
console.log(`Health Score: ${summary.prognostics.health_score}/100`);
console.log(`Est. Retirement: ${summary.prognostics.battery_retirement_date}`);
}summary = client.terminal.get_site_summary(site_id='Sibaya')
print(f"Fleet PR: {summary['fleet_pr_pct']}%")
# Soiling Analysis
if 'soiling' in summary:
soiling = summary['soiling']
print(f"Soiling Rate: {soiling['soiling_rate_pct_day']}%/day")
# Battery Health (aggregated)
if 'battery' in summary:
bat = summary['battery']
print(f"Avg SOH: {bat['avg_soh']}%")Monitor individual battery assets and calculate remaining warranty life based on both date and throughput.
// 1. Get asset with warranty details
const asset = await sdk.terminal.getAsset({
customer_id: 'cust123',
asset_id: 'BAT-001'
});
// 2. Calculate remaining warranty life
const status = sdk.terminal.constructor.calculateRemainingWarrantyLife({
warranty_expiry_date: asset.warranty_expiry_date,
warranty_throughput_kwh: asset.warranty_throughput_kwh,
current_throughput_kwh: 5420.5 // From telemetry
});
console.log(`Warranty Status: ${status.warranty_status}`);
console.log(`Limiting Factor: ${status.limiting_factor}`);# Calculate remaining warranty life
status = client.terminal.calculate_remaining_warranty_life(
warranty_expiry_date='2030-12-31',
warranty_throughput_kwh=10000.0,
current_throughput_kwh=8500.0
)
print(f"Warranty Status: {status['warranty_status']}")
print(f"Throughput Remaining: {status['throughput_remaining_pct']}%")| Method | Description |
|---|---|
getInverterTelemetry / get_inverter_telemetry |
Historical data for a single inverter |
getSiteTelemetry / get_site_telemetry |
Historical data for all inverters at a site |
getDataPeriod / get_data_period |
Discover available data time range |
streamInverter / stream_inverter |
Stream live data from a single inverter |
streamSite / stream_site |
Stream live data from all inverters at a site |
| Method | Description |
|---|---|
getTerminalAlerts / get_terminal_alerts |
Historical alerts for a single terminal device |
getSiteAlerts / get_site_alerts |
Historical alerts for all terminal devices at a site |
getAsset / get_asset |
Get asset details (including battery capacity and warranty) |
getSiteSummary / get_site_summary |
Get site summary with battery health KPIs (SOH, SOC) |
getDataPeriod / get_data_period |
Discover available alert time range |
streamTerminal / stream_terminal |
Stream live alerts from a single terminal device |
streamSite / stream_site |
Stream live alerts from all terminal devices at a site |
Trigger model training, detect data gaps, and manage edge devices directly via the SDK.
const results = await sdk.gapDetection.detectGaps({ customer_id: 'Sibaya' });
if (results.needs_backfill) {
console.log(`Missing intervals: ${results.total_missing_intervals}`);
}# Trigger a new training job
client.training.trigger_training(customer_id='Sibaya', promote=True)
# Check status
status = client.training.get_training_status(customer_id='Sibaya')
print(f"Training status: {status['status']}")| Method | Description |
|---|---|
getKpiRollup / get_kpi_rollup |
Site-level KPI summary snapshot |
getMaintenanceSignals / get_maintenance_signals |
Pending maintenance and health signals |
getForecastSnapshot / get_forecast_snapshot |
Pre-computed solar forecast snapshot |
getMaintenanceSchedule / get_maintenance_schedule |
Preventive-maintenance task list for the next 90 days (per inverter) |
getSnapshot / get_snapshot |
Generic snapshot fetch by kind |
| Parameter | Description |
|---|---|
resolution |
"5min" (default) or "daily" |
limit |
Max records per query (default 100, max 1000) |
cursor |
Resume pagination from a previous position |
polling_interval |
Seconds between polls for streaming (min 5, default 5) |
- 60 requests per minute per API key
- Max 1000 records per query
- Max 31-day time range per query
- Min 5-second polling interval for streaming
| API | Endpoint |
|---|---|
| Inverter Telemetry | https://af5jy5ob3e.execute-api.af-south-1.amazonaws.com/prod |
| OODA Terminal Alerts | https://3lpq00xevg.execute-api.af-south-1.amazonaws.com/prod |
| Partner API | https://8el3o25tc1.execute-api.af-south-1.amazonaws.com/prod |
| Error | Cause | Solution |
|---|---|---|
401 Unauthorized |
Invalid/missing API key | Check your API key with support@asoba.org |
403 Forbidden |
API key not scoped to site | Request access to the site_id you're querying |
429 Too Many Requests |
Rate limit exceeded | Wait and retry (60 req/min limit) |
ValidationError |
Invalid parameters | Check time ranges, limits, and required fields |
ConfigurationError |
Missing endpoint or API key | Verify environment variables are set |
Debug Steps:
- Verify all three env vars are set to the same API key:
echo $INVERTER_TELEMETRY_API_KEY/echo $OODA_TERMINAL_API_KEY/echo $PARTNER_API_KEY(a single key value, exported under three names — see Quick Start §3) - Run the provided examples first — they test the full flow
- Ensure you're querying a valid
site_id(trySibayafor testing)
sdk/
├── javascript/
│ ├── src/services/InverterTelemetryClient.js
│ ├── src/services/OodaTerminalClient.js
│ ├── src/services/PartnerApiClient.js
│ ├── src/types/index.d.ts
│ ├── examples/inverter-telemetry-example.js
│ ├── examples/ooda-terminal-example.js
│ ├── examples/partner-api-example.js
│ └── tests/
│ ├── inverterTelemetry.test.js
│ └── partnerApi.test.js
├── python/
│ ├── ona_platform/services/inverter_telemetry.py
│ ├── ona_platform/services/ooda_terminal.py
│ ├── ona_platform/services/partner_api.py
│ ├── ona_platform/services/data_ingestion.py
│ ├── ona_platform/utils/validation.py
│ ├── ona_platform/models/odse.py
│ ├── examples/inverter_telemetry_example.py
│ ├── examples/ooda_terminal_example.py
│ ├── examples/partner_api_example.py
│ └── tests/
│ ├── test_client.py
│ ├── test_inverter_telemetry_client.py
│ ├── test_partner_api_client.py
│ └── test_validation.py
└── backend/
├── inverter_telemetry_api/
├── ooda_terminal_api/
└── partner_api/
Need an API Key? Contact support@asoba.org with your use case.
Issues? Open one at https://github.com/AsobaCloud/sdk/issues
Email: support@asoba.org
MIT License