From 9648ef84ee845974b1b62a3100606fca69f006ab Mon Sep 17 00:00:00 2001
From: Charlie Zhang
Date: Mon, 31 Aug 2026 16:39:31 +0800
Subject: [PATCH] Add CN host
---
.cursor/rules/demos-pages.mdc | 2 +-
README.md | 4 ++++
demos/README.md | 2 +-
demos/mvp-frontend-only/README.md | 2 +-
demos/mvp-frontend-only/index.html | 1 +
demos/mvp-frontend-only/js/config.js | 1 +
demos/temi-openapi-scenario-runner/README.md | 13 +++++++++----
demos/temi-openapi-scenario-runner/index.html | 2 +-
demos/temi-openapi-scenario-runner/src/api.ts | 5 +++--
demos/temi-openapi-scenario-runner/src/main.ts | 18 ++++++++++++------
temi-partner.openapi.yaml | 6 +++++-
11 files changed, 39 insertions(+), 17 deletions(-)
diff --git a/.cursor/rules/demos-pages.mdc b/.cursor/rules/demos-pages.mdc
index 13b77ac..03914fd 100644
--- a/.cursor/rules/demos-pages.mdc
+++ b/.cursor/rules/demos-pages.mdc
@@ -10,7 +10,7 @@ Source of truth: `demos/README.md` and `demos/assemble-pages.sh`. Keep the READM
## Token
-- Client-only: OAT in memory only; send only as `x-api-key` to hosts in `config` / OpenAPI `servers`.
+- Client-only: OAT in memory only; send only as `x-api-key` to hosts in `config` / OpenAPI `servers` (`api.robotemi.com`, `api.robotemi.cn`, `integration.dev.temi.cloud`). Use the production host that matches the organization's region.
- No `sessionStorage` / `localStorage` / cookies / analytics / CDN scripts that can read the token.
- `fetch` that carries the OAT: `redirect: 'error'`.
- Do not official-host a backend that accepts someone else's OAT.
diff --git a/README.md b/README.md
index 3bf205e..bb7e625 100644
--- a/README.md
+++ b/README.md
@@ -13,6 +13,10 @@ Usage
Authenticate every request with an Organization Access Token (OAT) in the `x-api-key` header.
+Use `https://api.robotemi.com/openapi/v1` (Production) or
+`https://api.robotemi.cn/openapi/v1` (Production-CN) for the same region as
+the organization.
+
OpenAPI control requires a **PRO** (or PRO free-trial) robot.
```
diff --git a/demos/README.md b/demos/README.md
index 6341e17..868d202 100644
--- a/demos/README.md
+++ b/demos/README.md
@@ -10,7 +10,7 @@ An OAT is a secret. Demos in this repository must not collect, store, or transmi
Acceptable patterns include:
-- **Client-only apps.** The token is entered by the user in the browser and is sent only to temi APIs. It must not leave the browser for any other destination (no analytics, logging, or third-party backends).
+- **Client-only apps.** The token is entered by the user in the browser and is sent only to temi APIs. It must not leave the browser for any other destination (no analytics, logging, or third-party backends). Use Production (`api.robotemi.com`) or Production-CN (`api.robotemi.cn`) for the same region as the organization.
- **User-deployed serverless.** A small service the user can deploy themselves, with the token supplied at runtime as a parameter or environment variable. Do not bake tokens into source, config, or hosted defaults.
Do not:
diff --git a/demos/mvp-frontend-only/README.md b/demos/mvp-frontend-only/README.md
index 6fabc23..530d8bf 100644
--- a/demos/mvp-frontend-only/README.md
+++ b/demos/mvp-frontend-only/README.md
@@ -2,7 +2,7 @@
Client-only demo of the [temi OpenAPI](https://openapi-docs.robotemi.com). No build step, no backend.
-The Organization Access Token is typed in the browser and sent only as `x-api-key` to `api.robotemi.com` or `integration.dev.temi.cloud`. It is kept in memory for this page load only — not written to `sessionStorage` or `localStorage`.
+The Organization Access Token is typed in the browser and sent only as `x-api-key` to the selected OpenAPI host: `api.robotemi.com` (Production), `api.robotemi.cn` (Production-CN), or `integration.dev.temi.cloud` (Integration). Use the production host that matches the organization's region. The token is kept in memory for this page load only — not written to `sessionStorage` or `localStorage`.
## What it does
diff --git a/demos/mvp-frontend-only/index.html b/demos/mvp-frontend-only/index.html
index 1bce7fa..f185f72 100644
--- a/demos/mvp-frontend-only/index.html
+++ b/demos/mvp-frontend-only/index.html
@@ -31,6 +31,7 @@
temi OpenAPI — frontend-only demo
diff --git a/demos/mvp-frontend-only/js/config.js b/demos/mvp-frontend-only/js/config.js
index 01f93b0..037eefa 100644
--- a/demos/mvp-frontend-only/js/config.js
+++ b/demos/mvp-frontend-only/js/config.js
@@ -1,6 +1,7 @@
/** API hosts from temi-partner.openapi.yaml `servers`. */
export const ENVIRONMENTS = {
production: 'https://api.robotemi.com/openapi/v1',
+ 'production-cn': 'https://api.robotemi.cn/openapi/v1',
integration: 'https://integration.dev.temi.cloud/openapi/v1',
}
diff --git a/demos/temi-openapi-scenario-runner/README.md b/demos/temi-openapi-scenario-runner/README.md
index 042c427..a755c4b 100644
--- a/demos/temi-openapi-scenario-runner/README.md
+++ b/demos/temi-openapi-scenario-runner/README.md
@@ -6,8 +6,11 @@ It demonstrates the complete operator flow:
`verify → discover → validate → play → poll → stop`
The interface starts in Chinese and includes an English option. It supports the
-Production and Integration temi API environments, robot selection, MOVEMENT,
-SPEAK, and START_CALL actions, final run confirmation, and status polling.
+Production (`api.robotemi.com`), Production-CN (`api.robotemi.cn`), and
+Integration temi API environments, robot selection, MOVEMENT, SPEAK, and
+START_CALL actions, final run confirmation, and status polling.
+
+Choose the production environment that matches the organization's region.
## Run locally
@@ -32,8 +35,10 @@ The demo is served over HTTP; do not open the source or `dist/index.html` with
Enter your own Organization Access Token (OAT) in the page. The demo keeps it
only in page memory and sends it as `x-api-key` directly to the selected temi
-API origin. It does not use a backend, cookies, Web Storage, analytics, or
-third-party runtime code. The token is cleared when the connection is reset,
+API origin (`api.robotemi.com`, `api.robotemi.cn`, or
+`integration.dev.temi.cloud`). Use the production host that matches the
+organization's region. It does not use a backend, cookies, Web Storage,
+analytics, or third-party runtime code. The token is cleared when the connection is reset,
the environment changes, a request returns 401, or the page closes.
Never commit an OAT, put one in a URL, or include one in logs, screenshots, or
diff --git a/demos/temi-openapi-scenario-runner/index.html b/demos/temi-openapi-scenario-runner/index.html
index 298f4c1..251e456 100644
--- a/demos/temi-openapi-scenario-runner/index.html
+++ b/demos/temi-openapi-scenario-runner/index.html
@@ -5,7 +5,7 @@
场景运行 | temi Developer
diff --git a/demos/temi-openapi-scenario-runner/src/api.ts b/demos/temi-openapi-scenario-runner/src/api.ts
index 4dbefc5..fdc29d0 100644
--- a/demos/temi-openapi-scenario-runner/src/api.ts
+++ b/demos/temi-openapi-scenario-runner/src/api.ts
@@ -1,8 +1,9 @@
-export const ENVIRONMENTS = ["production", "integration"] as const;
+export const ENVIRONMENTS = ["production", "production-cn", "integration"] as const;
export type Environment = (typeof ENVIRONMENTS)[number];
export const ENVIRONMENT_BASE_URLS: Readonly> = Object.freeze({
production: "https://api.robotemi.com/openapi/v1",
+ "production-cn": "https://api.robotemi.cn/openapi/v1",
integration: "https://integration.dev.temi.cloud/openapi/v1",
});
@@ -211,7 +212,7 @@ export class TemiApiError extends Error {
}
export function isEnvironment(value: unknown): value is Environment {
- return value === "production" || value === "integration";
+ return typeof value === "string" && (ENVIRONMENTS as readonly string[]).includes(value);
}
export class TemiApiClient {
diff --git a/demos/temi-openapi-scenario-runner/src/main.ts b/demos/temi-openapi-scenario-runner/src/main.ts
index 8e191bc..1043f2f 100644
--- a/demos/temi-openapi-scenario-runner/src/main.ts
+++ b/demos/temi-openapi-scenario-runner/src/main.ts
@@ -110,10 +110,7 @@ let discovery: RobotDiscoveryController | null = null;
let composer: SequenceComposer | null = null;
let runState: RunStateSnapshot = createRunState();
let lifecycle: PlayPollStopLifecycle | null = null;
-let environmentTransport: Record = {
- production: "unknown",
- integration: "unknown",
-};
+let environmentTransport: Record = emptyEnvironmentTransport();
let pendingRobotSerialNumber: string | null = null;
const robotStatusPanels = new Map();
@@ -1747,7 +1744,7 @@ function resetPage(message: string): void {
composer = null;
clearConnection();
runState = createRunState();
- environmentTransport = { production: "unknown", integration: "unknown" };
+ environmentTransport = emptyEnvironmentTransport();
pendingRobotSerialNumber = null;
robotResourcesLoading = false;
editingValidatedSequence = false;
@@ -1954,7 +1951,16 @@ function statusLabelFor(status: RobotStatus["status"] | undefined, targetLanguag
}
function environmentLabel(value: Environment): string {
- return value === "production" ? "Production" : "Integration";
+ if (value === "production") return copy("生产", "Production");
+ if (value === "production-cn") return copy("生产(中国)", "Production-CN");
+ return copy("集成", "Integration");
+}
+
+function emptyEnvironmentTransport(): Record {
+ return Object.fromEntries(ENVIRONMENTS.map((value) => [value, "unknown"])) as Record<
+ Environment,
+ EnvironmentTransportState
+ >;
}
function displayName(value: string | undefined, fallback: string): string {
diff --git a/temi-partner.openapi.yaml b/temi-partner.openapi.yaml
index 143c571..afc28de 100644
--- a/temi-partner.openapi.yaml
+++ b/temi-partner.openapi.yaml
@@ -13,7 +13,9 @@ info:
Authenticate every request with an Organization Access Token (OAT) in the
`x-api-key` header. Tokens are scoped; each endpoint requires the permission
- noted in its description.
+ noted in its description. Call the production host that matches the
+ organization: Production (`api.robotemi.com`) or Production-CN
+ (`api.robotemi.cn`).
OpenAPI control requires a **PRO** (or PRO free-trial) robot, matching launcher
SDK access. `GET /robots` omits BASIC robots. Robot-scoped endpoints return
@@ -23,6 +25,8 @@ info:
servers:
- url: https://api.robotemi.com/openapi/v1
description: Production
+ - url: https://api.robotemi.cn/openapi/v1
+ description: Production-CN
- url: https://integration.dev.temi.cloud/openapi/v1
description: Integration
tags: