Skip to content

HTTP API

HTTP API 使用统一域名 https://api-tistore.tange365.com,分为两组:

  • /openapi/v1 面向可信业务服务端,使用 TGV1 签名;
  • /sdk/v1 面向持有短期 APP Access Token 的客户端,使用 Bearer Token。

业务服务端当前公开 4 个接口:

方法路径用途
POST/issue-device-access-token为设备签发上传所需的 device_access_token
POST/issue-app-access-token为客户端签发访问目标设备录像的 app_access_token
GET/query-recording-file-indexes分页查询录像文件索引
POST/delete-recordings删除指定设备和时间范围内的录像

签发 Token 前,先按 云端签发 Token 完成凭证保管和请求签名。查询或删除录像时,参阅 管理云录像

客户端可以调用 GET /sdk/v1/query-recording-days 查询指定范围内哪些日期有录像。通常应优先使用客户端 SDK 的 listRecordingDays 包装;直接接入 HTTP 时,按查询有录像的日期发送请求。

业务服务端 API 鉴权

/openapi/v1 请求使用 TGV1-HMAC-SHA256AppIdAccessKeyIdAccessKeySecret 只保存在可信业务服务端。请求时间与服务端相差在 300 秒以内。

请求 Header

Header说明
X-Tg-Algorithm固定为 TGV1-HMAC-SHA256
X-Tg-App-Id当前云存储应用的 AppId
X-Tg-DateUTC 时间,格式为 yyyyMMdd'T'HHmmss'Z'
X-Tg-Content-Sha256最终发送 Body 原始字节的 SHA-256 小写十六进制值;无 Body 时使用空字节摘要
X-Tg-Signed-Headers参与签名的 Header 名称,以小写、严格升序、分号连接
AuthorizationAccessKeyId、Credential Scope、SignedHeaders 和最终签名

POST 请求发送 Content-Type: application/json 和实际 Content-Length,并把这两个 Header 加入 SignedHeaders。SignedHeaders 不含 Host

1. 构造 Canonical Request

Canonical Request 由以下六行组成,行间使用 LF,末尾不追加换行:

text
HTTP_METHOD
CANONICAL_URI
CANONICAL_QUERY
CANONICAL_HEADERS
SIGNED_HEADER_NAMES
BODY_SHA256
  • HTTP_METHOD:使用大写方法名。
  • CANONICAL_URI:对 URL 的 escaped path 再执行 RFC 3986 Path 转义;普通 HTTP API 路径保持 /openapi/v1/...
  • CANONICAL_QUERY:按参数名排序;同名参数的值也排序;名称和值使用 RFC 3986 编码,空格编码为 %20,同名参数全部保留。
  • CANONICAL_HEADERS:只包含 SignedHeaders,Header 名转小写并严格升序,值去除首尾空白,每项写作 name:value 并以 LF 分隔。
  • SIGNED_HEADER_NAMES:与 X-Tg-Signed-Headers 完全一致。
  • BODY_SHA256:最终发送 Body 原始字节的 SHA-256 小写十六进制值。

必签 Header 包括 X-Tg-AlgorithmX-Tg-App-IdX-Tg-Date。POST、PUT、PATCH 还要签入 Content-Type 和实际 Content-LengthX-Tg-Content-Sha256 与 Body Hash 一致,SignedHeaders 不含该字段。

额外发送的 X-Tg-* Header 也要加入 SignedHeaders,但以下传输字段除外:

  • X-Tg-Signed-Headers
  • X-Tg-Content-Sha256
  • Credential
  • Signature

每个被签 Header 只发一个值。

2. 构造 String to Sign

Credential Scope 使用“签名 UTC 日期加 7 天”的日期,格式为:

text
YYYYMMDD/tgv1_request

这里的“加 7 天”是 TGV1 协议约定,不表示请求或 Token 有效 7 天。String to Sign 由四行组成:

text
TGV1-HMAC-SHA256
X-Tg-Date
CredentialScope
HexLower(SHA256(CanonicalRequest))

3. 派生签名

按以下顺序计算 HMAC-SHA256;前三步保留原始摘要字节,最后一步才转为小写十六进制:

