Tiyi v3.8.0

API integration and complete reference

For v3.8.0. Automate administration with ordinary ConnectRPC HTTP JSON: POST /tiyi.v1.Service/Method. No SDK is needed. Your application's OpenAPI file belongs to request protection; this page covers the API that manages Tiyi.

Complete reference: 309 RPCs / 26 services · request/response fields · permissions. Each RPC includes its path, request, response, enums, and required permissions.

1. Choose authentication

Context Entry point Credentials
Maintenance on the Tiyi host /run/tiyi/admin.sock Unix file permissions, usually sudo; local administrator privileges
Your computer, CI, or another service Management URL; use HTTPS in production Authorization: Bearer JWT, with the user's permissions
Remote Agent Generated node-install flow Expiring, node-limited enrollment token and node identity, separate from an administrator JWT

Local read without a login:

sudo curl --fail-with-body -sS --unix-socket /run/tiyi/admin.sock \
  http://localhost/tiyi.v1.SiteService/ListSites \
  -H 'Content-Type: application/json' -d '{"page":{"page":1,"pageSize":20}}'

Do not expose the entire admin socket through a public TCP proxy. For remote automation, create a user and role with the permissions needed for its tasks.

2. Log in and obtain a token

This example needs Bash, curl, Python 3, and jq. Replace SERVER_IP below with the Tiyi host's IP address; if you have an HTTPS management URL, use that URL instead. Change the username in the script from admin to your automation user when appropriate. The password is entered without echo and kept out of shell history; temporary credentials stay in a private directory.

export TIYI_API='http://SERVER_IP:8080'
umask 077
SESSION_DIR=$(mktemp -d)
python3 - "$SESSION_DIR/login.json" <<'PY'
import getpass, json, pathlib, sys
pathlib.Path(sys.argv[1]).write_text(json.dumps({
    "username": "admin", "password": getpass.getpass("Tiyi password: ")
}))
PY
curl --fail-with-body -sS -c "$SESSION_DIR/cookies" \
  -H 'Content-Type: application/json' \
  --data-binary @"$SESSION_DIR/login.json" \
  "$TIYI_API/tiyi.v1.AuthService/Login" -o "$SESSION_DIR/session.json"
rm "$SESSION_DIR/login.json"
while jq -e '.challengeRequired == true' "$SESSION_DIR/session.json" >/dev/null; do
  python3 - "$SESSION_DIR/challenge.json" <<'PY'
import getpass, json, pathlib, sys
pathlib.Path(sys.argv[1]).write_text(json.dumps({"code": getpass.getpass("MFA / RADIUS code: ")}))
PY
  curl --fail-with-body -sS -b "$SESSION_DIR/cookies" -c "$SESSION_DIR/cookies" \
    -H 'Content-Type: application/json' --data-binary @"$SESSION_DIR/challenge.json" \
    "$TIYI_API/tiyi.v1.AuthService/CompleteAuthenticationChallenge" \
    -o "$SESSION_DIR/session.json" || break
  rm -f "$SESSION_DIR/challenge.json"
done
TIYI_TOKEN=$(jq -er '.accessToken | select(length > 0)' "$SESSION_DIR/session.json")
export TIYI_TOKEN
tiyi auth user

A successful auth user returns the current identity. Resolve login failures before proceeding; an empty token is not a successful login. With TOTP or RADIUS challenges, challengeRequired is true and the challenge cookie must accompany completion. OIDC/SAML use browser redirect flows; an identity-provider token is not a Tiyi JWT.

The CLI does not automatically persist auth login output as a session. TIYI_API and TIYI_TOKEN provide credentials for subsequent CLI calls here. Defaults are 8 hours for access tokens and 7 days for refresh tokens, configurable by the administrator.

3. Query and create a site

Define a reusable function and request the first page:

tiyi_rpc() {
  curl --fail-with-body -sS "$TIYI_API/tiyi.v1.$1" \
    -H "Authorization: Bearer $TIYI_TOKEN" \
    -H 'Content-Type: application/json' -H 'Connect-Protocol-Version: 1' \
    --data-binary "$2"
}
tiyi_rpc SiteService/ListSites '{"page":{"page":1,"pageSize":20}}'

items contains the current page; page.total is the total as a 64-bit decimal string. Increment page.page for subsequent pages.

The following creates an immediately active demo site. Start the quickstart origin first and change example names or domains already in use:

cat > api-site.json <<'JSON'
{
  "site": {
    "name": "api-example",
    "primaryHost": "api.example.com",
    "upstreams": [{"inline": {"url": "http://127.0.0.1:9000", "weight": 100}}],
    "tls": {"mode": "TLS_MODE_NONE"},
    "waf": {"enabled": true, "mode": "WAF_MODE_BLOCKING"},
    "status": "RESOURCE_STATUS_ACTIVE"
  }
}
JSON
tiyi_rpc SiteService/CreateSite @api-site.json > created-site.json
SITE_ID=$(jq -er '.site.id' created-site.json)
curl -i -H 'Host: api.example.com' http://127.0.0.1/

Expect the origin's normal response. WAF is enabled and defaults to Light when no policy is specified. Also verify attack probes and node publication results.

4. Update one field with concurrency protection

Read the latest site, then send its revision and a mask to change only the name:

jq -n --arg id "$SITE_ID" '{id:$id}' > get-site.json
tiyi_rpc SiteService/GetSite @get-site.json > current-site.json
jq '{site:{id:.site.id,name:"api-renamed"},
     revision:.site.revision,updateMask:"name"}' current-site.json > update-site.json
tiyi_rpc SiteService/UpdateSite @update-site.json

updateMask is a comma-separated JSON field-path string, not an array. Keep revision as the returned decimal string to avoid JavaScript precision loss. On aborted or a revision conflict, reread, compare, and decide the change again instead of overwriting blindly. Update behavior varies by service; some operations, including policy-layer writes, replace complete objects.

5. Refresh, log out, and follow JSON conventions

The refresh token normally arrives in an HttpOnly cookie, not the login JSON. Renew using the latest cookie:

curl --fail-with-body -sS -b "$SESSION_DIR/cookies" -c "$SESSION_DIR/cookies" \
  -H 'Content-Type: application/json' -d '{}' \
  "$TIYI_API/tiyi.v1.AuthService/Refresh" -o "$SESSION_DIR/session.json"
TIYI_TOKEN=$(jq -er '.accessToken | select(length > 0)' "$SESSION_DIR/session.json")
export TIYI_TOKEN

Refresh rotates the token. Do not concurrently reuse or repeatedly replay an old refresh cookie. When done, log out and remove this session's temporary credentials:

curl --fail-with-body -sS -b "$SESSION_DIR/cookies" \
  -H 'Content-Type: application/json' -d '{}' "$TIYI_API/tiyi.v1.AuthService/Logout"
unset TIYI_TOKEN
rm -rf -- "$SESSION_DIR"
unset SESSION_DIR
Convention Handling
JSON fields Use documented lowerCamelCase such as siteId, not apply-YAML aliases
64-bit integers Keep revisions, counts, and totals as decimal strings unless conversion is known to be safe
bytes Base64 strings, including source, reportJson, and certificate PEM fields
Enums Full symbolic names, such as RESOURCE_STATUS_ACTIVE and TLS_MODE_MANAGED_ACME
Time RFC 3339, preferably UTC, e.g. 2026-09-17T08:00:00Z
Tenant Determined by the caller; a supplied tenantId does not grant cross-tenant access
Idempotency Use idempotencyKey only where defined; exact retries retain the key and request, changed requests need a new key

6. Errors and permissions

Connect errors usually look like {"code":"permission_denied","message":"..."}. Preserve the response body and HTTP status; completion of a network request is not proof of success.

Error Next action
unauthenticated / 401 Check token, expiry, and clock; refresh or log in again
permission_denied / 403 Check the user's roles and all required method permissions
invalid_argument / 400 Check field names, types, enums, limits, and required values
already_exists / 409 Read the existing name/hostname instead of recreating it
aborted / 409 State or preview changed; reread and preview again
failed_precondition Check certificates, policies, dependencies, node capabilities, and current state
resource_exhausted / unavailable Use bounded backoff; establish whether a write already succeeded and follow its idempotency contract
unimplemented / 501 The entry point cannot perform the action; retries or fabricated fields will not enable it

SystemService.Health needs system:read; use /healthz for anonymous liveness. A schema field is not an authorization grant.

7. API document and validation publication

Start with the copyable upload and preview. Call ApiSchemaService in dependency order below. Take IDs, hashes, and approval objects from real responses:

Step Method / CLI Output needed next
Upload a file StageSchemaSource / api-document upload source.id; the request's source field is file content as base64
Review mapping and existing endpoints PreviewApiDeclaration / declaration-preview reportSha256, catalogSha256, decoded report and matching results
Save the reviewed declaration StageApiDeclaration / declaration-stage contract.id; explicit decisions for ambiguous matches; independently reviewed coverage
Compile executable checks StageSchemaProfile / profile-stage profile.id and capability/diagnostic report; unsupported checks are not enforced
Select endpoint modes StageSchemaBindings / bindings-stage bindings.id; operationKeys selects validation and enforceOperationKeys is its enforced subset
Preview publication PreviewSchemaControl / preview Complete approval, change/resource reports, and executionSupported
Submit the review ChangeSchemaControl / publish intent and state; send the original approval, reason, idempotency key, and actually reviewed acknowledgements
Check application and requests OperationControlService.GetOperationControlState, GetSchemaValidation, GetSchemaDetection Serving-node results, configured validation, and detection statistics

Catalog-only publication may omit a validation binding and does not enable request validation. A root mapping uses basePath: "". Learning, request editing, document versions, deactivation, and rollback have dedicated methods in the field reference. Hiding/restoring an asset row does not enable blocking. Catalog membership changes use StageApiCatalogChange and the same publication lifecycle.

Copyable recipe: publish the demo order endpoint in Observe

Continue in the same directory and terminal with source-result.json, declaration-preview.json, and preview-result.json from the API protection tutorial. This needs jq and assumes the single demo endpoint has been reviewed. For a real document with ambiguous matches, conflicts, or unsupported checks, resolve the report first; do not automatically accept everything.

Stage the declaration and compile checks. After reviewing mapping and reconciliation:

jq -s '.[0] + {reportSha256:.[1].reportSha256,
  catalogSha256:.[1].catalogSha256,idempotencyKey:"orders-declaration-v1"}' \
  declaration-preview.json preview-result.json > declaration-stage.json
sudo tiyi api-document declaration-stage -f declaration-stage.json > declaration-result.json
jq '. + {idempotencyKey:"orders-profile-v1"}' declaration-preview.json > profile-stage.json
sudo tiyi api-document profile-stage -f profile-stage.json > profile-result.json
jq '{siteId:.contract.siteId,contractId:.contract.id}' declaration-result.json > contract-request.json
sudo tiyi api-document declaration -f contract-request.json > contract-result.json
jq -r '.contractJson' contract-result.json | base64 -d > contract.json
jq '.operations' contract.json

The operations should contain only the demo POST /api/orders. Inspect the profile's report; these operations still have not published to traffic.

Select observation and preview impact. The next block selects every endpoint in this one-endpoint demo. For your own document, narrow operationKeys to the reviewed endpoints:

jq -n --slurpfile c declaration-result.json --slurpfile p profile-result.json \
  --slurpfile d contract.json '{siteId:$c[0].contract.siteId,
    declarationId:$c[0].contract.id,profileId:$p[0].profile.id,
    operationKeys:[$d[0].operations[].key],idempotencyKey:"orders-bindings-observe-v1"}' \
  > bindings-stage.json
