Skip to content

授权方式

锦浪云 API 提供两种授权方式,适用于不同的客户类型:

授权方式认证方式适用客户
面向云平台用户授权自定义 HMAC-SHA1 签名业主和安装商
第三方授权OAuth2.0第三方服务商、第三方监控平台

API 基础域名

  • 面向云平台用户授权:https://www.soliscloud.com
  • 第三方授权(OAuth2.0):https://api-oauth2.soliscloud.com

注意

两种授权方式的接口相互独立,面向云平台用户授权接口不支持第三方授权调用,反之亦然。

关于接口路径中的 v1/v2 命名

文档中提到的 /v1/api//v2/api/ 仅为接口路径命名约定,不代表 API 版本升级或替代关系。两种路径的接口均持续维护,请根据授权方式选择对应的接口路径。


面向云平台用户授权(HMAC 签名)

按用户分配系统访问权限,用户只能在自己被分配的资源范围内操作。适用于在锦浪云拥有可登录账号,且账号下有直接归属或共享电站的 业主和安装商

接入流程

获取 API Key

  1. 登录锦浪云 WEB 端:https://www.soliscloud.com
  2. 点击 服务API 管理
  3. 点击 立即开通,验证身份后即可查看 KeyIDKeySecret

安全提醒

请妥善保管 KeySecret,避免数据泄露。开通 API 权限后,需要退出重新登录才能生效。

认证方式

接口使用自定义 HMAC-SHA1 签名认证。每次请求需要在 HTTP Header 中携带以下 4 个参数:

Header 参数说明
Content-MD5Body 内容的 MD5 值,经 Base64 编码
Content-Type固定值:application/json;charset=UTF-8
DateGMT 时间,格式:EEE, d MMM yyyy HH:mm:ss 'GMT'
AuthorizationAPI {apiId}:{sign}

Content-MD5 计算方式

  1. 对 Body 内容进行 MD5 加密
  2. 将加密结果转换为 128 位二进制数组
  3. 对二进制数组进行 Base64 编码
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 格式要求

  • 使用 GMT 时区时间
  • 格式:EEE, d MMM yyyy HH:mm:ss 'GMT'
  • 时间偏差不能超过当前时间正负 15 分钟,否则调用失败
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 签名计算

格式:"API " + apiId + ":" + Sign

Sign 计算公式:

Sign = Base64(HmacSHA1(KeySecret, POST + "\n" + Content-MD5 + "\n" + Content-Type + "\n" + Date + "\n" + CanonicalizedResource))
  • CanonicalizedResource = 要访问的 API 接口路径,如 /v1/api/inverterDetail
  • \n 表示换行符

完整签名示例:

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;

请求示例

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}

签名校验工具

支持的 API 类型

  • 数据获取 — 逆变器、采集器、EPM、气象仪、电表、电站数据查询
  • 设备控制 — 逆变器远程控制、参数读取
  • 电站管理 — 电站新增、修改、绑定/解绑设备

第三方授权(OAuth2.0)

允许第三方应用通过令牌合法访问用户资源,无需用户的账号密码。适用于在锦浪云无电站访问权限的第三方应用,如 第三方服务商、第三方监控平台

以下示例中 {API_OAUTH2_DOMAIN} 请替换为实际域名:https://api-oauth2.soliscloud.com

接入流程总览

申请条件

第三方平台接入前,需联系 Solis 销售申请开通,提交 SolisCloud API 开通申请材料.xlsx,Solis 将为第三方平台分配 API Key(client_id)API Secret(client_secret)

授权流程详解

第一步:请求用户授权码

在浏览器中访问以下 URL,引导业主完成登录和授权:

GET https://{API_OAUTH2_DOMAIN}/oauth/authorize?response_type=code&client_id=YOUR_API_KEY&redirect_uri=YOUR_REDIRECT_URI&state=RANDOM_STATE
参数类型必填说明
client_idString第三方应用在锦浪注册的 AppKey
response_typeString固定值 code,OAuth 2.0 协议标准参数
redirect_uriString授权回调地址,需与申请时提供给锦浪的回调地址一致
stateString状态值,用于防范 CSRF 攻击。业主授权后回调时会原样带回该字段,第三方应用可校验是否匹配。建议使用 UUID 或随机字符串生成,长度不超过 180 个字符
scopeString请求的权限集,多个权限用空格分隔(URL 编码为 %20)。如 access_data control_device。不传时默认请求全部权限

redirect_uri 编码