text
kDate     = HMAC-SHA256(key="TGV1" + AccessKeySecret, message=UTC_SIGNING_DATE)
kPath     = HMAC-SHA256(key=kDate, message=URL_PATH)
kSigning  = HMAC-SHA256(key=kPath, message="tgv1_request")
Signature = HexLower(HMAC-SHA256(key=kSigning, message=StringToSign))

UTC_SIGNING_DATE 取签名当天的 YYYYMMDD。Credential Scope 使用加 7 天后的日期。URL_PATH 使用解码后的 URL Path,和 Canonical URI 的二次百分号编码分开计算。

4. 设置 Authorization

text
TGV1-HMAC-SHA256 Credential=<AccessKeyId>/<CredentialScope>, SignedHeaders=<SignedHeaders>, Signature=<Signature>

Authorization 中的 SignedHeaders 必须与 X-Tg-Signed-Headers 完全一致。

固定测试向量

先使用下面的非生产凭证验证签名器。只有输出与预期 Authorization 完全一致,才能替换真实凭证和业务参数。

text
Method: POST
Path: /openapi/v1/issue-device-access-token
Raw Query: z=last&a=two&a=one
Body: {"device_id":"device-001"}
Signing Time: 2026-08-10T01:02:03Z
AppId: app-001
AccessKeyId: AK_TEST_001
AccessKeySecret: test-secret-not-real

请求 Header:

text
Content-Length: 26
Content-Type: application/json
X-Tg-Algorithm: TGV1-HMAC-SHA256
X-Tg-App-Id: app-001
X-Tg-Content-Sha256: f1e6aa2231dd0d25e64a2cf4ae2348a1a165ad5a442e6429ab29e62bf87cc16f
X-Tg-Date: 20260810T010203Z

规范化后的 Query、Scope 和 SignedHeaders:

text
Canonical Query: a=one&a=two&z=last
Credential Scope: 20260817/tgv1_request
SignedHeaders: content-length;content-type;x-tg-algorithm;x-tg-app-id;x-tg-date

预期 Authorization:

text
TGV1-HMAC-SHA256 Credential=AK_TEST_001/20260817/tgv1_request, SignedHeaders=content-length;content-type;x-tg-algorithm;x-tg-app-id;x-tg-date, Signature=2fd626b889af8cc4bd87fc9aaaf84f5fe8eb49438387308c7c6a42ce73b50c5b

生产日志记录接口名、状态码、coderequest_id

请求与响应 JSON

成功和失败都使用统一 JSON 包络,业务字段在 data

json
{
  "code": "OK",
  "message": "成功",
  "request_id": "req_xxx",
  "data": {}
}
字段类型说明
codestring程序判断依据。成功为 OK
messagestring给人看的说明
request_idstring单次请求的排查线索
dataobject业务字段。失败时为空对象 {}

整数按位置这样写:

位置写法例子
JSON BodyJSON number"duration_seconds": 3600"start_time_ms": 1786896000000
Query十进制数字start_time_ms=1786896000000
响应 data 中的时间戳和字节数十进制字符串"expires_at_ms": "1786899600000"

响应里按字符串接收 expires_at_msstart_time_msend_time_mssize_bytestotal_size_bytesduration_secondsrecording_retention_days 是 JSON number。has_moreeffective 是 boolean。

POST 的 Body 是 JSON 对象,字段以下表为准,Content-Typeapplication/json。GET 把参数放在 Query 里。device_id 填写要操作的目标设备的标识。

签发 device_access_token

http
POST /openapi/v1/issue-device-access-token
Content-Type: application/json

{
  "device_id": "device-001",
  "duration_seconds": 3600,
  "recording_retention_days": 7
}
字段类型必填说明
device_idstring要签发上传 Token 的设备的标识
duration_secondsnumberToken 有效期,900~43200 秒
recording_retention_daysnumber写入设备 Token 的录像保存天数,正整数

成功响应:

json
{
  "code": "OK",
  "message": "成功",
  "request_id": "req_xxx",
  "data": {
    "access_token": "opaque-token",
    "expires_at_ms": "1786899600000",
    "recording_retention_days": 7
  }
}
字段类型说明
access_tokenstring下发给设备的 device_access_token
expires_at_msstringToken 到期时间,UTC Unix 毫秒
recording_retention_daysnumber实际写入 Token 的保存天数