sudo tiyi api-document bindings-stage -f bindings-stage.json > bindings-result.json
jq -n --slurpfile c declaration-result.json --slurpfile b bindings-result.json \
  '{siteId:$c[0].contract.siteId,action:"activate",contractId:$c[0].contract.id,
    bindingId:$b[0].bindings.id,undeclaredAction:"allow"}' > control-preview.json
sudo tiyi api-document preview -f control-preview.json > control-preview-result.json
jq '{executionSupported,approval}' control-preview-result.json
jq -r '.changesJson' control-preview-result.json | base64 -d
jq -r '.resourcesJson' control-preview-result.json | base64 -d

Require executionSupported: true and review scope, capabilities, and resource reports. If execution is unavailable or partial acceptance is required, resolve it rather than adding flags to bypass review.

Submit the reviewed change and check nodes. The acknowledgement fields below represent the review you just performed. Query the exact returned intent:

jq -e 'select(.executionSupported == true) | {approval:.approval,
  idempotencyKey:"orders-publish-observe-v1",reason:"Reviewed demo orders API",
  impactAcknowledged:true,resourceAcknowledged:true,typeOnlyAcknowledged:true}' \
  control-preview-result.json > publish-request.json
sudo tiyi api-document publish -f publish-request.json > publication-result.json
jq '{siteId:.intent.siteId,intentId:.intent.id}' publication-result.json > state-request.json
sudo curl --fail-with-body -sS --unix-socket /run/tiyi/admin.sock \
  http://localhost/tiyi.v1.OperationControlService/GetOperationControlState \
  -H 'Content-Type: application/json' --data-binary @state-request.json

Repeat the final read-only query until the target nodes have applied it, then send both tutorial probes. Observe returns 200 for both unless an independent rule blocks. These idempotency keys identify one demo revision. Use a new key for changed content and retain the same key/request for an exact retry.

Move from observation to enforcement

After validating the demo and deciding to reject type errors, create an enforced binding:

jq '.enforceOperationKeys = .operationKeys |
    .idempotencyKey = "orders-bindings-enforce-v1"' \
  bindings-stage.json > bindings-enforce.json
sudo tiyi api-document bindings-stage -f bindings-enforce.json > bindings-result.json

Rebuild the control-preview request above using the new bindings-result.json, preview again, and review the reports. For publication, change the key to orders-publish-enforce-v1 and supply the real reason for the change. Do not reuse the Observe approval. Check this intent's serving-node results, then replay both probes: valid 200, invalid type 403. To revert, select the observing binding through a new preview/publication or restore a reviewed version in console history. Editing a local JSON file alone never changes live traffic.

8. HTTP probes, telemetry, and metrics

Endpoint Method and authentication Purpose
/healthz GET, management or socket, anonymous Process liveness only, normally {"status":"ok"}
/readyz GET, local socket only Storage and built-in proxy/config readiness; failure returns 503, management listener returns 404
/metrics GET, local socket only OpenMetrics; Prometheus setup
/download/tiyi GET, anonymous Running Controller binary for Agents on the same platform
/api/v1/telemetry/qps GET, JWT + telemetry:read Optional site, window such as 1m
/api/v1/telemetry/series, topk GET, JWT + telemetry:read Time windows, dimensions, and aggregates
/api/v1/telemetry/apitree GET, JWT + telemetry:read Required site; optional base=/, depth=128, and time bounds
/api/v1/telemetry/apitree/action POST, api_inventory:write + site:read hide, restore, retire, with the current expected_revision string
/debug/* GET, local socket only Diagnostics; fields may change across versions

Telemetry HTTP JSON uses its own snake_case fields and errors shaped as {"error":{"code":...,"message":...}}. Asset block and apply_body_suggestion actions are reserved and return 501; the old confirm action is unsupported. Unknown management URLs may return the console's HTML with status 200. Check Content-Type before treating it as an API response.