当回调地址中包含 & 等特殊字符时,必须对 redirect_uri 进行 URL 编码后再拼接,否则可能导致参数解析异常。例如 https://www.example.com/cn/?a=b&c=d 应编码为 https%3A%2F%2Fwww.example.com%2Fcn%2F%3Fa%3Db%26c%3Dd

第三方应用可将此链接集成到系统中,或直接发送给电站业主。业主访问后:

  1. 输入锦浪云账号密码登录
  2. 查看授权范围(数据获取 / 设备控制),点击同意
  3. 页面携带授权码重定向到回调地址,同时带回 state 参数
# 授权成功(带回 state)
https://your-callback.com/response?code=AUTHORIZATION_CODE&state=RANDOM_STATE

# 业主拒绝授权
https://your-callback.com/response?error=access_denied&state=RANDOM_STATE

第二步:通过授权码换取 Access Token

请求 URLPOST https://{API_OAUTH2_DOMAIN}/oauth/token

请求头(Authorization):使用 HTTP Basic 认证,值为 Basic Base64(client_id:client_secret)

Header 参数说明
Content-Typeapplication/x-www-form-urlencoded
AuthorizationBasic Base64(YOUR_API_KEY:YOUR_API_SECRET)

请求体(Body)

参数类型必填说明
grant_typeString固定为 authorization_code
codeString第一步获取的授权码
redirect_uriString与申请时填写的回调地址一致

client_id / client_secret

client_id 即锦浪分配的 AppKey,client_secret 即对应的 AppSecret。两者通过 Basic 认证方式放在请求头中,而非请求体中。

请求示例

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

响应示例

json
{
  "access_token": "eyJhbGci...",
  "token_type": "bearer",
  "refresh_token": "eyJhbGci...",
  "expires_in": 86400,
  "scope": "access_data control_device"
}
响应字段说明
access_token调用 API 所需的令牌
refresh_token用于续期的刷新令牌
expires_inToken 有效期(秒),默认 86400(1天)
scope授权范围:access_data 数据获取 / control_device 设备控制

重要提醒

  • 授权码只能使用一次,使用后立即失效
  • Access Token 有效期 1 天
  • Refresh Token 有效期 30 天
  • 授权码有效期 30 分钟

使用 Token 调用 API

获取 access_token 后,在每次 API 请求的 HTTP Header 中携带 Bearer Token:

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 '{}'

重要说明

  • Bearer Token 必须放在 HTTP 请求头中,格式为 Authorization: Bearer {access_token}
  • 不能将 token 放在请求体中
  • 所有 OAuth2.0 接口均使用此认证方式

刷新 Token

当 access_token 过期时,使用 refresh_token 续期,无需业主重新授权:

请求 URLPOST https://{API_OAUTH2_DOMAIN}/oauth/token

请求头(同第二步)

请求体(Body)

参数类型必填说明
grant_typeString固定为 refresh_token
refresh_tokenString上次返回的 refresh_token
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

刷新 Token 注意事项

  • 建议在 access_token 过期前主动刷新
  • 刷新后返回全新的 access_token 和 refresh_token,有效期重置
  • 刷新后旧 Token 立即失效
  • 若 refresh_token 也过期,需重新引导业主走完整授权流程
  • 如无需频繁重新授权,可由后台定期调用刷新接口保持 Token 有效

撤销授权

业主主动撤销

电站业主可通过锦浪云 WEB 端或 App 撤销授权:账号与安全 > 授权管理

三方应用主动撤销

第三方应用也可通过 API 主动注销某业主的授权,使其无法再访问业主数据。适用场景包括:业主主动注销授权、检测到安全漏洞时紧急撤销等。

请求 URLPOST https://{API_OAUTH2_DOMAIN}/oauth/revoke

请求头(Authorization):使用 HTTP Basic 认证,值为 Basic Base64(client_id:client_secret)

Header 参数说明
Content-Typeapplication/x-www-form-urlencoded
AuthorizationBasic Base64(YOUR_API_KEY:YOUR_API_SECRET)

请求体(Body)

参数类型必填说明
tokenString需要撤销的令牌,可以是 access_token 或 refresh_token

请求示例

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

返回示例

撤销成功后,接口返回 HTTP 200 状态码。

注意

撤销后该令牌立即失效。如撤销的是 refresh_token,则关联的 access_token 也将同时失效。

支持的 API 类型

  • 数据获取 — 逆变器、采集器、EPM、气象仪、电站数据查询
  • 设备控制 — 逆变器远程控制、参数读取、策略下发
  • 实时数据转发 — 通过 RocketMQ/MQTT/HTTP 获取实时数据