设备把该 Token 传给 C SDK 后上传录像。

签发 app_access_token

http
POST /openapi/v1/issue-app-access-token
Content-Type: application/json

{
  "device_id": "device-001",
  "duration_seconds": 3600
}
字段类型必填说明
device_idstring要签发 Token 的目标设备标识,客户端将访问这台设备的录像
duration_secondsnumberToken 有效期,900~43200 秒

成功响应:

json
{
  "code": "OK",
  "message": "成功",
  "request_id": "req_xxx",
  "data": {
    "access_token": "opaque-token",
    "expires_at_ms": "1786899600000"
  }
}
字段类型说明
access_tokenstring下发给客户端的 app_access_token
expires_at_msstringToken 到期时间,UTC Unix 毫秒

客户端用这个 Token 访问签发时指定的那台设备的录像。设备上传使用 device_access_token

查询录像文件索引

http
GET /openapi/v1/query-recording-file-indexes?device_id=device-001&start_time_ms=1786896000000&end_time_ms=1786899600000&page_size=100

查询范围是 UTC Unix 毫秒左闭右开区间 [start_time_ms, end_time_ms)。参数放在 Query 里。继续分页时,设备和时间范围与第一页相同,并把上一页的 next_page_token 填入 page_token

Query 参数类型必填说明
device_idstring要查询录像索引的设备的标识
start_time_msstringUTC Unix 毫秒开始时间,包含
end_time_msstringUTC Unix 毫秒结束时间,不含该时刻。单次范围最长 31 天
page_sizestring每页条数。省略或 0 时为 50,取值 1~200
page_tokenstring上一页返回的 next_page_token。第一页省略

成功响应:

json
{
  "code": "OK",
  "message": "成功",
  "request_id": "req_xxx",
  "data": {
    "items": [
      {
        "oss_id": "oss-001",
        "recording_retention_days": 7,
        "start_time_ms": "1786896000000",
        "end_time_ms": "1786899600000",
        "total_size_bytes": "1048576",
        "files": [
          {
            "path": "7/device-001/20260816/16-00-00-3600.data",
            "size_bytes": "1048576",
            "sha256": ""
          }
        ]
      }
    ],
    "has_more": false,
    "next_page_token": ""
  }
}
字段类型说明
itemsobject[]当前页的录像文件索引;没有命中时为空数组
has_morebooleantrue 时继续请求下一页
next_page_tokenstring有下一页时,把它原样填入下一页的 page_tokenhas_morefalse 时为空字符串,到此结束分页

items 中每个元素:

字段类型说明
oss_idstring录像所在的存储标识
recording_retention_daysnumber该录像的保存天数
start_time_msstring物理文件开始时间,UTC Unix 毫秒,左闭
end_time_msstring物理文件结束时间,UTC Unix 毫秒,右开
total_size_bytesstring文件总字节数
filesobject[]该索引下的物理文件

files 中每个元素:

字段类型说明
pathstring对象存储中的文件路径
size_bytesstring文件字节数
sha256string设备上报时可能附带的摘要。用 pathsize_bytes 识别文件

每一页是发出该次请求时的查询结果。分页过程中如果有新的录像索引进入同一时间范围,后续页面可能与已读页面重复或漏项。要得到一份稳定清单,等该时间范围不再写入新索引后,再从第一页查起。

删除录像

http
POST /openapi/v1/delete-recordings
Content-Type: application/json

{
  "device_id": "device-001",
  "start_time_ms": 1786896000000,
  "end_time_ms": 1786899600000
}
字段类型必填说明
device_idstring要删除其录像的设备的标识
start_time_msnumberUTC Unix 毫秒开始时间,包含
end_time_msnumberUTC Unix 毫秒结束时间,不含该时刻

删除范围是 UTC Unix 毫秒左闭右开区间 [start_time_ms, end_time_ms)。响应里的起止时间与这次请求提交的范围一致。

成功响应:

