4.8 KiB
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:
- Load the hosted SolarEdge/AWS Cognito login form.
- Submit the configured portal username and password together with the CSRF token and login cookies.
- Exchange the returned authorization code for OAuth tokens.
- Exchange those tokens at
/services/auth/token?legacy=falsefor a Monitoring portal session. - Send both the bearer access token and session cookies to internal services.
- Refresh the session through
POST /services/auth/refreshwhen 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:
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.
import { createSolarEdgePortalClientFromEnv } from "@solar-dash/solaredgeapi";
const portal = createSolarEdgePortalClientFromEnv();
await portal.authenticate();
Observed endpoints
Logical layout including optimizers
GET /services/layout/logical/generic/v2/site/{siteId}?include-optimizers=true
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:
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
POST /services/layout/information/optimizers
Content-Type: application/json
["OPTIMIZER_SERIAL_1", "OPTIMIZER_SERIAL_2"]
Observed response container:
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;
}>;
};
const information = await portal.getOptimizerInformation([
"OPTIMIZER_SERIAL_1",
"OPTIMIZER_SERIAL_2",
]);
Optimizer energy graph
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:
type SolarEdgeOptimizerEnergy = {
totalEnergy: number;
energyBars: Array<{
measurementTime: string;
energy: number | null;
}>;
};
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_REQUIREDif 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.