Skip to content

Authorization

The SolisCloud API supports two authorization methods to serve different customer types:

AuthorizationAuthenticationApplicable To
User-levelCustom HMAC-SHA1 SignaturePlant owners and installers
OAuth2.0OAuth2.0 Bearer TokenThird-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

  1. Log in to SolisCloud WEB: https://www.soliscloud.com
  2. Click ServiceAPI Management
  3. 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 ParameterDescription
Content-MD5MD5 hash of the Body content, Base64 encoded
Content-TypeFixed value: application/json;charset=UTF-8
DateGMT time, format: EEE, d MMM yyyy HH:mm:ss 'GMT'
AuthorizationAPI {apiId}:{sign}

Content-MD5 Calculation

  1. Apply MD5 hashing to the raw request body content
  2. Convert the resulting hash to a 128-bit binary array
  3. Base64-encode the binary array
java
public static String getDigest(String body) {
    MessageDigest md = MessageDigest.getInstance("MD5");
    md.update(body.getBytes());
    byte[] b = md.digest();
    return Base64.encodeBytes(b);
}
python
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')
javascript
const crypto = require('crypto');

function getDigest(body) {
    return crypto.createHash('md5').update(body).digest('base64');
}
java
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
java
SimpleDateFormat sdf = new SimpleDateFormat("EEE, d MMM yyyy HH:mm:ss 'GMT'", Locale.US);
sdf.setTimeZone(TimeZone.getTimeZone("GMT"));
python
from datetime import datetime, timezone

date_str = datetime.now(timezone.utc).strftime('%a, %d %b %Y %H:%M:%S GMT')
javascript
const dateStr = new Date().toUTCString();
java
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
  • \n represents a newline character (LF)

Complete signature example:

python
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}'
javascript
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}`;
java
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

bash
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

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
ParameterTypeRequiredDescription
client_idStringYesAppKey registered with Solis for the third-party app
response_typeStringYesFixed value: code (standard OAuth 2.0 parameter)
redirect_uriStringYesCallback URL; must match the redirect URI registered with Solis
stateStringNoState 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
scopeStringNoRequested 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:

  1. Log in with their SolisCloud account credentials
  2. Review the requested authorization scope (data access / device control) and click Approve
  3. Be redirected to the callback URL with the authorization code and state appended 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_STATE

Step 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 ParameterDescription
Content-Typeapplication/x-www-form-urlencoded
AuthorizationBasic Base64(YOUR_API_KEY:YOUR_API_SECRET)

Request Body:

ParameterTypeRequiredDescription
grant_typeStringYesFixed value: authorization_code
codeStringYesAuthorization code obtained in Step 1
redirect_uriStringYesMust 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:

bash
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.com

Response Example:

json
{
  "access_token": "eyJhbGci...",
  "token_type": "bearer",
  "refresh_token": "eyJhbGci...",
  "expires_in": 86400,
  "scope": "access_data control_device"
}
Response FieldDescription
access_tokenBearer token required for all API calls
refresh_tokenToken used to obtain a new access token without re-authorization
expires_inToken validity period in seconds; default 86400 (1 day)
scopeAuthorized 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:

bash
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:

ParameterTypeRequiredDescription
grant_typeStringYesFixed value: refresh_token
refresh_tokenStringYesThe refresh_token returned from the previous call
bash
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_TOKEN

Refresh Token Notes

  • Proactively refresh before the access_token expires to avoid service interruption
  • Each refresh returns a new access_token and refresh_token; the validity period resets upon each refresh
  • The previous tokens are immediately invalidated after a successful refresh
  • If the refresh_token has 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 ParameterDescription
Content-Typeapplication/x-www-form-urlencoded
AuthorizationBasic Base64(YOUR_API_KEY:YOUR_API_SECRET)

Request Body:

ParameterTypeRequiredDescription
tokenStringYesThe token to revoke — can be an access_token or a refresh_token

Request Example:

bash
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_TOKEN

Response 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