json
{
  "code": "OK",
  "message": "成功",
  "request_id": "req_xxx",
  "data": {
    "start_time_ms": "1786896000000",
    "end_time_ms": "1786899600000",
    "effective": true
  }
}
字段类型说明
start_time_msstring这次请求提交的开始时间,UTC Unix 毫秒
end_time_msstring这次请求提交的结束时间,UTC Unix 毫秒
effectiveboolean这组精确参数是否第一次写入删除记录

相同 AppIddevice_idstart_time_msend_time_ms 再次请求时,第一次返回 true,之后返回 false。两个范围只要起止时间不同,就会各写一条记录。

客户端查询和播放会扣除已删除区间。用客户端查询确认删除是否生效。HTTP API 的文件索引按物理文件完整覆盖才隐藏该项;部分覆盖时仍返回完整文件。存储空间随后回收。

查询有录像的日期

客户端使用短期 app_access_token 调用此接口。Token 已经绑定目标设备,请求中不再传 device_id

客户端 API 鉴权

调用时,把 APP Access Token 写入 Authorization Header。请求还要携带客户端平台和 SDK 版本。

http
GET /sdk/v1/query-recording-days?start_date=2026-08-01&end_date=2026-08-31&timezone=Asia%2FShanghai
Authorization: Bearer <app_access_token>
X-Tg-Platform: ANDROID
X-Tg-Sdk-Version: <sdk_version>

每个请求必须且只能发送一个 AuthorizationX-Tg-PlatformX-Tg-Sdk-Version Header。X-Tg-PlatformX-Tg-Sdk-Version 填写当前客户端平台和 SDK 版本。两者必须是有效的 UTF-8 字符串,长度为 1~64 字节,不能带首尾空白或控制字符。

APP 请求不要发送设备端专用的 X-Tg-Client-IdX-Tg-DateX-Tg-SignatureX-Tg-Nonce Header。即使这些 Header 的值为空,请求也会被拒绝。

Query 参数类型必填说明
start_datestring开始日期,严格使用 YYYY-MM-DD,包含当天
end_datestring结束日期,严格使用 YYYY-MM-DD,包含当天
timezonestring解释自然日边界的 IANA 时区 ID,例如 Asia/ShanghaiAmerica/New_York

起止日期均包含,单次最多查询 31 个自然日。时区必须使用有效的 IANA ID;不要传 UTC+8Local 或带首尾空白的值。服务端按该时区的自然日边界查询,因此夏令时切换日不固定为 24 小时。

成功响应会按日期升序返回范围内的每一天,包括没有录像的日期:

json
{
  "code": "OK",
  "message": "成功",
  "request_id": "req_xxx",
  "data": {
    "items": [
      {
        "date": "2026-08-01",
        "has_recording": true
      },
      {
        "date": "2026-08-02",
        "has_recording": false
      }
    ]
  }
}
字段类型说明
itemsobject[]查询范围内的完整逐日结果,按 date 升序排列
items[].datestringYYYY-MM-DD 日期
items[].has_recordingboolean该自然日至少有一个当前可见的录像时点时为 true

has_recording 只用于判断当天是否需要继续查询录像时间段。要取得可回放范围,请调用客户端 SDK 的 listRecordings

HTTP 状态code常见原因
400InvalidParameter日期或时区格式不合法,或客户端公共 Header 不合法
400InvalidParameter.TimeRange起止日期倒置,或范围超过 31 天
401AuthFailure.TokenMissing缺少 APP Access Token
401AuthFailure.TokenInvalidToken 无效或类型不匹配
401AuthFailure.TokenExpiredToken 已过期
403AccessDenied.Device当前应用无权访问 Token 绑定的设备
503ServiceUnavailable录像查询依赖暂时不可用

响应与诊断

用 HTTP 状态和 code 判断结果。排查时记录接口名、发生时间、状态码、coderequest_id

/openapi/v1 鉴权失败时,检查请求时间、AppId、签名输入和密钥配置。/sdk/v1/query-recording-days 鉴权失败时,检查 APP Access Token,以及 AuthorizationX-Tg-PlatformX-Tg-Sdk-Version 是否各发送一次且格式合法。

TiStore 开发文档