244 lines
4.7 KiB
Markdown
244 lines
4.7 KiB
Markdown
# YINIAN Server Contract v0
|
|
|
|
> Updated: 2026-04-26
|
|
> Status: Draft contract for M2 integration
|
|
> Client switch: set `YINIAN_API_BASE_URL` to enable HTTP mode. Without it, desktop uses mock mode.
|
|
|
|
## 1. Transport
|
|
|
|
- Base URL: `YINIAN_API_BASE_URL`
|
|
- Content type: `application/json`
|
|
- Auth header: `Authorization: Bearer <accessToken>`
|
|
- Desktop header: `X-YINIAN-App-Version: 0.1.0`
|
|
- Timestamps: Unix milliseconds.
|
|
- Field casing: server should prefer `camelCase`; desktop accepts key `snake_case` aliases for M1/M2 migration.
|
|
|
|
## 2. Error Shape
|
|
|
|
Non-2xx responses should return:
|
|
|
|
```json
|
|
{
|
|
"error": {
|
|
"code": "SESSION_EXPIRED",
|
|
"message": "Session expired"
|
|
}
|
|
}
|
|
```
|
|
|
|
The desktop currently surfaces `error.message` and falls back to `YINIAN API request failed: <status>`.
|
|
|
|
Recommended error codes:
|
|
|
|
- `INVALID_CREDENTIALS`
|
|
- `INVALID_SMS_CODE`
|
|
- `SESSION_EXPIRED`
|
|
- `WORKSPACE_FORBIDDEN`
|
|
- `CONFIG_UNAVAILABLE`
|
|
- `SKILLS_MANIFEST_UNAVAILABLE`
|
|
|
|
## 3. Device Payload
|
|
|
|
Login requests include:
|
|
|
|
```json
|
|
{
|
|
"device": {
|
|
"device_id": "dev_local_electron",
|
|
"platform": "darwin",
|
|
"app_version": "0.1.0",
|
|
"machine_name": "YINIAN Desktop"
|
|
}
|
|
}
|
|
```
|
|
|
|
The exact `device_id` is a placeholder in M2 and should be replaced by a stable desktop installation id later.
|
|
|
|
## 4. Auth
|
|
|
|
### POST `/auth/login/sms`
|
|
|
|
Request:
|
|
|
|
```json
|
|
{
|
|
"phone": "13800000000",
|
|
"code": "123456",
|
|
"device": {}
|
|
}
|
|
```
|
|
|
|
Response:
|
|
|
|
```json
|
|
{
|
|
"accessToken": "access_demo",
|
|
"refreshToken": "refresh_demo",
|
|
"accessTokenExpiresAt": 1777188600000
|
|
}
|
|
```
|
|
|
|
### POST `/auth/login/password`
|
|
|
|
Request:
|
|
|
|
```json
|
|
{
|
|
"account": "ops@example.com",
|
|
"password": "secret",
|
|
"device": {}
|
|
}
|
|
```
|
|
|
|
Response shape is the same as SMS login.
|
|
|
|
### POST `/auth/refresh`
|
|
|
|
Request:
|
|
|
|
```json
|
|
{
|
|
"refreshToken": "refresh_demo",
|
|
"device": {}
|
|
}
|
|
```
|
|
|
|
Response:
|
|
|
|
```json
|
|
{
|
|
"accessToken": "access_refreshed",
|
|
"refreshToken": "refresh_rotated",
|
|
"accessTokenExpiresAt": 1777189500000
|
|
}
|
|
```
|
|
|
|
M2 desktop persists only `refreshToken`; `accessToken` remains memory-only.
|
|
|
|
### GET `/auth/me`
|
|
|
|
Headers:
|
|
|
|
```http
|
|
Authorization: Bearer access_demo
|
|
```
|
|
|
|
Response:
|
|
|
|
```json
|
|
{
|
|
"user": {
|
|
"id": "user_ops_001",
|
|
"name": "王管理员",
|
|
"phone": "13800000000",
|
|
"email": "ops@example.com",
|
|
"avatar": "https://cdn.example.com/avatar.png"
|
|
},
|
|
"hotels": [
|
|
{
|
|
"id": "workspace_hangzhou_ops",
|
|
"name": "智念企业组织空间",
|
|
"brand": "智念"
|
|
}
|
|
],
|
|
"currentHotelId": "workspace_hangzhou_ops",
|
|
"accessTokenExpiresAt": 1777188600000
|
|
}
|
|
```
|
|
|
|
## 5. Config Sync
|
|
|
|
### GET `/config/sync?hotel_id=<hotelId>`
|
|
|
|
Response:
|
|
|
|
```json
|
|
{
|
|
"serverTime": 1777188000000,
|
|
"hotel": {
|
|
"id": "workspace_hangzhou_ops",
|
|
"name": "智念企业组织空间",
|
|
"brand": "智念"
|
|
},
|
|
"entitlements": [
|
|
{
|
|
"skillId": "daily-report",
|
|
"name": "日报生成助手",
|
|
"version": "1.0.0",
|
|
"enabled": true,
|
|
"category": "reporting",
|
|
"triggers": ["scheduled", "manual"],
|
|
"lastRunAt": 1777184400000
|
|
}
|
|
],
|
|
"notificationChannels": [
|
|
{
|
|
"id": "wechat_ops",
|
|
"kind": "wecom",
|
|
"label": "业务通知群",
|
|
"recipient": "room_001",
|
|
"enabled": true,
|
|
"source": "nianxx"
|
|
}
|
|
],
|
|
"featureFlags": {
|
|
"skillsSync": true,
|
|
"advancedSettings": false
|
|
},
|
|
"uiPolicy": {
|
|
"defaultPage": "today",
|
|
"showAdvancedSettings": false
|
|
}
|
|
}
|
|
```
|
|
|
|
## 6. Skills Manifest
|
|
|
|
### GET `/skills/manifest?hotel_id=<hotelId>`
|
|
|
|
Response:
|
|
|
|
```json
|
|
{
|
|
"serverTime": 1777188000000,
|
|
"hotelId": "workspace_hangzhou_ops",
|
|
"manifestVersion": "2026.04.26.1",
|
|
"skills": [
|
|
{
|
|
"skillId": "data-check",
|
|
"name": "数据检查助手",
|
|
"version": "1.2.0",
|
|
"enabled": true,
|
|
"bundleSha256": "sha256-demo",
|
|
"bundleUrl": "https://cdn.example.com/skills/data-check-1.2.0.tgz"
|
|
},
|
|
{
|
|
"skillId": "customer-reply-helper",
|
|
"name": "客户回复助手",
|
|
"version": "0.9.0",
|
|
"enabled": false,
|
|
"bundleSha256": "sha256-disabled"
|
|
}
|
|
]
|
|
}
|
|
```
|
|
|
|
M2 desktop consumes manifest metadata only. It does not download `bundleUrl`, verify signatures, or unpack bundles yet.
|
|
|
|
## 7. Client Normalization Rules
|
|
|
|
The desktop accepts:
|
|
|
|
- `camelCase` and `snake_case` aliases for known fields.
|
|
- Missing `user.name` as `未命名用户`.
|
|
- Missing hotel fields with safe empty/default values.
|
|
- Unknown skill categories as `ops-automation`.
|
|
- Unknown trigger values are ignored.
|
|
- Missing `uiPolicy.defaultPage` defaults to `today`.
|
|
- Missing arrays default to `[]`.
|
|
|
|
Malformed server responses should not break the shell, but required auth fields still fail fast:
|
|
|
|
- Missing `accessToken` after login or refresh throws `服务端未返回 access token`.
|
|
- Authenticated config/skills calls without a session throw `请先登录`.
|