Files
solaredge-optimizers/packages/solaredgeapi/docs/internal-optimizer-api.md
T

173 lines
4.8 KiB
Markdown

# SolarEdge internal optimizer API
> Status: experimental and unsupported. This document describes calls observed
> in the SolarEdge Monitoring web portal on 2026-08-09. SolarEdge can change or
> remove them without notice.
## Why a second client is required
The official API at `https://monitoringapi.solaredge.com` does not expose
per-optimizer measurements. Its API key is rejected by the internal portal
endpoints with HTTP 401.
The Monitoring portal instead uses an OAuth2 authorization-code flow with PKCE:
1. Load the hosted SolarEdge/AWS Cognito login form.
2. Submit the configured portal username and password together with the CSRF
token and login cookies.
3. Exchange the returned authorization code for OAuth tokens.
4. Exchange those tokens at `/services/auth/token?legacy=false` for a Monitoring
portal session.
5. Send both the bearer access token and session cookies to internal services.
6. Refresh the session through `POST /services/auth/refresh` when necessary.
`SolarEdgePortalClient` performs this flow with HTTP requests and a memory-only
cookie jar. It does not start or depend on a browser. Tokens and cookies are not
persisted to disk.
## Configuration
Keep all values on the server:
```env
SOLAREDGE_SITE_ID=...
SOLAREDGE_USERNAME=owner@example.com
SOLAREDGE_PASSWORD=...
```
Do not use the `EXPO_PUBLIC_` prefix. Values with that prefix are embedded in
the client application and are not secret.
```ts
import { createSolarEdgePortalClientFromEnv } from "@solar-dash/solaredgeapi";
const portal = createSolarEdgePortalClientFromEnv();
await portal.authenticate();
```
## Observed endpoints
### Logical layout including optimizers
```http
GET /services/layout/logical/generic/v2/site/{siteId}?include-optimizers=true
```
```ts
const layout = await portal.getLogicalLayout();
const optimizerSerials = await portal.listOptimizerSerials();
const optimizerMappings = await portal.listOptimizerMappings();
```
The response contains the site's logical device tree and optimizer serials.
`listOptimizerSerials()` traverses that tree and returns only nodes whose type
is `OPTIMIZER`. The raw layout is deliberately typed as a generic object because
this undocumented structure is more likely to change.
`listOptimizerMappings()` preserves SolarEdge's logical position fields:
```ts
type SolarEdgeOptimizerMapping = {
serial: string;
inverterId?: string; // e.g. "1"
stringId?: string; // e.g. "1.1"
optimizerId?: string; // e.g. "1.1.16"
inverterOrder?: number;
stringOrder?: number;
optimizerOrder?: number;
};
```
The IDs come from the portal's `displayOrder` properties and are therefore
preferable to deriving positions from array indexes.
### Optimizer information
```http
POST /services/layout/information/optimizers
Content-Type: application/json
["OPTIMIZER_SERIAL_1", "OPTIMIZER_SERIAL_2"]
```
Observed response container:
```ts
type SolarEdgeOptimizerInformation = {
basicInformationList: Array<{
serial: string;
type_?: string;
model?: string;
bundleType?: string;
multiOptimizers?: boolean;
modules?: Array<{
orientation?: string;
tilt?: number;
azimuth?: number;
manufacturer?: string;
model?: string;
}>;
[property: string]: unknown;
}>;
serialToLiveData: Record<string, {
lastMeasurement?: string;
current_A?: number | null;
optimizerVoltage_V?: number | null;
power_W?: number | null;
voltage_V?: number | null;
total_last_telemetry_energy_WH?: number | null;
[property: string]: unknown;
}>;
};
```
```ts
const information = await portal.getOptimizerInformation([
"OPTIMIZER_SERIAL_1",
"OPTIMIZER_SERIAL_2",
]);
```
### Optimizer energy graph
```http
GET /services/layout/energy-graph/site/{siteId}/optimizers
?chart-time-unit=hours
&start-date=yyyy-MM-dd
&end-date=yyyy-MM-dd
&optimizer-serials=SERIAL_1,SERIAL_2
```
Observed response:
```ts
type SolarEdgeOptimizerEnergy = {
totalEnergy: number;
energyBars: Array<{
measurementTime: string;
energy: number | null;
}>;
};
```
```ts
const energy = await portal.getOptimizerEnergy({
startDate: "2026-08-09",
endDate: "2026-08-09",
optimizerSerials: ["OPTIMIZER_SERIAL_1"],
chartTimeUnit: "hours",
});
```
## Limitations and operational safety
- The implementation currently supports username/password login without an
additional MFA challenge. It throws `AUTH_CHALLENGE_REQUIRED` if SolarEdge
asks for an OTP, authenticator, passkey, or another step.
- Credentials remain in process memory because they are needed for a fresh
login after session expiry. Do not log client options or error request bodies.
- Use conservative polling and caching. These endpoints have no published rate
limits or stability guarantees.
- Use this only for an account and site you are authorized to access. Review the
SolarEdge Monitoring Portal terms before production use.