Authorization
The SolisCloud API supports two authorization methods to serve different customer types:
| Authorization | Authentication | Applicable To |
|---|---|---|
| User-level | Custom HMAC-SHA1 Signature | Plant owners and installers |
| OAuth2.0 | OAuth2.0 Bearer Token | Third-party service providers and monitoring platforms |
API Base Domain:
- User-level Authorization:
https://www.soliscloud.com- OAuth2.0 Authorization:
https://api-oauth2.soliscloud.com
Important
The two authorization methods operate on independent interfaces. User-level authorization endpoints do not accept OAuth2.0 Bearer Tokens, and OAuth2.0 endpoints do not accept HMAC signatures.
About v1/v2 in API Paths
The /v1/api/ and /v2/api/ prefixes in the documentation are naming conventions only, not version indicators. Both path types are actively maintained. Please choose the appropriate path based on your authorization method.
User-level Authorization (HMAC Signature)
Grants system access permissions on a per-user basis. Each user can only operate within their assigned resource scope. Designed for plant owners and installers who hold a SolisCloud login account with directly owned or shared plants.
Access Flow
Get API Key
- Log in to SolisCloud WEB:
https://www.soliscloud.com - Click Service → API Management
- Click Activate Now, verify your identity, then view your KeyID and KeySecret
Security Reminder
Keep your KeySecret strictly confidential to prevent unauthorized access. After activating API permissions, please log out and log back in for the changes to take effect.
Authentication Method
All interfaces use Custom HMAC-SHA1 signature authentication. Each request must include the following 4 parameters in the HTTP request header:
| Header Parameter | Description |
|---|---|
| Content-MD5 | MD5 hash of the Body content, Base64 encoded |
| Content-Type | Fixed value: application/json;charset=UTF-8 |
| Date | GMT time, format: EEE, d MMM yyyy HH:mm:ss 'GMT' |
| Authorization | API {apiId}:{sign} |
Content-MD5 Calculation
- Apply MD5 hashing to the raw request body content
- Convert the resulting hash to a 128-bit binary array
- Base64-encode the binary array
public static String getDigest(String body) {
MessageDigest md = MessageDigest.getInstance("MD5");
md.update(body.getBytes());
byte[] b = md.digest();
return Base64.encodeBytes(b);
}import hashlib
import base64
def get_digest(body: str) -> str:
md5_hash = hashlib.md5(body.encode('utf-8')).digest()
return base64.b64encode(md5_hash).decode('utf-8')const crypto = require('crypto');
function getDigest(body) {
return crypto.createHash('md5').update(body).digest('base64');
}public static String getDigest(String body) throws Exception {
MessageDigest md = MessageDigest.getInstance("MD5");
md.update(body.getBytes());
byte[] b = md.digest();
return Base64.encodeBytes(b);
}Date Format Requirements
- Use GMT timezone
- Format:
EEE, d MMM yyyy HH:mm:ss 'GMT' - The timestamp must be within ±15 minutes of the current server time; requests outside this window will be rejected
SimpleDateFormat sdf = new SimpleDateFormat("EEE, d MMM yyyy HH:mm:ss 'GMT'", Locale.US);
sdf.setTimeZone(TimeZone.getTimeZone("GMT"));from datetime import datetime, timezone
date_str = datetime.now(timezone.utc).strftime('%a, %d %b %Y %H:%M:%S GMT')const dateStr = new Date().toUTCString();SimpleDateFormat sdf = new SimpleDateFormat("EEE, d MMM yyyy HH:mm:ss 'GMT'", Locale.US);
sdf.setTimeZone(TimeZone.getTimeZone("GMT"));
String dateStr = sdf.format(new Date());Authorization Signature Calculation
Format: "API " + apiId + ":" + Sign
Sign calculation formula:
Sign = Base64(HmacSHA1(KeySecret, POST + "\n" + Content-MD5 + "\n" + Content-Type + "\n" + Date + "\n" + CanonicalizedResource))CanonicalizedResource= the target API endpoint path, e.g./v1/api/inverterDetail\nrepresents a newline character (LF)
Complete signature example:
import hashlib
import base64
import hmac
import json
from datetime import datetime, timezone
api_id = 'YOUR_API_ID'
api_secret = 'YOUR_API_SECRET'
body = json.dumps({})
canonicalized_resource = '/v1/api/userStationList'
# 1. Content-MD5
content_md5 = base64.b64encode(
hashlib.md5(body.encode('utf-8')).digest()
).decode('utf-8')
# 2. Date
date_str = datetime.now(timezone.utc).strftime('%a, %d %b %Y %H:%M:%S GMT')
# 3. Sign
sign_str = f'POST\n{content_md5}\napplication/json;charset=UTF-8\n{date_str}\n{canonicalized_resource}'
sign = base64.b64encode(
hmac.new(api_secret.encode('utf-8'), sign_str.encode('utf-8'), hashlib.sha1).digest()
).decode('utf-8')
authorization = f'API {api_id}:{sign}'const crypto = require('crypto');
const apiId = 'YOUR_API_ID';
const apiSecret = 'YOUR_API_SECRET';
const body = JSON.stringify({});
const canonicalizedResource = '/v1/api/userStationList';
// 1. Content-MD5
const contentMd5 = crypto.createHash('md5').update(body).digest('base64');
// 2. Date
const dateStr = new Date().toUTCString();
// 3. Sign
const signStr = `POST\n${contentMd5}\napplication/json;charset=UTF-8\n${dateStr}\n${canonicalizedResource}`;
const sign = crypto.createHmac('sha1', apiSecret).update(signStr).digest('base64');
const authorization = `API ${apiId}:${sign}`;import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.security.MessageDigest;
import java.text.SimpleDateFormat;
import java.util.*;
String apiId = "YOUR_API_ID";
String apiSecret = "YOUR_API_SECRET";
String body = "{}";
String canonicalizedResource = "/v1/api/userStationList";
// 1. Content-MD5
MessageDigest md = MessageDigest.getInstance("MD5");
md.update(body.getBytes());
String contentMd5 = Base64.getEncoder().encodeToString(md.digest());
// 2. Date
SimpleDateFormat sdf = new SimpleDateFormat("EEE, d MMM yyyy HH:mm:ss 'GMT'", Locale.US);
sdf.setTimeZone(TimeZone.getTimeZone("GMT"));
String dateStr = sdf.format(new Date());
// 3. Sign
String signStr = "POST\n" + contentMd5 + "\napplication/json;charset=UTF-8\n" + dateStr + "\n" + canonicalizedResource;
Mac mac = Mac.getInstance("HmacSHA1");
mac.init(new SecretKeySpec(apiSecret.getBytes(), "HmacSHA1"));
String sign = Base64.getEncoder().encodeToString(mac.doFinal(signStr.getBytes()));
String authorization = "API " + apiId + ":" + sign;Request Example
POST /v1/api/userStationList
Content-MD5: kxdxk7rbAsrzSIWgEwhH4w==
Content-Type: application/json;charset=UTF-8
Date: Fri, 26 Jul 2019 06:00:46 GMT
Authorization: API {apiId}:nBYQWeuzy3Y+gp67BN8zXTmvSDk=
Body: {"pageNo":1,"pageSize":10}Signature Verification Tools
- Online HMAC tool: https://dinochiesa.github.io/hmachash/index.html — select HmacSHA1, use your KeySecret as the key, input the signing string, and Base64-encode the output to get
sign - Java full example download: Authorization.java
Supported API Types
- Data Access — Inverter, collector, EPM, weather station, ammeter, and plant data queries
- Device Control — Inverter remote control, parameter reading
- Plant Management — Plant creation, modification, device binding/unbinding
OAuth2.0 Authorization (Bearer Token)
Enables third-party applications to access user resources via delegated tokens, without requiring the user's account credentials. Designed for third-party applications that do not hold SolisCloud plant access permissions — such as third-party service providers and monitoring platforms.
In the examples below, replace
{API_OAUTH2_DOMAIN}with the actual domain:https://api-oauth2.soliscloud.com
Access Flow Overview
Prerequisites
Prior to integration, third-party platforms must contact Solis sales to apply for activation and submit the SolisCloud API Activation Application.xlsx. Upon approval, Solis will issue an API Key (client_id) and API Secret (client_secret).
Authorization Flow Details
Step 1: Request an Authorization Code
Direct the plant owner to the following URL in a browser to initiate the authorization flow:
GET https://{API_OAUTH2_DOMAIN}/oauth/authorize?response_type=code&client_id=YOUR_API_KEY&redirect_uri=YOUR_REDIRECT_URI&state=RANDOM_STATE| Parameter | Type | Required | Description |
|---|---|---|---|
| client_id | String | Yes | AppKey registered with Solis for the third-party app |
| response_type | String | Yes | Fixed value: code (standard OAuth 2.0 parameter) |
| redirect_uri | String | Yes | Callback URL; must match the redirect URI registered with Solis |
| state | String | No | State value for CSRF protection. Returned unchanged in the callback; the third-party app should verify it matches. Recommended: UUID or random string, up to 180 characters |
| scope | String | No | Requested permission scopes, separated by spaces (URL-encoded as %20). E.g. access_data control_device. Defaults to all available scopes if omitted |
redirect_uri Encoding
When the callback URL contains special characters such as &, you must URL-encode redirect_uri before assembling the request, otherwise parameter parsing may fail. For example, https://www.example.com/cn/?a=b&c=d should be encoded as https%3A%2F%2Fwww.example.com%2Fcn%2F%3Fa%3Db%26c%3Dd.
The third-party application may embed this link in its own UI or send it directly to the plant owner. Upon visiting the link, the owner will:
- Log in with their SolisCloud account credentials
- Review the requested authorization scope (data access / device control) and click Approve
- Be redirected to the callback URL with the authorization code and
stateappended as query parameters
# Authorization granted (state returned)
https://your-callback.com/response?code=AUTHORIZATION_CODE&state=RANDOM_STATE
# Owner denied authorization
https://your-callback.com/response?error=access_denied&state=RANDOM_STATEStep 2: Exchange Authorization Code for Access Token
Request URL: POST https://{API_OAUTH2_DOMAIN}/oauth/token
Request Header (Authorization): Use HTTP Basic Auth — value is Basic Base64(client_id:client_secret)
| Header Parameter | Description |
|---|---|
| Content-Type | application/x-www-form-urlencoded |
| Authorization | Basic Base64(YOUR_API_KEY:YOUR_API_SECRET) |
Request Body:
| Parameter | Type | Required | Description |
|---|---|---|---|
| grant_type | String | Yes | Fixed value: authorization_code |
| code | String | Yes | Authorization code obtained in Step 1 |
| redirect_uri | String | Yes | Must exactly match the callback URL registered during application |
client_id / client_secret
client_id is the AppKey assigned by Solis; client_secret is the corresponding AppSecret. Both are passed via the HTTP Basic authentication header, not in the request body.
Request Example:
POST https://{API_OAUTH2_DOMAIN}/oauth/token
Authorization: Basic YOUR_BASE64_ENCODED_CREDENTIALS
Content-Type: application/x-www-form-urlencoded
grant_type=authorization_code&code=YOUR_CODE&redirect_uri=https%3A%2F%2Fyour-callback.comResponse Example:
{
"access_token": "eyJhbGci...",
"token_type": "bearer",
"refresh_token": "eyJhbGci...",
"expires_in": 86400,
"scope": "access_data control_device"
}| Response Field | Description |
|---|---|
| access_token | Bearer token required for all API calls |
| refresh_token | Token used to obtain a new access token without re-authorization |
| expires_in | Token validity period in seconds; default 86400 (1 day) |
| scope | Authorized scopes: access_data — data access; control_device — device control |
Token Validity
- Authorization code is single-use and becomes invalid immediately upon exchange
- Access Token is valid for 1 day
- Refresh Token is valid for 30 days
- Authorization code expires after 30 minutes
Calling the API
Once you have obtained the access_token, include it as a Bearer Token in the Authorization header of every API request:
curl -X POST "https://{API_OAUTH2_DOMAIN}/api/access_data/userStationList" \
-H "Content-Type: application/json;charset=UTF-8" \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
-d '{}'Refreshing the Access Token
When the access_token expires, use the refresh_token to obtain a new one without requiring the plant owner to re-authorize:
Request URL: POST https://{API_OAUTH2_DOMAIN}/oauth/token
Request Header (same as Step 2)
Request Body:
| Parameter | Type | Required | Description |
|---|---|---|---|
| grant_type | String | Yes | Fixed value: refresh_token |
| refresh_token | String | Yes | The refresh_token returned from the previous call |
POST https://{API_OAUTH2_DOMAIN}/oauth/token
Authorization: Basic YOUR_BASE64_ENCODED_CREDENTIALS
Content-Type: application/x-www-form-urlencoded
grant_type=refresh_token&refresh_token=YOUR_REFRESH_TOKENRefresh Token Notes
- Proactively refresh before the
access_tokenexpires to avoid service interruption - Each refresh returns a new
access_tokenandrefresh_token; the validity period resets upon each refresh - The previous tokens are immediately invalidated after a successful refresh
- If the
refresh_tokenhas also expired, the plant owner must complete the full authorization flow again - To minimize re-authorization events, the backend may periodically invoke the refresh endpoint to maintain token validity
Revoking Authorization
Owner-initiated Revocation
Plant owners may revoke third-party authorization at any time through the SolisCloud web portal or mobile app: Account & Security → Authorization Management
Third-party-initiated Revocation
Third-party applications may also proactively revoke a specific owner's authorization via API, rendering the associated tokens invalid. Use cases include: offering an "unauthorize" button in the owner's management interface, or emergency revocation upon detecting a security breach.
Request URL: POST https://{API_OAUTH2_DOMAIN}/oauth/revoke
Request Header (Authorization): HTTP Basic Auth — value is Basic Base64(client_id:client_secret)
| Header Parameter | Description |
|---|---|
| Content-Type | application/x-www-form-urlencoded |
| Authorization | Basic Base64(YOUR_API_KEY:YOUR_API_SECRET) |
Request Body:
| Parameter | Type | Required | Description |
|---|---|---|---|
| token | String | Yes | The token to revoke — can be an access_token or a refresh_token |
Request Example:
POST /oauth/revoke HTTP/1.1
Host: api-oauth2.soliscloud.com
Content-Type: application/x-www-form-urlencoded
Authorization: Basic Base64(client_id:client_secret)
token=YOUR_ACCESS_TOKEN_OR_REFRESH_TOKENResponse Example:
Upon successful revocation, the API returns an HTTP 200 status code.
Note
The revoked token is immediately invalidated. If a refresh_token is revoked, the associated access_token is also invalidated.
Supported API Types
- Data Access — Inverter, collector, EPM, weather station, and plant data queries
- Device Control — Inverter remote control, parameter reading, strategy dispatch
- Real-time Data Forwarding — Real-time data via RocketMQ/MQTT/HTTP
