Skill: cinience/aliyun-esa-manage (version 1.0.0)

--- name: aliyun-esa-manage description: Use when managing Alibaba Cloud ESA — deploy HTML/static sites via Pages, manage Edge Routines (ER) for serverless edge functions, use Edge KV for distributed key-value storage, handle site management, DNS records, cache rules, and query traffic analytics via OpenAPI/SDK. Use when working with ESA, edge deployment, edge functions, Pages, ER, KV storage, DNS, cache, site configuration, traffic analytics, bandwidth trends, or top-N rankings. --- Category: service # Edge Security Acceleration (ESA) - Pages, Edge Routine, KV, Site Management, Analytics & More Use Alibaba Cloud OpenAPI (RPC) with official Python SDK to manage all ESA capabilities. Alibaba Cloud ESA provides five core capabilities: - **Pages** — Deploy HTML or static directories to edge nodes (quick deployment flow based on Edge Routine) - **Edge Routine (ER)** — Full lifecycle management of serverless edge functions - **Edge KV** — Distributed edge key-value storage with Namespace/Key/Value management - **Site Management** — Site management, DNS records, cache rules, certificates, etc. - **Analytics** — Traffic analysis, time-series trends, Top-N rankings, bandwidth statistics, request metrics Use Python SDK uniformly to call ESA OpenAPI. ## Prerequisites - Prepare AccessKey (RAM user/role with least privilege). - Install Python SDK: `pip install alibabacloud_esa20240910 alibabacloud_tea_openapi alibabacloud_credentials` - ESA OpenAPI is RPC style; prefer SDK or OpenAPI Explorer to avoid manual signing. ## SDK quickstart ```python from alibabacloud_esa20240910.client import Client as Esa20240910Client from alibabacloud_esa20240910 import models as esa_models from alibabacloud_tea_openapi import models as open_api_models def create_client(region_id: str = "cn-hangzhou") -> Esa20240910Client: config = open_api_models.Config( region_id=region_id, endpoint="esa.cn-hangzhou.aliyuncs.com", ) return Esa20240910Client(config) ``` ## Pages — Edge Page Deployment Pages is a quick deployment flow based on Edge Routine, deploying HTML or static directories to the edge. ### HTML Page Deployment Flow ``` CreateRoutine → GetRoutineStagingCodeUploadInfo → Upload code to OSS → CommitRoutineStagingCode → PublishRoutineCodeVersion(staging) → PublishRoutineCodeVersion(production) → GetRoutine(get access URL) ``` ### Static Directory Deployment Flow ``` CreateRoutine → CreateRoutineWithAssetsCodeVersion → Package zip and upload to OSS → Poll GetRoutineCodeVersionInfo(wait for available) → CreateRoutineCodeDeployment(staging) → CreateRoutineCodeDeployment(production) → GetRoutine(get access URL) ``` ### Zip Package Structure The zip package structure depends on `EDGE_ROUTINE_TYPE` (automatically determined by `checkEdgeRoutineType` based on whether entry file and assets directory exist): - **JS_ONLY**: `routine/index.js` (bundled with esbuild or `--no-bundle` to read source files directly) - **ASSETS_ONLY**: All static files under `assets/`, maintaining original directory structure - **JS_AND_ASSETS**: `routine/index.js` + `assets/` static resources (most common) The `assets/` path is relative to `assets.directory` in configuration. Configuration priority: CLI args > `esa.jsonc` / `esa.toml`. ### Key Notes - **Function name rules**: lowercase letters/numbers/hyphens, start with lowercase letter, length >= 2 - **Same name function**: Reuse if exists, deploy new version code - Deploy to both staging and production by default - After successful deployment, get `defaultRelatedRecord` via `GetRoutine` as access domain Detailed reference: `references/pages.md` ## Edge Routine (ER) — Edge Functions Manage the complete lifecycle of serverless edge functions via Python SDK. ### Core Workflow ``` CreateRoutine → GetRoutineStagingCodeUploadInfo → Upload code to OSS → CommitRoutineStagingCode → PublishRoutineCodeVersion → (CreateRoutineRoute) → GetRoutine ``` ### API Summary - **Function Management**: `CreateRoutine`, `DeleteRoutine`, `GetRoutine`, `GetRoutineUserInfo`, `ListUserRoutines` - **Code Version**: `GetRoutineStagingCodeUploadInfo`, `CommitRoutineStagingCode`, `PublishRoutineCodeVersion`, `DeleteRoutineCodeVersion` - **Routes**: `CreateRoutineRoute`, `UpdateRoutineRoute`, `DeleteRoutineRoute`, `GetRoutineRoute`, `ListRoutineRoutes`, `ListSiteRoutes` - **Related Records**: `CreateRoutineRelatedRecord`, `DeleteRoutineRelatedRecord`, `ListRoutineRelatedRecords` ### ER Code Format ```javascript export default { async fetch(request) { return new Response("Hello", { headers: { "content-type": "text/html;charset=UTF-8" }, }); }, }; ``` Detailed reference: `references/er.md` ## Edge KV — Edge Key-Value Storage Distributed edge key-value storage, readable and writable in Edge Routine, also manageable via OpenAPI/SDK. ### Core Concepts - **Namespace**: Isolation container for KV data, Key max 512 chars, Value max 2MB (high capacity 25MB) - Supports TTL expiration: `Expiration` (Unix timestamp) or `ExpirationTtl` (seconds) ### API Summary - **Namespace**: `CreateKvNamespace`, `DeleteKvNamespace`, `GetKvNamespace`, `GetKvAccount`, `DescribeKvAccountStatus` - **Single Key Operations**: `PutKv`, `GetKv`, `GetKvDetail`, `DeleteKv`, `PutKvWithHighCapacity` - **Batch Operations**: `BatchPutKv`, `BatchDeleteKv`, `BatchPutKvWithHighCapacity`, `BatchDeleteKvWithHighCapacity`, `ListKvs` ### Quick Start ```python client = create_client() # Create namespace client.create_kv_namespace(esa_models.CreateKvNamespaceRequest(namespace="my-ns")) # Write client.put_kv(esa_models.PutKvRequest(namespace="my-ns", key="k1", value="v1")) # Read resp = client.get_kv(esa_models.GetKvRequest(namespace="my-ns", key="k1")) ``` Detailed reference: `references/kv.md` ## Site Management — Site Management Use Python SDK to manage ESA sites, DNS records, cache rules, etc. ### API behavior notes - Most list APIs support pagination via `PageNumber` + `PageSize`. - `ListSites` returns sites across all regions; no need to iterate regions. - Newly created sites start as `pending`; complete access verification via `VerifySite` to activate. - Deleting a site removes all associated configuration. - `UpdateSiteAccessType` can switch between CNAME and NS, but switching to CNAME may fail if incompatible DNS records exist. - DNS record APIs (`CreateRecord`, `ListRecords`, etc.) work for both NS and CNAME connected sites. **CNAME sites** are limited to `CNAME` and `A/AAAA` types only, and records cannot disable acceleration (proxy must stay enabled). - DNS record `Type` parameter must be exact: use `A/AAAA` (not `A`), `CNAME`, `MX`, `TXT`, `NS`, `SRV`, `CAA`. - `CreateCacheRule` supports two config types: `global` (site-wide default) and `rule` (conditional rule with match expression). ### Workflow 1) Confirm target site ID, access type (CNAME/NS), and desired action. 2) Find API group and exact operation name in `references/api_overview.md`. 3) Call API with Python SDK (preferred) or OpenAPI Explorer. 4) Verify results with describe/list APIs. 5) If you need repeatable inventory or summaries, use `scripts/` and write outputs under `output/aliyun-esa-manage/`. ### SDK priority 1) Python SDK (preferred) 2) OpenAPI Explorer 3) Other SDKs (only if Python is not feasible) ### Python SDK scripts (recommended for inventory) - List all ESA sites: `scripts/list_sites.py` - Summarize sites by plan: `scripts/summary_sites_by_plan.py` - Check site status: `scripts/check_site_status.py` - List DNS records for a site: `scripts/list_dns_records.py` ## Analytics — Traffic Analysis Query and analyze ESA site traffic data using `DescribeSiteTimeSeriesData` and `DescribeSiteTopData` APIs. ### Core Features - **Time-Series Data**: Query traffic trends with configurable time granularity - **Top-N Rankings**: Get rankings by country/IP/host/path/status code dimensions - **Multiple Metrics**: Traffic, Requests, RequestTraffic, PageView - **Rich Dimensions**: Country, province, ISP, browser, device, host, path, status code, etc. ### Two Main APIs #### 1. DescribeSiteTimeSeriesData - Time-Series Trends Query traffic trends over time, returning aggregated data points. **Time Granularity Rules:** | Time Range | Interval | Interval Value | |------------|----------|----------------| | <= 3 hours | 1 minute | `60` | | 3-12 hours | 5 minutes | `300` | | 12 hours - 1 day | 15 minutes | `900` | | 1-10 days | 1 hour | `3600` | | 10-31 days | 1 day | `86400` | #### 2. DescribeSiteTopData - Top-N Rankings Query Top-N ranking data by various dimensions. **Limit Options:** `5`, `10`, `150` ### Available Metrics (FieldName) | Field | Type | Description | |-------|------|-------------| | `Traffic` | int | Response traffic from ESA to client (bytes) | | `Requests` | int | Number of requests | | `RequestTraffic` | int | Client request traffic (bytes) | | `PageView` | int | Page views | ### Available Dimensions **Geographic Dimensions:** `ClientCountryCode` (country), `ClientProvinceCode` (province), `ClientISP` (ISP), `ClientASN` **Client Info:** `ClientIP`, `ClientIPVersion`, `ClientBrowser`, `ClientDevice`, `ClientOS` **Request Details:** `ClientRequestHost`, `ClientRequestMethod`, `ClientRequestPath`, `ClientRequestProtocol`, `ClientRequestQuery`, `ClientRequestReferer`, `ClientRequestUserAgent` **Response/Cache:** `EdgeCacheStatus`, `EdgeResponseStatusCode`, `EdgeResponseContentType`, `OriginResponseStatusCode` **Others:** `ALL` (aggregated), `SiteId` (account-level query), `Version`, `ClientSSLProtocol`, `ClientXRequestedWith` ### Error Handling | HTTP Code | Error Code | Description | |-----------|------------|-------------| | 400 | `InvalidParameter.TimeRange` | Time range exceeded (max 31 days) | | 400 | `InvalidEndTime.Mismatch` | EndTime earlier than StartTime | | 400 | `InvalidParameter.Field` | Invalid field name | | 400 | `InvalidParameter.Dimension` | Invalid dimension | | 400 | `InvalidTime.Malformed` | Time format error (use yyyy-MM-ddTHH:mm:ssZ) | Detailed reference: `references/time-series.md`, `references/top-data.md`, `references/fields.md` ## Common operation mapping ### Site Management - Create site: `CreateSite` - List sites: `ListSites` (supports `SiteName`, `Status`, `AccessType`, `Coverage` filters) - Get site details: `GetSite` - Delete site: `DeleteSite` - Check site name availability: `CheckSiteName` - Verify site ownership: `VerifySite` - Update access type: `UpdateSiteAccessType` - Update coverage: `UpdateSiteCoverage` - Get current nameservers: `GetSiteCurrentNS` - Update custom nameservers: `UpdateSiteVanityNS` - Pause/resume site: `UpdateSitePause`, `GetSitePause` - Site exclusivity: `UpdateSiteNameExclusive`, `GetSiteNameExclusive` - Version management: `ActivateVersionManagement`, `DeactivateVersionManagement` ### Site Configuration - IPv6: `GetIPv6`, `UpdateIPv6` ### DNS Records NS access: full record type support. CNAME access: only `CNAME` and `A/AAAA`, proxy must stay enabled. - Create record: `CreateRecord` - List records: `ListRecords` (supports `Type`, `RecordName`, `Proxied` filters) - Get record: `GetRecord` - Update record: `UpdateRecord` - Delete record: `DeleteRecord` - Batch create: `BatchCreateRecords` - Export records: `ExportRecords` ### Cache Rules - Create cache rule: `CreateCacheRule` - List cache rules: `ListCacheRules` - Get cache rule: `GetCacheRule` - Update cache rule: `UpdateCacheRule` - Delete cache rule: `DeleteCacheRule` **Cache rule expression notes (important):** - `CreateCacheRule` parameters are **flat**, not a nested JSON `Rule` object. - The `Rule` parameter is a match condition expression string. See **Rule Expression Syntax** section below. - Quick reminders: `ends_with()`/`starts_with()` must use function-call style; `matches` (regex) requires standard plan or above. - Set edge cache TTL with `--EdgeCacheMode override_origin --EdgeCacheTtl <seconds>`. ## Rule Expression Syntax ESA uses a unified rule engine expression syntax across multiple features (cache rules, WAF custom rules, rate limiting, URL rewrite, header modification, etc.). ### When to use Use this syntax for the `Rule` parameter in any ESA API that accepts a match condition expression: - `CreateCacheRule` / `UpdateCacheRule` - Cache rules - `CreateWafRule` / `UpdateWafRule` - WAF custom rules - `CreateRatePlanRule` - Rate limiting rules - `CreateRewriteUrlRule` / `UpdateRewriteUrlRule` - URL rewrite rules - Origin rules, redirect rules, header modification rules, etc. ### Expression format ``` (condition) (condition1 and condition2) (condition1) or (condition2) ``` Max nesting depth: **2 levels**. ### Operator syntax - two styles **Infix style** (operator between field and value): ``` (field eq "value") (field ne "value") (field contains "value") (field in {"value1" "value2"}) (field matches "regex") ``` **Function style** (operator wraps field): ``` (starts_with(field, "value")) (ends_with(field, "value")) (exists(field)) (len(field) gt 100) (lower(field) eq "value") ``` ### Common patterns ```bash # Match file extension --Rule '(http.request.uri.path.extension eq "html")' # Match multiple extensions --Rule '(http.request.uri.path.extension in {"js" "css" "png" "jpg"})' # Match URL prefix --Rule '(starts_with(http.request.uri, "/api/"))' # Match URL suffix --Rule '(ends_with(http.request.uri, ".html"))' # Match URL containing substring (value MUST start with /) --Rule '(http.request.uri contains "/test")' # Match specific host --Rule '(http.host eq "www.example.com")' # Combined conditions --Rule '(http.request.uri contains "/test" and ip.geoip.country eq "CN")' # Match by country --Rule '(ip.geoip.country eq "CN")' # Exclude path --Rule '(not starts_with(http.request.uri, "/admin/"))' # Negating set membership --Rule '(not http.host in {"a.com" "b.com"})' ``` ### Key Gotchas 1. `ends_with` and `starts_with` must use **function-call syntax**, NOT infix. 2. `matches` (regex) requires **standard plan or above**; basic plan returns `RuleRegexQuotaCheckFailed`. 3. `contains` with URI must include path separator: `"/test"` is correct; `"test"` alone causes `CompileRuleError`. 4. List values in `in` operator are **space-separated** inside braces: `{"a.com" "b.com"}`. 5. Outer parentheses are **optional** for single conditions. 6. Use `ne` for "not equal", **never** use `not...eq`. 7. Use `not...in` for negating set membership (not before field), not `not in`. ### Plan Limitations | Plan | eq/ne/in/starts_with/ends_with | contains | matches (regex) | |------|-------------------------------|----------|----------------| | Basic | Supported | Supported | Not supported | | Standard | Supported | Supported | Supported | | Enterprise | Supported | Supported | Supported | ## AccessKey priority (must follow, align with README) 1) Environment variables: `ALICLOUD_ACCESS_KEY_ID` / `ALICLOUD_ACCESS_KEY_SECRET` / `ALICLOUD_REGION_ID` Region policy: `ALICLOUD_REGION_ID` is an optional default. If unset, decide the most reasonable region for the task; if unclear, ask the user. 2) Shared config file: `~/.alibabacloud/credentials` (region still from env) ### Auth setup (README-aligned) Environment variables: ```bash export ALICLOUD_ACCESS_KEY_ID="your-ak" export ALICLOUD_ACCESS_KEY_SECRET="your-sk" export ALICLOUD_REGION_ID="cn-hangzhou" ``` Also supported by the Alibaba Cloud SDKs: ```bash export ALIBABA_CLOUD_ACCESS_KEY_ID="your-ak" export ALIBABA_CLOUD_ACCESS_KEY_SECRET="your-sk" ``` Shared config file: `~/.alibabacloud/credentials` ```ini [default] type = access_key [REDACTED_SECRET] [REDACTED_SECRET] ``` ## API discovery - Product code: `ESA` - Default API version: `2024-09-10` - Metadata endpoint: `https://api.aliyun.com/meta/v1/products/ESA/versions/2024-09-10/api-docs.json` - Use OpenAPI metadata endpoints to list APIs and get schemas (see references). ## Output policy If you need to save responses or generated artifacts, write them under: `output/aliyun-esa-manage/` ## References ### Pages, ER & KV - **Pages Deployment Reference**: `references/pages.md` - **Edge Routine Reference**: `references/er.md` - **Edge KV Storage Reference**: `references/kv.md` ### Site Management - API overview: `references/api_overview.md` - Endpoints: `references/endpoints.md` - Sites: `references/sites.md` - DNS records: `references/dns-records.md` - Cache: `references/cache.md` - Sources: `references/sources.md` - **Rule expression - generation guide**: `references/rule-generation-guide.md` - **Rule expression - match fields**: `references/rule-match-fields.md` - **Rule expression - operators**: `references/rule-operators.md` - **Rule expression - examples**: `references/rule-examples.md` ### Analytics - **Time-Series Data API**: `references/time-series.md` - **Top-N Data API**: `references/top-data.md` - **Metrics and Dimensions Reference**: `references/fields.md`

path: agents/openai.yaml
content: interface:
  display_name: "Alibaba Cloud Network ESA"
  short_description: "Edge security acceleration workflows"
  default_prompt: "Use $alicloud-network-esa to complete this network/esa task on Alibaba Cloud."


path: references/api_overview.md
content: # ESA OpenAPI overview (2024-09-10) - Pages, ER, KV, Site Management, DNS & Cache

API index for Pages deployment, Edge Routine, Edge KV, site management, configuration, DNS records, and cache rules.
All APIs via Python SDK (`alibabacloud_esa20240910`).

## Pages (Based on Edge Routine)

Underlying ER API calls, see `references/pages.md` for complete flow.

### HTML Deployment Core APIs
- CreateRoutine - Create edge function
- GetRoutineStagingCodeUploadInfo - Get code upload OSS signature
- CommitRoutineStagingCode - Submit code version
- PublishRoutineCodeVersion - Publish to staging/production
- GetRoutine - Get access URL

### Static Directory Deployment Core APIs
- CreateRoutine - Create edge function
- CreateRoutineWithAssetsCodeVersion - Create code version with assets
- GetRoutineCodeVersionInfo - Query version build status
- CreateRoutineCodeDeployment - Deploy by percentage to specified environment
- GetRoutine - Get access URL

## Edge Routine (ER)

### Function Management
- CreateRoutine - Create edge function
- DeleteRoutine - Delete edge function
- GetRoutine - Get edge function details
- GetRoutineUserInfo - Get user edge function info
- ListUserRoutines - Paginate all edge functions

### Code Version
- GetRoutineStagingCodeUploadInfo - Get code upload info
- CommitRoutineStagingCode - Submit staging code version
- PublishRoutineCodeVersion - Publish code version to staging/production
- DeleteRoutineCodeVersion - Delete code version
- CreateRoutineWithAssetsCodeVersion - Create code version with assets (for static directory deployment)
- GetRoutineCodeVersionInfo - Get code version status
- CreateRoutineCodeDeployment - Create code deployment (for assets deployment)
- ListRoutineCodeVersions - Paginate code versions
- GetRoutineCodeVersion - Query single code version details
- UpdateRoutineConfigDescription - Update function description

### Route Management
- CreateRoutineRoute - Create function route
- DeleteRoutineRoute - Delete function route
- GetRoutineRoute - Get route details
- UpdateRoutineRoute - Update function route
- ListRoutineRoutes - List function routes
- ListSiteRoutes - List site routes

### Related Record Management
- CreateRoutineRelatedRecord - Create function related record (domain)
- DeleteRoutineRelatedRecord - Delete function related record
- ListRoutineRelatedRecords - List function related records

## Edge KV

Edge key-value storage, supports Namespace and Key-Value management.

### Namespace Management
- CreateKvNamespace - Create KV storage space
- DeleteKvNamespace - Delete KV storage space
- GetKvNamespace - Query single namespace info
- GetKvAccount - Query account KV usage info and all namespaces
- DescribeKvAccountStatus - Query if Edge KV is enabled

### Single Key Operations
- PutKv - Write key-value pair (≤2MB)
- PutKvWithHighCapacity - Write large capacity key-value pair (≤25MB)
- GetKv - Read key's value
- GetKvDetail - Read key-value and TTL info
- DeleteKv - Delete key-value pair

### Batch Operations
- BatchPutKv - Batch write key-value pairs (≤2MB)
- BatchPutKvWithHighCapacity - Batch write large capacity (≤100MB)
- BatchDeleteKv - Batch delete key-value pairs (≤10000)
- BatchDeleteKvWithHighCapacity - Batch delete large capacity (≤100MB)
- ListKvs - List all keys under namespace (supports prefix filter and pagination)

## Site Management

- CreateSite - Add site
- ListSites - List sites (supports pagination and filters)
- GetSite - Get site details
- DeleteSite - Delete site
- CheckSiteName - Check site name availability
- VerifySite - Verify site ownership
- UpdateSiteAccessType - Update access type (CNAME/NS)
- UpdateSiteCoverage - Update coverage area
- GetSiteCurrentNS - Get current NS servers
- UpdateSiteVanityNS - Update custom NS
- UpdateSitePause - Pause/resume site proxy
- GetSitePause - Get site proxy status
- UpdateSiteNameExclusive - Set site exclusive
- GetSiteNameExclusive - Get site exclusive status
- ActivateVersionManagement - Enable version management
- DeactivateVersionManagement - Disable version management

## Site Configuration

- GetIPv6 - Get IPv6 config
- UpdateIPv6 - Update IPv6 config

## DNS Records

NS access: supports all record types. CNAME access: only `CNAME` and `A/AAAA`, and proxy (acceleration) cannot be disabled.

- CreateRecord - Create DNS record
- ListRecords - List DNS records (supports Type, RecordName, Proxied filters)
- GetRecord - Get DNS record details
- UpdateRecord - Update DNS record
- DeleteRecord - Delete DNS record
- BatchCreateRecords - Batch create DNS records
- ExportRecords - Export DNS records

## Cache Rules

- CreateCacheRule - Create cache rule
- ListCacheRules - List cache rules
- GetCacheRule - Get cache rule details
- UpdateCacheRule - Update cache rule
- DeleteCacheRule - Delete cache rule

## References

- Official API list: https://next.api.aliyun.com/document/ESA/2024-09-10/overview
- API metadata: https://api.aliyun.com/meta/v1/products/ESA/versions/2024-09-10/api-docs.json


path: references/cache.md
content: # ESA Cache

Cache rules for ESA sites.

## Cache Rules

Cache rules control how content is cached at ESA edge nodes.

### Common operations

- Create: `CreateCacheRule`
- List: `ListCacheRules`
- Query: `GetCacheRule`
- Update: `UpdateCacheRule`
- Delete: `DeleteCacheRule`

### Rule types

- `global`: Site-wide default cache configuration
- `rule`: Conditional rule with match expression

### CreateCacheRule parameters

Parameters are **flat** (not a nested JSON object). Key parameters:

Required:
- `SiteId`: Site ID (Long)
- `Rule`: Match condition expression (String). **For rule expression syntax, see `rule-generation-guide.md` and related rule reference files in this directory.**

Optional:
- `RuleName`: Rule name (not required for `global` type)
- `RuleEnable`: `on` / `off` (not required for `global` type)
- `EdgeCacheMode`: Edge cache mode
  - `follow_origin`: Follow origin cache policy (default)
  - `no_cache`: Do not cache
  - `override_origin`: Override origin cache policy
  - `follow_origin_bypass`: Follow origin if exists, otherwise no cache
- `EdgeCacheTtl`: Edge cache TTL in seconds (e.g. 864000 = 10 days)
- `BrowserCacheMode`: Browser cache mode (`no_cache`, `follow_origin`, `override_origin`)
- `BrowserCacheTtl`: Browser cache TTL in seconds
- `QueryStringMode`: Query string handling (`ignore_all`, `exclude_query_string`, `reserve_all`, `include_query_string`)
- `BypassCache`: Bypass cache mode (`cache_all`, `bypass_all`)
- `ServeStale`: Serve stale cache when origin unavailable (`on`/`off`)
- `SortQueryStringForCache`: Sort query string for cache key (`on`/`off`)

### Rule expression syntax

**See rule expression reference files in this directory for complete documentation:**
- `rule-generation-guide.md` - Generation guide from natural language
- `rule-match-fields.md` - All match fields (HTTP, IP/Geo, Map fields)
- `rule-operators.md` - All operators (string/numeric comparison, function-style, negation, logical)
- `rule-examples.md` - Scenario examples and common mistakes

Quick reminder for cache rules:
- Parameters are **flat** (not a nested JSON `Rule` object).
- `ends_with`/`starts_with` use **function-call syntax**: `(ends_with(http.request.uri, ".html"))`.
- `matches` (regex) requires standard plan or above.

### Cache TTL options

- Override origin: set `EdgeCacheMode` to `override_origin` + `EdgeCacheTtl` in seconds
- Follow origin: set `EdgeCacheMode` to `follow_origin`
- Browser cache: set `BrowserCacheMode` + `BrowserCacheTtl`
- Common TTL values: 3600 (1h), 86400 (1d), 604800 (7d), 864000 (10d), 2592000 (30d)

## Tiered Cache

Multi-level cache architecture configuration.

- `GetTieredCache`: Get current tiered cache config
- `UpdateTieredCache`: Update tiered cache config

Cache architecture modes:
- `edge_regional`: Regional edge caching
- `edge_smart`: Smart edge caching

## Cache Tag

Cache tag configuration for granular cache control.

- `GetCacheTag`: Get cache tag config
- `UpdateCacheTag`: Update cache tag config

## References

- CreateCacheRule: https://help.aliyun.com/zh/esa/developer-reference/api-esa-2024-09-10-createcacherule
- ListCacheRules: https://help.aliyun.com/zh/esa/developer-reference/api-esa-2024-09-10-listcacherules


path: references/dns-records.md
content: # ESA DNS Records

DNS records management for ESA sites.

## Access Type & Record Restrictions

- **NS access**: Full DNS record management via API, supports all record types (A/AAAA, CNAME, MX, TXT, NS, SRV, CAA).
- **CNAME access**: DNS record APIs are available, but with restrictions:
  - **Only `CNAME` and `A/AAAA` record types** are allowed. Other types (MX, TXT, NS, SRV, CAA) will fail.
  - Records **cannot disable acceleration** (proxy must remain enabled, i.e. `Proxied` must be `true`).

## Common operations

- Create: `CreateRecord`
- List: `ListRecords` (supports pagination and filters)
- Query: `GetRecord`
- Update: `UpdateRecord`
- Delete: `DeleteRecord`
- Batch create: `BatchCreateRecords`
- Export: `ExportRecords`

## Supported record types

| Type | NS access | CNAME access |
|------|-----------|-------------|
| `A/AAAA` | Supported | Supported (must enable proxy) |
| `CNAME` | Supported | Supported (must enable proxy) |
| `MX` | Supported | Not supported |
| `TXT` | Supported | Not supported |
| `NS` | Supported | Not supported |
| `SRV` | Supported | Not supported |
| `CAA` | Supported | Not supported |

**Note**: Record type must be exact: use `A/AAAA` (not just `A`).

## ListRecords filters

- `Type`: Record type (e.g., `A/AAAA`, `CNAME`)
- `RecordName`: Record name (fuzzy match)
- `Proxied`: Whether proxied through ESA (true/false)

## CreateRecord parameters

Required:
- `SiteId`: Site ID
- `RecordName`: **Must be the full domain name** (e.g., `www.example.com`), not just the subdomain prefix (e.g., `www`). The suffix must match the site name, otherwise returns `InvalidParameter.InvalidRecordNameSuffix`.
- `Type`: Record type
- `Data`: Record value in JSON format (e.g., `{"Value":"1.2.3.4"}` for A/AAAA, `{"Value":"target.com"}` for CNAME)
- `Ttl`: Time to live (seconds). Set to `1` for system-determined TTL.

Conditionally required:
- `BizName`: **Required when `Proxied` is `true`** (i.e., acceleration enabled). Valid values: `web`, `api`, `image_video`. Omitting it when proxy is on causes `InvalidParameter.InvalidBiz`.
- `SourceType`: Required for CNAME records. Valid values: `OSS`, `S3`, `LB`, `OP`, `Domain`. Defaults to `Domain` if omitted.

Optional:
- `Proxied`: Enable ESA proxy/acceleration (default: false). CNAME-access sites must set this to `true`.
- `Priority`: Priority for MX/SRV records.
- `HostPolicy`: Origin host policy for CNAME records: `follow_hostname` or `follow_origin_domain`.
- `Comment`: Record comment (max 100 characters).

### Key gotchas

1. **RecordName must be full domain**: `qodertest.qoder.weiyigirl.top`, not `qodertest`.
2. **BizName is required when Proxied=true**: Without it, API returns `InvalidParameter.InvalidBiz` with misleading message "business type is empty or incorrect".
3. **CNAME-access sites must enable proxy**: Setting `Proxied=false` on CNAME-access sites will fail.

## Behavioral notes

- Record names must be unique within the same type
- Deleting a record that doesn't exist returns success (idempotent)
- `BatchCreateRecords` can create multiple records of different types at once
- `ExportRecords` returns all DNS records in BIND zone file format

## References

- CreateRecord: https://help.aliyun.com/zh/esa/developer-reference/api-esa-2024-09-10-createrecord
- ListRecords: https://help.aliyun.com/zh/esa/developer-reference/api-esa-2024-09-10-listrecords


path: references/endpoints.md
content: # ESA OpenAPI endpoints

ESA uses RPC-style OpenAPI. Prefer SDK or OpenAPI Explorer.

## Public endpoint

- Default: `esa.cn-hangzhou.aliyuncs.com`
- Pattern: `esa.<region-id>.aliyuncs.com`

## Common endpoints

| Region | Endpoint |
|--------|----------|
| cn-hangzhou | esa.cn-hangzhou.aliyuncs.com |
| cn-shanghai | esa.cn-shanghai.aliyuncs.com |
| cn-beijing | esa.cn-beijing.aliyuncs.com |
| ap-southeast-1 | esa.ap-southeast-1.aliyuncs.com |

## Notes

- API version: `2024-09-10`
- ESA is a global service; most operations work from any endpoint.
- Use `cn-hangzhou` as default if region is unspecified.

## References

- Endpoint list: https://www.alibabacloud.com/help/en/esa/developer-reference/endpoints


path: references/er.md
content: # Edge Routine (ER) — Edge Function Reference

ESA Edge Routine is a serverless edge function service where code runs on global edge nodes. Supports full lifecycle management: creation, code submission, deployment, route configuration, and record management.

Manage Edge Routine via Python SDK calling ESA OpenAPI.

## API List

### Function Management

| API | Description | Key Parameters |
|-----|-------------|----------------|
| `CreateRoutine` | Create edge function | `Name`(required, lowercase letters/numbers/hyphens, >=2 chars), `Description`(optional) |
| `DeleteRoutine` | Delete edge function | `Name`(required) |
| `GetRoutine` | Get edge function details, including code version list, related records, default access domain | `Name`(required) |
| `GetRoutineUserInfo` | Get user edge function overview info | No parameters |
| `ListUserRoutines` | Paginate all edge functions under account | `PageNumber`, `PageSize` |

### Code Version Management

| API | Description | Key Parameters |
|-----|-------------|----------------|
| `GetRoutineStagingCodeUploadInfo` | Get signature info for code upload to OSS | `Name`(required) |
| `CommitRoutineStagingCode` | Submit staging code, generate formal code version | `Name`(required), `CodeDescription`(optional) |
| `PublishRoutineCodeVersion` | Publish code version to staging/production | `Name`(required), `Env`(required, "staging"/"production"), `CodeVersion`(required) |
| `DeleteRoutineCodeVersion` | Delete code version | `Name`(required), `CodeVersion`(required) |
| `CreateRoutineWithAssetsCodeVersion` | Create code version with assets (for static file deployment) | `Name`(required), `CodeDescription`(optional) |
| `GetRoutineCodeVersionInfo` | Get code version status (init/available/failed) | `Name`(required), `CodeVersion`(required) |
| `CreateRoutineCodeDeployment` | Deploy code version to specified environment by percentage (for assets deployment) | `Name`(required), `Env`(required), `Strategy`(required), `CodeVersions`(required, JSON) |
| `ListRoutineCodeVersions` | Paginate function's code versions | `Name`(required), `PageNumber`, `PageSize` |
| `GetRoutineCodeVersion` | Query single code version details | `Name`(required), `CodeVersion`(required) |

### Route Management

| API | Description | Key Parameters |
|-----|-------------|----------------|
| `CreateRoutineRoute` | Create route | `SiteId`(required), `Route`(path, e.g. `test.example.com/*`), `RoutineName`(required), `RouteName`(required), `RouteEnable`("on"/"off"), `Bypass`("on"/"off") |
| `UpdateRoutineRoute` | Update route configuration | `SiteId`(required), `ConfigId`(required), `RouteName`(required), `RouteEnable`(required), `Rule`(required), `RoutineName`(required), `Bypass`(required) |
| `DeleteRoutineRoute` | Delete route | `SiteId`(required), `ConfigId`(required) |
| `GetRoutineRoute` | Get route details | `SiteId`(required), `ConfigId`(required) |
| `ListRoutineRoutes` | List all routes for a function | `RoutineName`(required), `RouteName`(optional filter), `PageNumber`, `PageSize` |
| `ListSiteRoutes` | List all routes for a site | `SiteId`(required), `RouteName`(optional filter), `PageNumber`, `PageSize` |

### Related Record Management

| API | Description | Key Parameters |
|-----|-------------|----------------|
| `CreateRoutineRelatedRecord` | Create function related record (domain), triggers function execution | `Name`(required), `SiteId`(required), `RecordName`(required) |
| `DeleteRoutineRelatedRecord` | Delete related record | `Name`(required), `SiteId`(required), `RecordName`(required), `RecordId`(optional) |
| `ListRoutineRelatedRecords` | List all related records for a function | `Name`(required), `PageNumber`, `PageSize`, `SearchKeyWord`(optional) |

## Standard Workflow

### Create and Deploy Edge Function (Complete Flow)

```
1. CreateRoutine                           → Create function
2. GetRoutineStagingCodeUploadInfo          → Get upload signature
3. Upload code to OSS (POST with signature) → Code upload
4. CommitRoutineStagingCode                 → Submit code version
5. PublishRoutineCodeVersion(env=staging)   → Deploy to staging
6. PublishRoutineCodeVersion(env=production)→ Deploy to production
7. (Optional) CreateRoutineRoute           → Bind custom domain route
8. (Optional) CreateRoutineRelatedRecord   → Create related record
9. GetRoutine                              → Get details, obtain default access URL
```

### Code Format Requirements

Edge Routine code must export `fetch` handler:

```javascript
async function handleRequest(request) {
  return new Response("Hello World", {
    headers: { "content-type": "text/html;charset=UTF-8" },
  });
}

export default {
  async fetch(request) {
    return handleRequest(request);
  },
};
```

### Route Pattern Explanation

Route's `Rule` field uses ESA rule expression, for example:
- `(http.host eq "test.example.com" and starts_with(http.request.uri.path, "/"))`

Simplified path format (e.g. `test.example.com/*`) needs to be converted to rule expression:
- Domain prefix `*` = `ends_with(http.host, ".example.com")`
- Path suffix `*` = `starts_with(http.request.uri.path, "/")`

## Python SDK Usage

```python
from alibabacloud_esa20240910.client import Client as Esa20240910Client
from alibabacloud_esa20240910 import models as esa_models
from alibabacloud_tea_openapi import models as open_api_models
import requests


def create_client(region_id: str = "cn-hangzhou") -> Esa20240910Client:
    config = open_api_models.Config(
        region_id=region_id,
        endpoint="esa.cn-hangzhou.aliyuncs.com",
    )
    return Esa20240910Client(config)


# Create edge function
def create_routine(name: str, description: str = ""):
    client = create_client()
    request = esa_models.CreateRoutineRequest(name=name, description=description)
    return client.create_routine(request)


# List edge functions
def list_routines():
    client = create_client()
    resp = client.get_routine_user_info()
    return resp.body


# Get function details
def get_routine(name: str):
    client = create_client()
    request = esa_models.GetRoutineRequest(name=name)
    return client.get_routine(request)


# Delete edge function
def delete_routine(name: str):
    client = create_client()
    request = esa_models.DeleteRoutineRequest(name=name)
    return client.delete_routine_with_options(request)


# Upload code and deploy (complete flow)
def deploy_code(name: str, code: str, env: str = "production"):
    client = create_client()

    # 1. Get upload signature
    upload_info = client.get_routine_staging_code_upload_info(
        esa_models.GetRoutineStagingCodeUploadInfoRequest(name=name)
    )
    oss_config = upload_info.body.oss_post_config

    # 2. Upload code to OSS
    form_data = {
        "OSSAccessKeyId": oss_config.ossaccess_key_id,
        "Signature": oss_config.signature,
        "callback": oss_config.callback,
        "x:codeDescription": oss_config.x_code_description,
        "policy": oss_config.policy,
        "key": oss_config.key,
    }
    requests.post(oss_config.url, data=form_data, files={"file": code.encode()})

    # 3. Submit code version
    commit_resp = client.commit_routine_staging_code(
        esa_models.CommitRoutineStagingCodeRequest(name=name)
    )
    code_version = commit_resp.body.code_version

    # 4. Deploy
    client.publish_routine_code_version(
        esa_models.PublishRoutineCodeVersionRequest(
            name=name, env=env, code_version=code_version
        )
    )
    return code_version
```

### Route Management

```python
# Create route
def create_route(site_id: int, routine_name: str, route_name: str, rule: str):
    client = create_client()
    request = esa_models.CreateRoutineRouteRequest(
        site_id=site_id,
        routine_name=routine_name,
        route_name=route_name,
        rule=rule,
        route_enable="on",
        bypass="off",
    )
    return client.create_routine_route(request)


# List function routes
def list_routine_routes(routine_name: str):
    client = create_client()
    request = esa_models.ListRoutineRoutesRequest(routine_name=routine_name)
    return client.list_routine_routes(request)
```

### Related Record Management

```python
# Create related record
def create_related_record(name: str, site_id: int, record_name: str):
    client = create_client()
    request = esa_models.CreateRoutineRelatedRecordRequest(
        name=name, site_id=site_id, record_name=record_name
    )
    return client.create_routine_related_record(request)
```


path: references/fields.md
content: This document introduces the field names and their meanings returned when calling ESA data analysis APIs. These fields are data analysis results obtained through API calls, used to support deeper data analysis and business insights. Through these fields, you can get detailed traffic, requests, cache status, and other information to comprehensively understand business performance and system operation status.

## **Metrics**

Data analysis provides you with rich data metric values, including response traffic, request count, request traffic, page views, etc. These metrics can comprehensively show your business performance and help you deeply understand business activity, traffic distribution, user behavior patterns, and system performance. Through these detailed data analyses, you can more accurately evaluate business operation status, timely discover potential issues, and formulate corresponding optimization strategies to improve user experience and business efficiency.

| **Field Name** | **Data Type** | **Description** |
| --- | --- | --- |
| Traffic | int | Size of ESA node response returned to client, unit: Byte |
| Requests | int | Number of requests |
| RequestTraffic | int | Size of client request, unit: Byte |
| PageView | int | Page views |

## **Dimensions**

Data analysis provides multiple dimensions for data metrics, helping you analyze business performance from different angles. Helps you comprehensively understand traffic geographic distribution, user behavior patterns, request details, cache status, and system performance. Multi-dimensional data analysis not only helps optimize business processes and improve user experience, but also helps you quickly locate issues and formulate targeted solutions to better manage business operations.

| **Field Name** | **Description** |
| --- | --- |
| ALL | User dimension full data |
| ClientASN | [Autonomous System Number (ASN)](https://help.aliyun.com/zh/edge-security-acceleration/esa/support/what-is-asn) information parsed from client IP address |
| ClientBrowser | Client browser type |
| ClientCountryCode | `ISO-3166 Alpha-2 Code` parsed from client IP address |
| ClientDevice | Client device type |
| ClientIP | Client IP that established connection with ESA node |
| ClientIPVersion | Client IP version that established connection with ESA node |
| ClientISP | ISP information parsed from client IP address |
| ClientOS | Client system model |
| ClientProvinceCode | China mainland province information parsed from client IP address |
| ClientRequestHost | Client request `Host` information |
| ClientRequestMethod | Client request `HTTP Method` information |
| ClientRequestPath | Client request path information |
| ClientRequestProtocol | Client request protocol information |
| ClientRequestQuery | Client request `Query` information |
| ClientRequestReferer | Client request `Referer` information |
| ClientRequestUserAgent | Client request `User-Agent` information |
| ClientSSLProtocol | Client SSL protocol version, `-` indicates no SSL used |
| ClientXRequestedWith | Client `X-Requested-With` request header |
| EdgeCacheStatus | Cache status of client request |
| EdgeResponseContentType | `Content-Type` information of ESA node response |
| EdgeResponseStatusCode | Status code returned by ESA node response to client |
| OriginResponseStatusCode | Origin response status code |
| SiteId | Current site ID |
| Version | Version management version number |


path: references/kv.md
content: # Edge KV — Edge Key-Value Storage Reference

ESA Edge KV is a distributed edge key-value storage service, readable and writable in Edge Routine, also manageable via OpenAPI. Suitable for edge configuration distribution, feature flags, A/B testing, and other scenarios.

## Core Concepts

- **Namespace (Storage Space)**: Isolation container for KV data, each account can create multiple namespaces
- **Key**: Key name, max 512 characters, cannot contain spaces or backslashes
- **Value**: Value, standard API max 2MB, high capacity API max 25MB
- **TTL**: Optional expiration time, supports absolute timestamp (Expiration) or relative seconds (ExpirationTtl)

## Limits

| Limit Item | Value |
|------------|-------|
| Max Key length | 512 characters |
| Single Value max (PutKv/BatchPutKv) | 2 MB |
| Single Value max (PutKvWithHighCapacity) | 25 MB |
| Batch request body max (BatchPutKvWithHighCapacity/BatchDeleteKvWithHighCapacity) | 100 MB |
| BatchDeleteKv max keys per request | 10,000 |
| Single Namespace max capacity | 1 GB |
| ListKvs pagination limit | PageNumber × PageSize ≤ 50,000 |

## API List

### Namespace Management

| API | Description | Key Parameters |
|-----|-------------|----------------|
| `CreateKvNamespace` | Create KV storage space | `Namespace`(required, string), `Description`(optional) |
| `DeleteKvNamespace` | Delete KV storage space | `Namespace`(required, string) |
| `GetKvNamespace` | Query single namespace info | `Namespace`(required, string) |
| `GetKvAccount` | Query account KV usage info and all namespaces | No parameters |
| `DescribeKvAccountStatus` | Query if Edge KV is enabled | No parameters |

### Single Key Operations

| API | Description | Key Parameters |
|-----|-------------|----------------|
| `PutKv` | Write key-value pair (≤2MB) | `Namespace`(required), `Key`(required), `Value`(body, required), `Expiration`(optional, Unix timestamp), `ExpirationTtl`(optional, seconds), `Base64`(optional, bool) |
| `PutKvWithHighCapacity` | Write large capacity key-value pair (≤25MB) | Same as PutKv, but via SDK body method |
| `GetKv` | Read key's value | `Namespace`(required), `Key`(required), `Base64`(optional, bool) |
| `GetKvDetail` | Read key-value and TTL | `Namespace`(required), `Key`(required) |
| `DeleteKv` | Delete key-value pair | `Namespace`(required), `Key`(required) |

### Batch Operations

| API | Description | Key Parameters |
|-----|-------------|----------------|
| `BatchPutKv` | Batch write key-value pairs (≤2MB) | `Namespace`(required), body is JSON array `[{Key, Value, Expiration?, ExpirationTtl?}]` |
| `BatchPutKvWithHighCapacity` | Batch write large capacity (≤100MB) | Same as above, via SDK body method |
| `BatchDeleteKv` | Batch delete key-value pairs (≤10000) | `Namespace`(required), body is JSON array `["key1", "key2", ...]` |
| `BatchDeleteKvWithHighCapacity` | Batch delete large capacity (≤100MB) | Same as above, via SDK body method |
| `ListKvs` | List all keys in namespace | `Namespace`(required), `Prefix`(optional), `PageNumber`(optional), `PageSize`(optional, default 20, max 100) |

## Python SDK Usage

### Installation

```bash
pip install alibabacloud_esa20240910 alibabacloud_tea_openapi alibabacloud_credentials
```

### Namespace Management

```python
from alibabacloud_esa20240910.client import Client as Esa20240910Client
from alibabacloud_esa20240910 import models as esa_models
from alibabacloud_tea_openapi import models as open_api_models


def create_client(region_id: str = "cn-hangzhou") -> Esa20240910Client:
    config = open_api_models.Config(
        region_id=region_id,
        endpoint="esa.cn-hangzhou.aliyuncs.com",
    )
    return Esa20240910Client(config)


# Create namespace
def create_namespace(name: str, description: str = ""):
    client = create_client()
    request = esa_models.CreateKvNamespaceRequest(
        namespace=name,
        description=description,
    )
    return client.create_kv_namespace(request)


# List all namespaces (via GetKvAccount)
def list_namespaces():
    client = create_client()
    request = esa_models.GetKvAccountRequest()
    resp = client.get_kv_account(request)
    return resp.body


# Delete namespace
def delete_namespace(name: str):
    client = create_client()
    request = esa_models.DeleteKvNamespaceRequest(namespace=name)
    return client.delete_kv_namespace(request)
```

### Key-Value Operations

```python
# Write key-value pair
def put_kv(namespace: str, key: str, value: str, ttl: int = None):
    client = create_client()
    request = esa_models.PutKvRequest(
        namespace=namespace,
        key=key,
        value=value,
    )
    if ttl:
        request.expiration_ttl = ttl
    return client.put_kv(request)


# Read key's value
def get_kv(namespace: str, key: str):
    client = create_client()
    request = esa_models.GetKvRequest(
        namespace=namespace,
        key=key,
    )
    return client.get_kv(request)


# Delete key-value pair
def delete_kv(namespace: str, key: str):
    client = create_client()
    request = esa_models.DeleteKvRequest(
        namespace=namespace,
        key=key,
    )
    return client.delete_kv(request)


# List keys
def list_kvs(namespace: str, prefix: str = None):
    client = create_client()
    request = esa_models.ListKvsRequest(
        namespace=namespace,
        prefix=prefix,
    )
    return client.list_kvs(request)
```

### Batch Operations

```python
import json

# Batch write
def batch_put_kv(namespace: str, items: list):
    """items: [{"Key": "k1", "Value": "v1", "ExpirationTtl": 3600}, ...]"""
    client = create_client()
    request = esa_models.BatchPutKvRequest(
        namespace=namespace,
    )
    # body is JSON string
    request.body = json.dumps(items).encode("utf-8")
    return client.batch_put_kv(request)


# Batch delete
def batch_delete_kv(namespace: str, keys: list):
    """keys: ["key1", "key2", ...]"""
    client = create_client()
    request = esa_models.BatchDeleteKvRequest(
        namespace=namespace,
    )
    request.body = json.dumps(keys).encode("utf-8")
    return client.batch_delete_kv(request)
```

## Using KV in Edge Routine

In Edge Routine code, you need to create an instance via `new EdgeKV({namespace: "..."})` to access KV storage (no global instance, must be explicitly created each time):

```javascript
export default {
  async fetch(request) {
    const kv = new EdgeKV({ namespace: "my-namespace" });

    // Write
    await kv.put("key1", "value1");

    // Read
    const value = await kv.get("key1");

    // Delete
    await kv.delete("key1");

    return new Response(value || "not found");
  },
};
```

## Common Workflows

### 1. Initialize KV Storage

```
DescribeKvAccountStatus → (Enable if not enabled)
CreateKvNamespace → PutKv / BatchPutKv → ListKvs verify
```

### 2. Configuration Distribution (Edge Config Hot Update)

```
1. Write config via OpenAPI: PutKv(namespace="config", key="feature-flags", value=json)
2. Edge Routine reads config: new EdgeKV({namespace: "config"}).get("feature-flags")
3. Update config by calling PutKv again, edge nodes sync automatically
```

### 3. Data Cleanup

```
ListKvs(prefix="temp-") → Filter keys to delete → BatchDeleteKv
```

## Common Error Codes

| HTTP | Error Code | Description |
|------|------------|-------------|
| 400 | InvalidNameSpace.Malformed | Invalid namespace name (e.g. empty string) |
| 400 | InvalidKey.Malformed | Invalid key name (e.g. empty string) |
| 400 | InvalidKey.ExceedsMaximum | Key length exceeds 512 bytes |
| 400 | InvalidValue.ExceedsMaximum | Value exceeds 2MB (or 25MB) |
| 404 | InvalidNameSpace.NotFound | Namespace does not exist |
| 404 | InvalidKey.NotFound | Key does not exist |
| 406 | InvalidNameSpace.Duplicate | Namespace already exists |
| 406 | InvalidNameSpace.QuotaFull | Namespace quota exceeded |
| 403 | InvalidKey.ExceedsCapacity | Namespace capacity full |
| 429 | TooQuickRequests | Modify/delete operations too frequent |


path: references/pages.md
content: # ESA Pages — Edge Page Deployment Reference

ESA Pages provides the ability to quickly deploy HTML pages or static file directories to edge nodes. Built on Edge Routine, deployments are completed via Python SDK calling ESA OpenAPI.

## Deploy HTML Pages

### Flow

```
1. CreateRoutine(name)                     → Create routine (skip if exists)
2. GetRoutineStagingCodeUploadInfo(name)   → Get OSS upload signature
3. POST code to OSS                        → Upload code file
4. CommitRoutineStagingCode(name)          → Commit code version
5. PublishRoutineCodeVersion(staging)      → Deploy to staging
6. PublishRoutineCodeVersion(production)   → Deploy to production
7. GetRoutine(name)                        → Get defaultRelatedRecord as access URL
```

### Code Template

HTML content needs to be wrapped as Edge Routine code:

```javascript
const html = `<html><body>Hello World</body></html>`;

async function handleRequest(request) {
  return new Response(html, {
    headers: { "content-type": "text/html;charset=UTF-8" },
  });
}

export default {
  async fetch(request) {
    return handleRequest(request);
  },
};
```

### Python SDK Example

```python
from alibabacloud_esa20240910.client import Client as Esa20240910Client
from alibabacloud_esa20240910 import models as esa_models
from alibabacloud_tea_openapi import models as open_api_models
import requests


def create_client() -> Esa20240910Client:
    config = open_api_models.Config(
        region_id="cn-hangzhou",
        endpoint="esa.cn-hangzhou.aliyuncs.com",
    )
    return Esa20240910Client(config)


def deploy_html(name: str, html: str):
    """Deploy HTML page to ESA Pages"""
    client = create_client()

    # Escape special characters in template string
    escaped_html = html.replace("`", "\\`").replace("$", "\\$")
    code = f'''const html = `{escaped_html}`;

async function handleRequest(request) {{
  return new Response(html, {{
    headers: {{ "content-type": "text/html;charset=UTF-8" }},
  }});
}}

export default {{
  async fetch(request) {{
    return handleRequest(request);
  }},
}};'''

    # 1. Create routine (skip if exists)
    try:
        client.create_routine(esa_models.CreateRoutineRequest(name=name))
    except Exception as e:
        if "RoutineNameAlreadyExist" not in str(e):
            raise

    # 2. Get upload signature
    upload_info = client.get_routine_staging_code_upload_info(
        esa_models.GetRoutineStagingCodeUploadInfoRequest(name=name)
    )
    oss = upload_info.body.oss_post_config

    # 3. Upload code to OSS
    form_data = {
        "OSSAccessKeyId": oss.ossaccess_key_id,
        "Signature": oss.signature,
        "callback": oss.callback,
        "x:codeDescription": oss.x_code_description,
        "policy": oss.policy,
        "key": oss.key,
    }
    requests.post(oss.url, data=form_data, files={"file": code.encode()})

    # 4. Commit code version
    commit = client.commit_routine_staging_code(
        esa_models.CommitRoutineStagingCodeRequest(name=name)
    )
    version = commit.body.code_version

    # 5-6. Deploy to staging and production
    for env in ["staging", "production"]:
        client.publish_routine_code_version(
            esa_models.PublishRoutineCodeVersionRequest(
                name=name, env=env, code_version=version
            )
        )

    # 7. Get access URL
    routine = client.get_routine(esa_models.GetRoutineRequest(name=name))
    domain = routine.body.default_related_record
    return f"https://{domain}" if domain else None
```

## Deploy Static File Directory

### Flow

```
1. CreateRoutine(name)                            → Create routine (skip if exists)
2. CreateRoutineWithAssetsCodeVersion(name)       → Create assets code version, get OSS signature
3. Package directory as zip → POST zip to OSS     → Upload assets
4. Poll GetRoutineCodeVersionInfo(name, version)  → Wait for available status
5. CreateRoutineCodeDeployment(staging, 100%)     → Deploy to staging
6. CreateRoutineCodeDeployment(production, 100%)  → Deploy to production
7. GetRoutine(name)                               → Get access URL
```

### Zip Package Structure

The zip package structure created during deployment depends on the project's `EDGE_ROUTINE_TYPE`, with three cases:

#### 1. JS_ONLY (entry file only)

```
your-project.zip
└── routine/
    └── index.js        ← Code bundled by esbuild (or source file directly when --no-bundle)
```

#### 2. ASSETS_ONLY (static resources only)

```
your-project.zip
└── assets/
    ├── image.png
    ├── style.css
    └── subdir/
        └── data.json   ← All files under assets directory, maintaining original structure
```

#### 3. JS_AND_ASSETS (entry file + static resources, most common)

```
your-project.zip
├── routine/
│   └── index.js        ← Dynamic code (bundled JS)
└── assets/
    ├── image.png
    └── ...             ← Static resources, maintaining original structure
```

#### Key Details

- `index.js` content source: By default, produced by prodBuild (esbuild) bundling the entry file; if `--no-bundle` is passed, reads source file directly
- Paths under `assets/` are relative to `assets.directory` in configuration, recursively traversing all subdirectories and files
- Zip package is converted to Buffer via `zip.toBuffer()` and uploaded to OSS (first get OSS temporary credentials via API, then POST upload), with max 3 retries
- Project type determination logic is in `checkEdgeRoutineType`, based on whether entry file and assets directory actually exist
- Configuration source priority: CLI args > `esa.jsonc` / `esa.toml` config file

### Python SDK Example

```python
import os
import zipfile
import io
import time
import json
import requests


def deploy_folder(name: str, folder_path: str, description: str = ""):
    """Deploy static directory to ESA Pages"""
    client = create_client()

    # 1. Create routine
    try:
        client.create_routine(
            esa_models.CreateRoutineRequest(name=name, description=description)
        )
    except Exception as e:
        if "RoutineNameAlreadyExist" not in str(e):
            raise

    # 2. Create assets code version
    # Note: This API needs to be called via callApi method
    from alibabacloud_tea_openapi import models as api_models
    params = api_models.Params(
        action="CreateRoutineWithAssetsCodeVersion",
        version="2024-09-10", protocol="https", method="POST",
        auth_type="AK", body_type="json", req_body_type="json",
        style="RPC", pathname="/",
    )
    body = {"Name": name, "CodeDescription": description}
    request = api_models.OpenApiRequest(body=body)
    runtime = {}
    result = client._client.call_api(params, request, runtime)
    oss_config = result.get("body", {}).get("OssPostConfig", {})
    code_version = result.get("body", {}).get("CodeVersion")

    # 3. Package and upload zip
    buf = io.BytesIO()
    with zipfile.ZipFile(buf, "w", zipfile.ZIP_DEFLATED) as zf:
        for root, dirs, files in os.walk(folder_path):
            for f in files:
                full = os.path.join(root, f)
                rel = os.path.relpath(full, folder_path).replace(os.sep, "/")
                zf.write(full, f"assets/{rel}")
    buf.seek(0)

    form_data = {
        "OSSAccessKeyId": oss_config["OSSAccessKeyId"],
        "Signature": oss_config["Signature"],
        "policy": oss_config["Policy"],
        "key": oss_config["Key"],
    }
    if oss_config.get("XOssSecurityToken"):
        form_data["x-oss-security-token"] = oss_config["XOssSecurityToken"]
    requests.post(oss_config["Url"], data=form_data, files={"file": buf.getvalue()})

    # 4. Wait for version ready
    for _ in range(300):
        info = client._client.call_api(
            api_models.Params(
                action="GetRoutineCodeVersionInfo", version="2024-09-10",
                protocol="https", method="GET", auth_type="AK",
                body_type="json", req_body_type="json", style="RPC", pathname="/",
            ),
            api_models.OpenApiRequest(query={"Name": name, "CodeVersion": code_version}),
            {},
        )
        status = info.get("body", {}).get("Status", "").lower()
        if status == "available":
            break
        if status not in ("", "init"):
            raise RuntimeError(f"Build failed: {status}")
        time.sleep(1)

    # 5-6. Deploy
    for env in ["staging", "production"]:
        client._client.call_api(
            api_models.Params(
                action="CreateRoutineCodeDeployment", version="2024-09-10",
                protocol="https", method="POST", auth_type="AK",
                body_type="json", req_body_type="json", style="RPC", pathname="/",
            ),
            api_models.OpenApiRequest(query={
                "Name": name, "Env": env, "Strategy": "percentage",
                "CodeVersions": json.dumps([{"Percentage": 100, "CodeVersion": code_version}]),
            }),
            {},
        )

    # 7. Get access URL
    routine = client.get_routine(esa_models.GetRoutineRequest(name=name))
    domain = routine.body.default_related_record
    return f"https://{domain}" if domain else None
```

## Common Use Cases

### 1. Deploy Single HTML Page

Suitable for quick prototypes, games, demo pages:

```python
url = deploy_html("game-2048", "<html><body>...</body></html>")
print(f"Access URL: {url}")
```

### 2. Deploy Frontend Build Output

Suitable for React/Vue/Angular frontend projects' dist/build directories:

```python
url = deploy_folder("my-app", "/path/to/dist")
print(f"Access URL: {url}")
```

## Notes

1. **Function name rules**: Only lowercase letters, numbers, hyphens; must start with lowercase letter; length >= 2
2. **Same name function**: If function exists, reuses existing function and deploys new version code
3. **Deployment environments**: Deploys to both staging and production by default
4. **Access URL**: After successful deployment, get default access domain via `defaultRelatedRecord` from `GetRoutine`
5. **Static directory deployment**: Directory cannot be empty; files in zip are placed under `assets/` prefix
6. **HTML escaping**: When wrapping as ER code, escape backticks and `$` symbols
7. **Assets deployment**: `CreateRoutineWithAssetsCodeVersion` and `CreateRoutineCodeDeployment` need to be called via `callApi` method (not directly wrapped by SDK)

path: references/rule-examples.md
content: # ESA Rule Expression - Scenario Examples

Practical examples for common ESA rule expression scenarios.

## Static Resource Caching

```bash
# Cache all images (single extension)
--Rule '(http.request.uri.path.extension eq "jpg")'

# Cache multiple static resource types
--Rule '(http.request.uri.path.extension in {"js" "css" "png" "jpg" "gif" "ico" "woff2" "svg" "webp"})'

# Cache specific file
--Rule '(http.request.uri.path.full_file_name eq "index.html")'

# Cache all HTML pages
--Rule '(ends_with(http.request.uri, ".html"))'
```

## URL Path Matching

```bash
# Match URL prefix (API path)
--Rule '(starts_with(http.request.uri, "/api/"))'

# Match URL suffix
--Rule '(ends_with(http.request.uri, ".json"))'

# Match URL containing substring (MUST start with /)
--Rule '(http.request.uri contains "/test")'
--Rule '(http.request.uri.path contains "/dynamic")'

# Exclude admin path from caching
--Rule '(not starts_with(http.request.uri, "/admin/"))'

# Exclude multiple paths
--Rule '(not starts_with(http.request.uri, "/admin/") and not starts_with(http.request.uri, "/internal/"))'
```

## Domain / Host Matching

```bash
# Match specific hostname
--Rule '(http.host eq "static.example.com")'

# Match multiple domains
--Rule '(http.host in {"cdn1.example.com" "cdn2.example.com" "cdn3.example.com"})'

# Match domain suffix (e.g. all .cn domains) - requires standard plan
--Rule '(ends_with(http.host, ".cn"))'

# Match domain prefix
--Rule '(starts_with(http.host, "api."))'

# Exclude specific domain
--Rule '(http.host ne "internal.example.com")'
```

## Geo / IP Matching

```bash
# Match by country
--Rule '(ip.geoip.country eq "CN")'

# Match multiple countries
--Rule '(ip.geoip.country in {"CN" "JP" "KR"})'

# Match by continent (Asia)
--Rule '(ip.geoip.continent eq "AS")'

# Match by AS number
--Rule '(ip.geoip.asnum eq 45104)'

# Match IPv6 traffic
--Rule '(ip.src.version eq "IPv6")'
```

## Request Method Matching

```bash
# Match GET requests only
--Rule '(http.request.method eq "GET")'

# Match POST requests
--Rule '(http.request.method eq "POST")'

# Match non-GET requests
--Rule '(http.request.method ne "GET")'
```

## Header / Cookie / Query Matching

```bash
# Match if authorization header exists
--Rule '(exists(http.request.headers["authorization"]))'

# Match if specific cookie exists
--Rule '(exists(http.request.cookies["session"]))'

# Match if query parameter exists
--Rule '(exists(http.request.uri.args["nocache"]))'

# Match by cookie length
--Rule '(len(http.request.cookies["session"]) gt 1024)'

# Match if no authorization header (unauthenticated)
--Rule '(not exists(http.request.headers["authorization"]))'
```

## Combined Conditions

```bash
# Specific domain + static resources
--Rule '(http.host eq "www.example.com" and http.request.uri.path.extension in {"js" "css" "png"})'

# Specific domain + path prefix
--Rule '(http.host eq "www.example.com" and starts_with(http.request.uri, "/static/"))'

# Static resources but exclude admin
--Rule '(http.request.uri.path.extension in {"js" "css" "png" "jpg"} and not starts_with(http.request.uri, "/admin/"))'

# GET requests from China to HTML pages
--Rule '(http.request.method eq "GET" and ip.geoip.country eq "CN" and ends_with(http.request.uri, ".html"))'

# HTTPS only + specific domain
--Rule '(ssl eq true and http.host eq "secure.example.com")'
```

## OR Conditions (Multiple Rule Groups)

```bash
# Match either domain
--Rule '(http.host eq "a.example.com") or (http.host eq "b.example.com")'

# Match API path OR static resources
--Rule '(starts_with(http.request.uri, "/api/")) or (http.request.uri.path.extension in {"js" "css" "png"})'

# Match China OR Japan traffic
--Rule '(ip.geoip.country eq "CN") or (ip.geoip.country eq "JP")'
```

## Case-Insensitive Matching

```bash
# Case-insensitive URL match (use lower() wrapper)
--Rule '(lower(http.request.uri) eq "/readme.html")'

# Case-insensitive host match
--Rule '(lower(http.host) eq "www.example.com")'
```

## Regex Examples (Standard Plan and Above)

```bash
# Match versioned API paths
--Rule '(http.request.uri matches "^/api/v[0-9]+")'

# Match image files by extension
--Rule '(http.request.uri matches "\\.(jpg|png|gif|webp)$")'

# Match subdomains pattern
--Rule '(http.host matches "^(www|blog|docs)\\.example\\.com$")'
```


path: references/rule-generation-guide.md
content: # ESA Rule Expression - Generation Guide

This guide provides detailed rules for generating correct ESA rule expressions from natural language requirements.

## Operator Usage Rules (CRITICAL)

### Forbidden Operator Formats

These formats will cause errors - **NEVER USE THEM**:

| Forbidden Format | Correct Alternative |
|------------------|---------------------|
| `not...eq` | Use `ne` instead |
| `not...ne` | Use `eq` instead |
| `not in` | Use `not...in` (with space) |
| `not contains` | Use `not...contains` (with space) |
| `not matches` | Use `not...matches` (with space) |

### Operator Selection by User Expression

| User Expression | Operator | Example |
|-----------------|----------|---------|
| "equals/is/为" | `eq` | `http.host eq "example.com"` |
| "not equals/is not/不等于" | `ne` | `http.host ne "test.com"` |
| "contains the following/包含以下各项" | `in` + set | `http.host in {"a.com" "b.com"}` |
| "does not contain the following/不包含以下各项" | `not...in` + set | `not http.host in {"a.com" "b.com"}` |
| "contains/包含" (substring) | `contains` | `http.request.uri contains "/test"` |
| "does not contain/不包含" (substring) | `not...contains` | `not http.request.uri contains "/test"` |
| "starts with/以...开头" | `starts_with` | `starts_with(http.request.uri, "/api")` |
| "does not start with/不以...开头" | `not starts_with` | `not starts_with(http.request.uri, "/api")` |
| "ends with/以...结尾" | `ends_with` | `ends_with(http.request.uri, ".html")` |
| "does not end with/不以...结尾" | `not ends_with` | `not ends_with(http.request.uri, ".html")` |

## Rule Type Recognition

### Priority 1: Explicit Context

If user mentions rule type explicitly ("cache rule", "HTTPS rule", "version management"), use that type.

### Priority 2: Keywords Inference

| Keywords | Rule Type |
|----------|-----------|
| cache, 缓存, expire, ttl | Cache Rules |
| compress, 压缩, gzip, brotli | Compression Rules |
| HTTPS, SSL/TLS, certificate | HTTPS Rules |
| redirect, 重定向, 301, 302 | Redirect Rules |
| request header, 请求头 | Request Header Modification |
| response header, 响应头 | Response Header Modification |
| rewrite, 重写, url rewrite | URL Rewrite Rules |
| load balance, 负载均衡 | Load Balancing Rules |
| version, 版本, A/B test, 灰度 | Version Management |
| origin, 回源 | Origin Rules |

### Priority 3: Default

If unable to determine, default to **Node Rules** (most versatile).

## Rule Type Field Restrictions

| Rule Type | Allowed Fields |
|-----------|---------------|
| Node Rules | All common fields |
| Load Balancing | Node fields + `ip.src.region_code` + `http.request.timestamp.sec` |
| HTTPS Rules | Only `http.host` (only `eq`/`ne` supported) |
| Version Management | Node fields, but no `in_list`/`not in_list`; `matches` quota-limited |

## URI Field Selection Rule

Choose the correct URI field based on match value format:

| Match Value Format | Use Field |
|-------------------|-----------|
| Starts with `http://` or `https://` (full URL) | `http.request.full_uri` |
| Starts with `/` (path only) | `http.request.uri` |
| Path without protocol | `http.request.uri` |

**Examples:**
```
# Full URL matching
http.request.full_uri eq "http://example.com/api/v1/users"

# Path only matching
http.request.uri eq "/api/v1/users"
http.request.uri contains "/test"
```

**IMPORTANT**: When using `contains` with `http.request.uri`, the value **MUST start with `/`**:
- CORRECT: `http.request.uri contains "/test"`
- WRONG: `http.request.uri contains "test"` (will cause `CompileRuleError`)

## Value Types and Literals

| Type | Format | Example |
|------|--------|---------|
| String | Double quotes, escape `\\` `\"` | `"example.com"` |
| Integer | No quotes | `30`, `45104` |
| Boolean | Direct use | `ssl`, `not ssl` |
| IP | Single or CIDR | `192.168.1.1`, `192.168.0.0/24` |
| Set | Space-separated in braces | `{"a.com" "b.com" "c.com"}` |
| Object | Subscript access | `http.request.headers["x-header"]` |

## Special Values

- **Match all requests**: Return `true` (boolean, not string)
- **HTTP version values**: `HTTP/1.0`, `HTTP/1.1`, `HTTP/2.0`, `HTTP/3.0`
- **`ip.geoip.asnum`**: Integer, no quotes

## Logical Operators

- **Priority**: `not` > `and` > `or`
- **Use parentheses** to eliminate ambiguity
- **Max nesting**: 2 levels

**Combination examples:**
```
# AND combination
(http.host eq "example.com" and starts_with(http.request.uri, "/api"))

# OR combination
(http.host eq "a.com") or (http.host eq "b.com")

# Complex combination
(http.request.uri contains "/test" and ip.geoip.country eq "CN")

# Negation with set
(http.host contains "example.com") and (not http.host in {"sub1.example.com" "sub2.example.com"})
```

## Error Examples and Corrections

### Error 1: Using `not...eq`

```
# Input: Requests that are NOT m3u8 files
# WRONG: (not http.request.uri.path.extension eq "m3u8")
# CORRECT: http.request.uri.path.extension ne "m3u8"
```

### Error 2: Using `not in` (wrong syntax)

```
# Input: Exclude jpg and png files
# WRONG: http.request.uri.path.extension not in {"jpg" "png"}
# CORRECT: not http.request.uri.path.extension in {"jpg" "png"}
```

### Error 3: Using `not contains` (wrong syntax)

```
# Input: User-Agent not containing "bot"
# WRONG: http.user_agent not contains "bot"
# CORRECT: not http.user_agent contains "bot"
```

## Generation Flow

1. **Preprocess input**: Extract match conditions, ignore configuration operations
2. **Check "match all"**: If "all requests", return `true`
3. **Identify rule type**: Determine type by keywords and context
4. **Filter allowed fields**: Based on rule type
5. **Select field and operator**: Based on user expression and field support
6. **Apply URI field rule**: Choose `http.request.uri` or `http.request.full_uri`
7. **Type matching**: String with quotes, integer without quotes
8. **Combine expression**: Use parentheses, prefer function-style for prefix/suffix
9. **Error check**: Field range, operator restrictions, type constraints

## Final Checklist

Before output, verify:

1. ✅ Correct operator format (no `not...eq`, `not in`, `not contains`)
2. ✅ Correct field names and syntax
3. ✅ Correct value types (strings quoted, integers unquoted)
4. ✅ Correct set syntax `{value1 value2}`
5. ✅ Correct parentheses grouping and logical operators
6. ✅ Correct URI field selection based on value format


path: references/rule-match-fields.md
content: # ESA Rule Expression - Match Fields

Complete list of match fields available in ESA rule expressions.

## Standard Fields (HTTP Request)

| Field | Type | Supported Operators | Example |
|-------|------|---------------------|---------|
| `http.host` | String | `in`, `eq`, `ne`, `contains`, `matches`, `starts_with`, `ends_with`, `in_list` | `http.host in {"example.com"}` |
| `http.cookie` | String | `in`, `eq`, `ne`, `contains`, `matches` | `http.cookie contains "PHPSESSID"` |
| `http.referer` | String | `in`, `eq`, `ne`, `contains`, `matches` | `http.referer contains "google.com"` |
| `http.request.method` | String | `in`, `eq`, `ne` | `http.request.method in {"GET" "POST"}` |
| `http.request.uri` | String | `in`, `eq`, `ne`, `contains`, `matches`, `starts_with`, `ends_with` | `http.request.uri contains "/test"` |
| `http.request.uri.path` | String | `in`, `eq`, `ne`, `contains`, `matches`, `starts_with`, `ends_with` | `starts_with(http.request.uri.path, "/api")` |
| `http.request.uri.path.extension` | String | `in`, `eq`, `ne` | `http.request.uri.path.extension in {"jpg" "png"}` |
| `http.request.uri.path.file_name` | String | `in`, `eq`, `ne` | `http.request.uri.path.file_name eq "index"` |
| `http.request.uri.path.full_file_name` | String | `in`, `eq`, `ne` | `http.request.uri.path.full_file_name eq "index.html"` |
| `http.request.uri.query` | String | `in`, `eq`, `ne`, `contains`, `matches`, `starts_with`, `ends_with` | `http.request.uri.query contains "utm_source"` |
| `http.request.full_uri` | String | `in`, `eq`, `ne`, `contains`, `matches`, `starts_with`, `ends_with` | `http.request.full_uri eq "http://example.com/api"` |
| `http.request.version` | String | `in`, `eq`, `ne` | `http.request.version eq "HTTP/2.0"` |
| `http.request.scheme` | String | `eq` | `http.request.scheme eq "https"` |
| `http.user_agent` | String | `in`, `eq`, `ne`, `contains`, `matches` | `http.user_agent contains "bot"` |
| `http.x_forwarded_for` | String | `in`, `eq`, `ne`, `contains`, `matches` | `http.x_forwarded_for eq "192.168.1.1"` |
| `http.request.body.mime` | String | `eq`, `ne` | `http.request.body.mime eq "application/json"` |
| `http.request.timestamp.sec` | Integer | `eq`, `ne`, `le`, `ge`, `lt`, `gt` | `http.request.timestamp.sec gt 1735019278` |

**HTTP version values**: `HTTP/1.0`, `HTTP/1.1`, `HTTP/2.0`, `HTTP/3.0`

## Map Fields (support key access with `["key"]`)

| Field | Type | Description | Access example |
|-------|------|-------------|---------------|
| `http.request.headers` | Object | Request headers | `http.request.headers["accept"]` |
| `http.request.cookies` | Map | Individual cookies | `http.request.cookies["session"]` |
| `http.request.uri.args` | Map | Query parameters | `http.request.uri.args["page"]` |
| `http.request.body.form` | Map | Form body fields | `http.request.body.form["username"]` |

## IP / Geo Fields

| Field | Type | Supported Operators | Example |
|-------|------|---------------------|---------|
| `ip.src` | IP | `in`, `eq`, `ne`, `in_list` | `ip.src in {192.168.1.1 10.0.0.5}` |
| `ip.geoip.country` | String | `in`, `eq`, `ne` | `ip.geoip.country eq "CN"` |
| `ip.geoip.continent` | String | `in`, `eq`, `ne` | `ip.geoip.continent eq "AS"` |
| `ip.geoip.asnum` | Integer | `in`, `eq`, `ne`, `in_list` | `ip.geoip.asnum eq 45104` |
| `ip.src.isp` | String | `in`, `eq`, `ne` | `ip.src.isp contains "China Telecom"` |
| `ip.src.version` | String | `eq`, `ne` | `ip.src.version eq "IPv4"` |
| `ip.src.subdivision_1_iso_code` | String | `in`, `eq`, `ne` | `ip.src.subdivision_1_iso_code in {"CN-ZJ" "CN-GD"}` |
| `ip.src.region_code` | String | `in`, `eq`, `ne` | `ip.src.region_code eq "us-west-1"` |

**Note**: `ip.geoip.asnum` is an **integer**, do not use quotes.

## Boolean Fields

| Field | Type | Description |
|-------|------|-------------|
| `ssl` | Boolean | Whether the request is HTTPS |

## Extended Fields (advanced, may require specific plan)

| Field | Type | Description |
|-------|------|-------------|
| `ali.ja3_hash` | String | JA3 TLS fingerprint hash |
| `ali.ja4` | String | JA4 TLS fingerprint |
| `ali.js_detection.passed` | Boolean | Whether JS challenge passed |
| `ali.static_resource` | Boolean | Whether request is for a static resource |
| `ali.tls_client_auth.cert_verified` | Boolean | Whether client TLS cert is verified |
| `ali.tls_hash` | String | TLS hash |

## Field selection guidance

| Scenario | Recommended field |
|----------|------------------|
| Match by domain | `http.host` |
| Match URL path or suffix | `http.request.uri` |
| Match file type | `http.request.uri.path.extension` |
| Match specific file | `http.request.uri.path.full_file_name` |
| Match query parameter | `http.request.uri.args["key"]` |
| Match by country | `ip.geoip.country` |
| Match by IP | `ip.src` |
| Match by request method | `http.request.method` |
| Match by header | `http.request.headers["header-name"]` |
| Match by cookie | `http.request.cookies["cookie-name"]` |

## Notes

- Not all fields are available for all rule types. If a field causes `CompileRuleError`, try a related field (e.g. use `http.request.uri` instead of `http.request.uri.path`).
- Map fields accessed with `["key"]` return the value for that key as a string.
- Extended fields (`ali.*`) may require specific plan levels or feature activation.


path: references/rule-operators.md
content: # ESA Rule Expression - Operators

Complete list of operators available in ESA rule expressions.

## Forbidden Operator Formats (CRITICAL)

These formats are **ABSOLUTELY FORBIDDEN** and will cause errors:

| Forbidden Format | Why | Correct Alternative |
|------------------|-----|---------------------|
| `not...eq` | Invalid syntax | Use `ne` |
| `not...ne` | Invalid syntax | Use `eq` |
| `not in` | Wrong position | Use `not...in` (not before field) |
| `not contains` | Wrong position | Use `not...contains` (not before field) |
| `not matches` | Wrong position | Use `not...matches` (not before field) |
| `field ends_with "value"` | Wrong syntax | Use `ends_with(field, "value")` |
| `field starts_with "value"` | Wrong syntax | Use `starts_with(field, "value")` |

**Correct negation patterns:**
```
# Negating equality
WRONG:  not http.host eq "a.com"
RIGHT:  http.host ne "a.com"

# Negating set membership
WRONG:  http.host not in {"a.com" "b.com"}
RIGHT:  not http.host in {"a.com" "b.com"}

# Negating contains
WRONG:  http.request.uri not contains "/test"
RIGHT:  not http.request.uri contains "/test"
```

## String Comparison (Infix Style)

Syntax: `(field operator "value")`

| Operator | Description | Syntax | Example |
|----------|-------------|--------|---------|
| `eq` | Equal to | `(field eq "value")` | `(http.host eq "www.example.com")` |
| `ne` | Not equal to | `(field ne "value")` | `(http.host ne "test.example.com")` |
| `contains` | Contains substring | `(field contains "value")` | `(http.request.uri contains "/test")` |
| `in` | In a set of values | `(field in {"v1" "v2"})` | `(http.host in {"a.com" "b.com"})` |
| `matches` | Regex match (PCRE) | `(field matches "regex")` | `(http.request.uri matches "^/api/v[0-9]+")` |

**Important notes:**
- `in` uses **space-separated** values in braces, NOT comma-separated: `{"a" "b" "c"}`
- `matches` requires **standard plan or above**; basic plan returns `RuleRegexQuotaCheckFailed`
- `contains` works with `http.request.uri` and `http.host`. When matching URI, include the path separator (e.g. `"/test"` not `"test"`)

## String Comparison (Function Style)

Syntax: `(function(field, "value"))`

| Operator | Description | Syntax | Example |
|----------|-------------|--------|---------|
| `starts_with` | Starts with prefix | `(starts_with(field, "value"))` | `(starts_with(http.request.uri, "/api/"))` |
| `ends_with` | Ends with suffix | `(ends_with(field, "value"))` | `(ends_with(http.request.uri, ".html"))` |

**Critical:** `starts_with` and `ends_with` **MUST** use function-call syntax. Infix syntax like `(field ends_with "value")` will cause `CompileRuleError`.

## Numeric Comparison (Infix Style)

Syntax: `(field operator number)`

| Operator | Description | Syntax | Example |
|----------|-------------|--------|---------|
| `eq` | Equal to | `(field eq num)` | `(ip.geoip.asnum eq 45104)` |
| `ne` | Not equal to | `(field ne num)` | `(ip.geoip.asnum ne 45104)` |
| `lt` | Less than | `(field lt num)` | `(ip.geoip.asnum lt 45104)` |
| `le` | Less than or equal | `(field le num)` | `(ip.geoip.asnum le 45104)` |
| `gt` | Greater than | `(field gt num)` | `(ip.geoip.asnum gt 45104)` |
| `ge` | Greater than or equal | `(field ge num)` | `(ip.geoip.asnum ge 45104)` |

## Length Check (Function Style)

Syntax: `(len(field) operator number)`

| Operator | Description | Syntax | Example |
|----------|-------------|--------|---------|
| `len eq` | Length equals | `(len(field) eq num)` | `(len(http.cookie) eq 100)` |
| `len gt` | Length greater than | `(len(field) gt num)` | `(len(http.cookie) gt 1024)` |
| `len lt` | Length less than | `(len(field) lt num)` | `(len(http.request.uri) lt 2048)` |

## Existence Check (Function Style)

| Operator | Description | Syntax | Example |
|----------|-------------|--------|---------|
| `exists` | Field exists | `(exists(field))` | `(exists(http.request.headers["authorization"]))` |
| `not exists` | Field not exists | `(not exists(field))` | `(not exists(http.request.headers["authorization"]))` |

## Transformation (Function Style)

| Function | Description | Syntax | Example |
|----------|-------------|--------|---------|
| `lower` | Convert to lowercase | `lower(field)` | `(lower(http.request.uri) contains "api")` |

Use `lower()` for case-insensitive matching by wrapping the field.

## Negation

Any expression can be negated with `not`:

| Operator | Syntax | Example |
|----------|--------|---------|
| `not` | `(not expr)` | `(not http.host contains "test")` |
| `not...contains` | `(not field contains "value")` | `(not http.host contains "staging")` |
| `not...in` | `(not field in {...})` | `(not http.host in {"a.com" "b.com"})` |
| `not...matches` | `(not field matches "regex")` | `(not http.request.uri matches "^/internal")` |
| `not starts_with` | `(not starts_with(field, "v"))` | `(not starts_with(http.request.uri, "/admin"))` |
| `not ends_with` | `(not ends_with(field, "v"))` | `(not ends_with(http.request.uri, ".json"))` |

## Logical Operators

| Operator | Description | Syntax | Example |
|----------|-------------|--------|---------|
| `and` | Both conditions | `(expr1 and expr2)` | `(http.host eq "a.com" and starts_with(http.request.uri, "/api"))` |
| `or` | Either condition | `(expr1) or (expr2)` | `(http.host eq "a.com") or (http.host eq "b.com")` |

**Nesting rules:**
- Max nesting depth: **2 levels**
- `and` conditions go inside the same parentheses: `(A and B and C)`
- `or` conditions separate parenthesized groups: `(A) or (B) or (C)`
- Mixed: `(A and B) or (C and D)`

## Plan Limitations

| Plan | eq/ne/in/starts_with/ends_with | contains | matches (regex) |
|------|-------------------------------|----------|----------------|
| Basic | Supported | Supported | **Not supported** |
| Standard | Supported | Supported | Supported |
| Enterprise | Supported | Supported | Supported |

## Common CompileRuleError causes

1. Using `ends_with`/`starts_with` as infix operators instead of function syntax
2. Using `wildcard` (not a valid ESA operator)
3. Using `contains` with URI but missing path separator (use `"/test"` not `"test"`)
4. Using unsupported match fields for the specific rule type
5. Missing parentheses around the expression
6. Using comma-separated values in `in` instead of space-separated


path: references/sites.md
content: # ESA Sites

Sites are the basic management unit of ESA. Each site corresponds to a domain (or subdomain).

## Common operations

- Create: `CreateSite`
- List: `ListSites` (supports pagination and filters)
- Query: `GetSite`
- Delete: `DeleteSite`
- Check name: `CheckSiteName`
- Change access type: `UpdateSiteAccessType`
- Change coverage: `UpdateSiteCoverage`

## Access types

- **CNAME**: Keep original DNS, add CNAME record pointing to ESA edge node.
  - Pros: Simple setup, no DNS migration needed.
  - Limitation: **DNS record APIs are NOT available** (CreateRecord, ListRecords, etc. will fail).
- **NS**: Delegate nameservers to ESA, ESA takes over DNS resolution.
  - Pros: Full DNS management via API.
  - Limitation: Must change NS records at domain registrar.

## Switching access type

Use `UpdateSiteAccessType` to switch between CNAME and NS.

- NS -> CNAME: Will fail if incompatible DNS records exist. Delete A/AAAA records first.
- CNAME -> NS: Works directly. After switching, manage DNS via ESA API.

## Coverage options

- `domestic`: China mainland only
- `overseas`: Outside China mainland
- `global`: Worldwide

## Plan types

- `basicplan` / `standardplan` / `advancedplan` / `enterpriseplan`

## ListSites filters

- `SiteName`: Fuzzy match
- `Status`: Site status (active, pending, etc.)
- `AccessType`: NS or CNAME
- `Coverage`: domestic, overseas, global
- `PlanSubscribeType`: Plan type

## Behavioral notes

- Newly created sites start as `pending`; complete access verification to activate.
- Deleting a site removes all associated configuration (DNS records, cache rules, WAF rules, certificates, etc.).
- `ListSites` returns sites across all regions; no need to iterate regions.

## References

- CreateSite: https://help.aliyun.com/zh/esa/developer-reference/api-esa-2024-09-10-createsite
- ListSites: https://help.aliyun.com/zh/esa/developer-reference/api-esa-2024-09-10-listsites
- Access types: https://help.aliyun.com/zh/esa/user-guide/add-site


path: references/sources.md
content: Official documentation sources (for future updates)
====================================================

# Product pages
- OpenAPI product page: https://api.aliyun.com/product/ESA
- API metadata: https://api.aliyun.com/meta/v1/products/ESA/versions/2024-09-10/api-docs.json
- API overview: https://www.alibabacloud.com/help/en/esa/developer-reference/api-esa-2024-09-10-overview
- Endpoints: https://www.alibabacloud.com/help/en/esa/developer-reference/endpoints
- Product home: https://www.alibabacloud.com/help/en/esa/
- Quick start: https://www.alibabacloud.com/help/en/esa/getting-started/

# Key operation docs
- ListSites: https://help.aliyun.com/zh/esa/developer-reference/api-esa-2024-09-10-listsites
- CreateSite: https://help.aliyun.com/zh/esa/developer-reference/api-esa-2024-09-10-createsite
- CreateRecord: https://help.aliyun.com/zh/esa/developer-reference/api-esa-2024-09-10-createrecord
- ListRecords: https://help.aliyun.com/zh/esa/developer-reference/api-esa-2024-09-10-listrecords
- PurgeCaches: https://help.aliyun.com/zh/esa/developer-reference/api-esa-2024-09-10-purgecaches
- PreloadCaches: https://help.aliyun.com/zh/esa/developer-reference/api-esa-2024-09-10-preloadcaches
- ApplyCertificate: https://help.aliyun.com/zh/esa/developer-reference/api-esa-2024-09-10-applycertificate
- CreateWafRule: https://help.aliyun.com/zh/esa/developer-reference/api-esa-2024-09-10-createwafrule
- ListWafRulesets: https://help.aliyun.com/zh/esa/developer-reference/api-esa-2024-09-10-listwafrulesets
- CreateOriginPool: https://help.aliyun.com/zh/esa/developer-reference/api-esa-2024-09-10-createoriginpool
- CreateCacheRule: https://help.aliyun.com/zh/esa/developer-reference/api-esa-2024-09-10-createcacherule

# Access type docs
- CNAME vs NS: https://help.aliyun.com/zh/esa/user-guide/add-site


path: references/time-series.md
content: # DescribeSiteTimeSeriesData API Reference

Query account-level or site-level traffic analysis time-series data.

Official docs: https://api.aliyun.com/document/ESA/2024-09-10/DescribeSiteTimeSeriesData

## Request Parameters

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `SiteId` | string | No | Site ID. Empty = account-level query |
| `StartTime` | string | No | Start time (ISO8601 UTC+0) |
| `EndTime` | string | No | End time (ISO8601 UTC+0) |
| `Interval` | string | No | Time granularity in seconds |
| `Fields` | array | Yes | Query metrics array |

### Fields Structure

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `FieldName` | string | No | Metric name (e.g., `Traffic`, `Request`) |
| `Dimension` | array | No | Dimension array (e.g., `["SiteId"]`) |

## Response Parameters

| Parameter | Type | Description |
|-----------|------|-------------|
| `Data` | array | Time-series data array |
| `SummarizedData` | array | Aggregated summary data |
| `StartTime` | string | Query start time |
| `EndTime` | string | Query end time |
| `Interval` | long | Data granularity (seconds) |
| `SamplingRate` | float | Sampling rate (%) |
| `RequestId` | string | Request ID |

### Data Structure

| Parameter | Type | Description |
|-----------|------|-------------|
| `FieldName` | string | Metric name |
| `DimensionName` | string | Dimension name |
| `DimensionValue` | string | Dimension value |
| `DimensionValueAlias` | string | Dimension alias (e.g., site name) |
| `DetailData` | array | Time-series points |

### DetailData Structure

| Parameter | Type | Description |
|-----------|------|-------------|
| `TimeStamp` | string | Time point (ISO8601) |
| `Value` | any | Metric value |

### SummarizedData Structure

| Parameter | Type | Description |
|-----------|------|-------------|
| `FieldName` | string | Metric name |
| `DimensionName` | string | Dimension name |
| `DimensionValue` | string | Dimension value |
| `AggMethod` | string | Aggregation method (`sum`, `avg`) |
| `Value` | any | Aggregated value |

## Time Granularity Rules

| Time Range | Interval | Parameter Value |
|------------|----------|-----------------|
| <= 3 hours | 1 minute | `60` |
| 3-12 hours | 5 minutes | `300` |
| 12 hours - 1 day | 15 minutes | `900` |
| 1-10 days | 1 hour | `3600` |
| 10-31 days | 1 day | `86400` |

**Default**: If `StartTime` and `EndTime` not specified, returns last 24 hours.

**Note**: Large time ranges may use sampling.

## Code Examples

### Query site traffic (hourly)

```python
from alibabacloud_esa20240910.client import Client as EsaClient
from alibabacloud_esa20240910 import models as esa_models
from alibabacloud_tea_openapi import models as open_api_models
from datetime import datetime, timedelta, timezone

def create_client():
    config = open_api_models.Config(
        region_id="cn-hangzhou",
        endpoint="esa.cn-hangzhou.aliyuncs.com",
    )
    return EsaClient(config)

def query_traffic(site_id: str, hours: int = 24):
    client = create_client()

    end_time = datetime.now(timezone.utc)
    start_time = end_time - timedelta(hours=hours)

    resp = client.describe_site_time_series_data(
        esa_models.DescribeSiteTimeSeriesDataRequest(
            site_id=site_id,
            start_time=start_time.strftime("%Y-%m-%dT%H:%M:%SZ"),
            end_time=end_time.strftime("%Y-%m-%dT%H:%M:%SZ"),
            interval="3600",  # 1 hour
            fields=[
                esa_models.DescribeSiteTimeSeriesDataRequestFields(
                    field_name="Traffic",
                    dimension=["SiteId"]
                )
            ]
        )
    )

    return resp.body

# Usage
result = query_traffic("974351557069296", hours=24)
for item in result.data:
    print(f"Site: {item.dimension_value_alias}")
    for point in item.detail_data:
        print(f"  {point.time_stamp}: {point.value} bytes")
```

### Query multiple metrics

```python
def query_multiple_metrics(site_id: str):
    client = create_client()

    resp = client.describe_site_time_series_data(
        esa_models.DescribeSiteTimeSeriesDataRequest(
            site_id=site_id,
            start_time="2026-03-09T00:00:00Z",
            end_time="2026-03-10T00:00:00Z",
            interval="3600",
            fields=[
                esa_models.DescribeSiteTimeSeriesDataRequestFields(
                    field_name="Traffic",
                    dimension=["ALL"]
                ),
                esa_models.DescribeSiteTimeSeriesDataRequestFields(
                    field_name="Request",
                    dimension=["ALL"]
                ),
                esa_models.DescribeSiteTimeSeriesDataRequestFields(
                    field_name="HitRate",
                    dimension=["ALL"]
                )
            ]
        )
    )

    return resp.body
```

### Query by country

```python
def query_traffic_by_country(site_id: str):
    client = create_client()

    resp = client.describe_site_time_series_data(
        esa_models.DescribeSiteTimeSeriesDataRequest(
            site_id=site_id,
            start_time="2026-03-09T00:00:00Z",
            end_time="2026-03-10T00:00:00Z",
            interval="3600",
            fields=[
                esa_models.DescribeSiteTimeSeriesDataRequestFields(
                    field_name="Traffic",
                    dimension=["ClientCountryCode"]
                )
            ]
        )
    )

    # Group by country
    for item in resp.body.data:
        country = item.dimension_value
        print(f"Country: {country}")
        for point in item.detail_data:
            print(f"  {point.time_stamp}: {point.value}")
```

## Response Example

```json
{
  "Data": [
    {
      "FieldName": "Traffic",
      "DimensionName": "SiteId",
      "DimensionValue": "974351557069296",
      "DimensionValueAlias": "lwcwiki.fun",
      "DetailData": [
        {"TimeStamp": "2026-03-09T16:00:00Z", "Value": 38428},
        {"TimeStamp": "2026-03-09T17:00:00Z", "Value": 1042},
        {"TimeStamp": "2026-03-09T18:00:00Z", "Value": 629}
      ]
    }
  ],
  "SummarizedData": [
    {
      "FieldName": "Traffic",
      "DimensionName": "SiteId",
      "DimensionValue": "974351557069296",
      "DimensionValueAlias": "lwcwiki.fun",
      "AggMethod": "sum",
      "Value": 27317844
    }
  ],
  "StartTime": "2026-03-09T16:00:00Z",
  "EndTime": "2026-03-10T15:59:00Z",
  "Interval": 3600,
  "SamplingRate": 100
}
```

## Error Codes

| HTTP Code | Error Code | Description |
|-----------|------------|-------------|
| 400 | `InvalidParameter.TimeRange` | Time range exceeds limit |
| 400 | `InvalidEndTime.Mismatch` | EndTime earlier than StartTime |
| 400 | `InvalidParameter.Field` | Invalid field name |
| 400 | `InvalidParameter.Dimension` | Invalid dimension |
| 400 | `InvalidTime.Malformed` | Wrong time format |
| 400 | `TooManyDimensions` | Too many query dimensions |
| 400 | `TooManyRequests` | Rate limited |


path: references/top-data.md
content: # DescribeSiteTopData API Reference

Query account-level or site-level traffic analysis Top-N ranking data.

Official docs: https://api.aliyun.com/document/ESA/2024-09-10/DescribeSiteTopData

## Request Parameters

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `SiteId` | string | No | Site ID. Empty = account-level query |
| `StartTime` | string | No | Start time (ISO8601 UTC+0) |
| `EndTime` | string | No | End time (ISO8601 UTC+0) |
| `Interval` | string | No | Time granularity in seconds |
| `Fields` | array | Yes | Query metrics array |
| `Limit` | string | No | Top-N count. Values: `5`, `10`, `150` |

### Fields Structure

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `FieldName` | string | No | Metric name (e.g., `Traffic`, `Request`) |
| `Dimension` | array | No | Dimension array (e.g., `["ClientCountryCode"]`) |

## Response Parameters

| Parameter | Type | Description |
|-----------|------|-------------|
| `Data` | array | Top-N data array |
| `StartTime` | string | Query start time |
| `EndTime` | string | Query end time |
| `SamplingRate` | float | Sampling rate (%) |
| `RequestId` | string | Request ID |

### Data Structure

| Parameter | Type | Description |
|-----------|------|-------------|
| `FieldName` | string | Metric name |
| `DimensionName` | string | Dimension name |
| `DetailData` | array | Top-N ranking data |

### DetailData Structure

| Parameter | Type | Description |
|-----------|------|-------------|
| `DimensionValue` | string | Dimension value (e.g., country code) |
| `Value` | any | Metric value |

## Code Examples

### Query top 10 countries by traffic

```python
from alibabacloud_esa20240910.client import Client as EsaClient
from alibabacloud_esa20240910 import models as esa_models
from alibabacloud_tea_openapi import models as open_api_models
from datetime import datetime, timedelta, timezone

def create_client():
    config = open_api_models.Config(
        region_id="cn-hangzhou",
        endpoint="esa.cn-hangzhou.aliyuncs.com",
    )
    return EsaClient(config)

def query_top_countries(site_id: str, limit: int = 10):
    client = create_client()

    end_time = datetime.now(timezone.utc)
    start_time = end_time - timedelta(hours=24)

    resp = client.describe_site_top_data(
        esa_models.DescribeSiteTopDataRequest(
            site_id=site_id,
            start_time=start_time.strftime("%Y-%m-%dT%H:%M:%SZ"),
            end_time=end_time.strftime("%Y-%m-%dT%H:%M:%SZ"),
            fields=[
                esa_models.DescribeSiteTopDataRequestFields(
                    field_name="Traffic",
                    dimension=["ClientCountryCode"]
                )
            ],
            limit=str(limit)
        )
    )

    return resp.body

# Usage
result = query_top_countries("974351557069296", limit=10)
for item in result.data:
    print(f"Metric: {item.field_name} by {item.dimension_name}")
    for i, rank in enumerate(item.detail_data, 1):
        print(f"  #{i} {rank.dimension_value}: {rank.value} bytes")
```

### Query top hosts by requests

```python
def query_top_hosts(site_id: str):
    client = create_client()

    resp = client.describe_site_top_data(
        esa_models.DescribeSiteTopDataRequest(
            site_id=site_id,
            start_time="2026-03-10T00:00:00Z",
            end_time="2026-03-11T00:00:00Z",
            fields=[
                esa_models.DescribeSiteTopDataRequestFields(
                    field_name="Request",
                    dimension=["Host"]
                )
            ],
            limit="10"
        )
    )

    return resp.body
```

### Query top URLs by traffic

```python
def query_top_urls(site_id: str):
    client = create_client()

    resp = client.describe_site_top_data(
        esa_models.DescribeSiteTopDataRequest(
            site_id=site_id,
            start_time="2026-03-10T00:00:00Z",
            end_time="2026-03-11T00:00:00Z",
            fields=[
                esa_models.DescribeSiteTopDataRequestFields(
                    field_name="Traffic",
                    dimension=["RequestUri"]
                )
            ],
            limit="150"
        )
    )

    return resp.body
```

### Query account-level top sites

```python
def query_top_sites():
    """Query top sites at account level (no SiteId)"""
    client = create_client()

    resp = client.describe_site_top_data(
        esa_models.DescribeSiteTopDataRequest(
            # No site_id = account level
            start_time="2026-03-10T00:00:00Z",
            end_time="2026-03-11T00:00:00Z",
            fields=[
                esa_models.DescribeSiteTopDataRequestFields(
                    field_name="Traffic",
                    dimension=["SiteId"]
                )
            ],
            limit="10"
        )
    )

    return resp.body
```

## Response Example

```json
{
  "Data": [
    {
      "FieldName": "Traffic",
      "DimensionName": "ClientCountryCode",
      "DetailData": [
        {"DimensionValue": "HK", "Value": 9132354},
        {"DimensionValue": "CN", "Value": 703820},
        {"DimensionValue": "CA", "Value": 64679},
        {"DimensionValue": "US", "Value": 33979},
        {"DimensionValue": "SG", "Value": 27666}
      ]
    }
  ],
  "StartTime": "2026-03-10T02:47:00Z",
  "EndTime": "2026-03-11T02:47:00Z",
  "SamplingRate": 100
}
```

## Common Use Cases

| Use Case | FieldName | Dimension |
|----------|-----------|-----------|
| Top countries by traffic | `Traffic` | `ClientCountryCode` |
| Top provinces (China) | `Traffic` | `ClientProvinceCode` |
| Top ISPs | `Traffic` | `ClientISP` |
| Top hosts | `Request` | `Host` |
| Top URLs | `Traffic` | `RequestUri` |
| Top sites (account level) | `Traffic` | `SiteId` |
| Top status codes | `Request` | `Status` |

## Error Codes

| HTTP Code | Error Code | Description |
|-----------|------------|-------------|
| 400 | `InvalidParameter.TimeRange` | Time range exceeds limit |
| 400 | `InvalidEndTime.Mismatch` | EndTime earlier than StartTime |
| 400 | `InvalidParameter.Field` | Invalid field name |
| 400 | `InvalidParameter.Dimension` | Invalid dimension |
| 400 | `InvalidTime.Malformed` | Wrong time format |
| 400 | `TooManyDimensions` | Too many query dimensions |
| 400 | `TooManyRequests` | Rate limited |

## Differences from Time-Series API

| Feature | Time-Series | Top-N |
|---------|-------------|-------|
| Purpose | Trend over time | Ranking at a point |
| Returns | Time-stamped values | Ranked values |
| Limit | N/A | 5, 10, or 150 |
| SummarizedData | Yes | No |


path: scripts/check_site_status.py
content: #!/usr/bin/env python3
"""Check ESA site status and configuration overview.

Displays site info and configuration settings (IPv6, dev mode, tiered cache, etc.).
"""

from __future__ import annotations

import argparse
import os

from alibabacloud_esa20240910.client import Client as Esa20240910Client
from alibabacloud_esa20240910 import models as esa_models
from alibabacloud_tea_openapi import models as open_api_models


def create_client() -> Esa20240910Client:
    config = open_api_models.Config(
        region_id="cn-hangzhou",
        endpoint="esa.cn-hangzhou.aliyuncs.com",
    )
    ak = os.getenv("ALICLOUD_ACCESS_KEY_ID") or os.getenv("ALIBABA_CLOUD_ACCESS_KEY_ID")
    sk = os.getenv("ALICLOUD_ACCESS_KEY_SECRET") or os.getenv("ALIBABA_CLOUD_ACCESS_KEY_SECRET")
    [REDACTED_SECRET]"ALICLOUD_SECURITY_TOKEN") or os.getenv("ALIBABA_CLOUD_SECURITY_TOKEN")
    if ak and sk:
        config.access_key_id = ak
        config.access_key_secret = sk
        if [REDACTED_SECRET] = token
    return Esa20240910Client(config)


def main() -> int:
    parser = argparse.ArgumentParser(description="Check ESA site status and configuration")
    parser.add_argument("--site-id", type=int, required=True, help="ESA site ID")
    args = parser.parse_args()

    client = create_client()
    site_id = args.site_id

    # 1. Site info
    site_resp = client.get_site(esa_models.GetSiteRequest(site_id=site_id))
    site = site_resp.body.site_model  # GetSite returns nested site_model object
    print("=== Site Info ===")
    print(f"  Site Name:   {getattr(site, 'site_name', 'N/A')}")
    print(f"  Site ID:     {site_id}")
    print(f"  Status:      {getattr(site, 'status', 'N/A')}")
    print(f"  Access Type: {getattr(site, 'access_type', 'N/A')}")
    print(f"  Plan:        {getattr(site, 'plan_name', 'N/A')}")
    print(f"  Coverage:    {getattr(site, 'coverage', 'N/A')}")
    print(f"  CNAME Zone:  {getattr(site, 'cname_zone', 'N/A')}")
    print()

    # 2. Tiered cache config
    try:
        cache_resp = client.get_tiered_cache(esa_models.GetTieredCacheRequest(site_id=site_id))
        print("=== Tiered Cache ===")
        print(f"  Architecture: {getattr(cache_resp.body, 'cache_architecture_mode', 'N/A')}")
    except Exception as e:
        print(f"=== Tiered Cache ===\n  Error: {e}")
    print()

    # 3. IPv6 config
    try:
        ipv6_resp = client.get_ipv6(esa_models.GetIPv6Request(site_id=site_id))
        print("=== IPv6 ===")
        print(f"  Enabled: {getattr(ipv6_resp.body, 'enable', 'N/A')}")
    except Exception as e:
        print(f"=== IPv6 ===\n  Error: {e}")
    print()

    # 4. Development mode
    try:
        dev_resp = client.get_development_mode(esa_models.GetDevelopmentModeRequest(site_id=site_id))
        print("=== Development Mode ===")
        print(f"  Enabled: {getattr(dev_resp.body, 'enable', 'N/A')}")
    except Exception as e:
        print(f"=== Development Mode ===\n  Error: {e}")
    print()

    # 5. SEO bypass
    try:
        seo_resp = client.get_seo_bypass(esa_models.GetSeoBypassRequest(site_id=site_id))
        print("=== SEO Bypass ===")
        print(f"  Enabled: {getattr(seo_resp.body, 'enable', 'N/A')}")
    except Exception as e:
        print(f"=== SEO Bypass ===\n  Error: {e}")

    return 0


if __name__ == "__main__":
    raise SystemExit(main())


path: scripts/list_dns_records.py
content: #!/usr/bin/env python3
"""List DNS records for an ESA site.

Only works for NS-connected sites. CNAME sites will return an error.
"""

from __future__ import annotations

import argparse
import json
import os
import sys

from alibabacloud_esa20240910.client import Client as Esa20240910Client
from alibabacloud_esa20240910 import models as esa_models
from alibabacloud_tea_openapi import models as open_api_models


def create_client() -> Esa20240910Client:
    config = open_api_models.Config(
        region_id="cn-hangzhou",
        endpoint="esa.cn-hangzhou.aliyuncs.com",
    )
    ak = os.getenv("ALICLOUD_ACCESS_KEY_ID") or os.getenv("ALIBABA_CLOUD_ACCESS_KEY_ID")
    sk = os.getenv("ALICLOUD_ACCESS_KEY_SECRET") or os.getenv("ALIBABA_CLOUD_ACCESS_KEY_SECRET")
    [REDACTED_SECRET]"ALICLOUD_SECURITY_TOKEN") or os.getenv("ALIBABA_CLOUD_SECURITY_TOKEN")
    if ak and sk:
        config.access_key_id = ak
        config.access_key_secret = sk
        if [REDACTED_SECRET] = token
    return Esa20240910Client(config)


def iter_records(client: Esa20240910Client, site_id: int, record_type: str | None = None):
    """Iterate over all DNS records for a site with pagination."""
    page_number = 1
    page_size = 100
    while True:
        req = esa_models.ListRecordsRequest(
            site_id=site_id,
            page_number=page_number,
            page_size=page_size,
        )
        if record_type:
            req.type = record_type
        resp = client.list_records(req)
        records = resp.body.records or []
        for rec in records:
            yield rec
        total = resp.body.total_count or 0
        if page_number * page_size >= total:
            break
        page_number += 1


def to_record(rec) -> dict:
    """Convert a record object to a dict."""
    return {
        "record_id": rec.record_id,
        "record_name": rec.record_name,
        "type": rec.type,
        "data": getattr(rec, "data", None) or getattr(rec, "record_data", None),
        "ttl": rec.ttl,
        "proxied": rec.proxied,
        "create_time": rec.create_time,
        "update_time": rec.update_time,
    }


def main() -> int:
    parser = argparse.ArgumentParser(description="List DNS records for an ESA site")
    parser.add_argument("--site-id", type=int, required=True, help="ESA site ID")
    parser.add_argument("--type", dest="record_type", help="Filter by record type (e.g., A/AAAA, CNAME)")
    parser.add_argument("--json", action="store_true", help="Output as JSON")
    args = parser.parse_args()

    client = create_client()

    try:
        records = [to_record(rec) for rec in iter_records(client, args.site_id, args.record_type)]
    except Exception as e:
        if "CnameSiteRecordUnsupport" in str(e):
            print("Error: DNS record APIs are not available for CNAME-connected sites.", file=sys.stderr)
            print("This site uses CNAME access type. Switch to NS access to manage DNS records.", file=sys.stderr)
            return 1
        raise

    if args.json:
        print(json.dumps({"records": records, "total": len(records)}, indent=2, ensure_ascii=False))
    else:
        if not records:
            print("No DNS records found.")
            return 0
        # TSV output
        print("record_name\ttype\tdata\tttl\tproxied")
        for rec in records:
            print(f"{rec['record_name']}\t{rec['type']}\t{rec['data']}\t{rec['ttl']}\t{rec['proxied']}")

    return 0


if __name__ == "__main__":
    raise SystemExit(main())


path: scripts/list_sites.py
content: #!/usr/bin/env python3
"""List all ESA sites.

Outputs TSV by default. Use --json for JSON output.
"""

from __future__ import annotations

import argparse
import json
import os
from typing import Iterable

from alibabacloud_esa20240910.client import Client as Esa20240910Client
from alibabacloud_esa20240910 import models as esa_models
from alibabacloud_tea_openapi import models as open_api_models


def create_client() -> Esa20240910Client:
    config = open_api_models.Config(
        region_id="cn-hangzhou",
        endpoint="esa.cn-hangzhou.aliyuncs.com",
    )
    ak = os.getenv("ALICLOUD_ACCESS_KEY_ID") or os.getenv("ALIBABA_CLOUD_ACCESS_KEY_ID")
    sk = os.getenv("ALICLOUD_ACCESS_KEY_SECRET") or os.getenv("ALIBABA_CLOUD_ACCESS_KEY_SECRET")
    [REDACTED_SECRET]"ALICLOUD_SECURITY_TOKEN") or os.getenv("ALIBABA_CLOUD_SECURITY_TOKEN")
    if ak and sk:
        config.access_key_id = ak
        config.access_key_secret = sk
        if [REDACTED_SECRET] = token
    return Esa20240910Client(config)


def iter_sites(client: Esa20240910Client) -> Iterable:
    page_number = 1
    page_size = 50
    while True:
        resp = client.list_sites(esa_models.ListSitesRequest(
            page_number=page_number,
            page_size=page_size,
        ))
        for site in resp.body.sites:
            yield site
        total = resp.body.total_count
        if page_number * page_size >= total:
            break
        page_number += 1


def to_record(site) -> dict:
    return {
        "site_id": site.site_id,
        "site_name": site.site_name,
        "status": site.status,
        "access_type": site.access_type,
        "plan_name": site.plan_name,
        "coverage": site.coverage,
        "cname_zone": getattr(site, "cname_zone", None),
        "create_time": site.create_time,
    }


def main() -> int:
    parser = argparse.ArgumentParser(description="List all ESA sites")
    parser.add_argument("--json", action="store_true", help="Output JSON array")
    parser.add_argument("--output", help="Write output to file")
    args = parser.parse_args()

    client = create_client()
    records = [to_record(site) for site in iter_sites(client)]

    if args.json:
        output = json.dumps(records, ensure_ascii=False, indent=2)
    else:
        lines = [
            "site_id\tsite_name\tstatus\taccess_type\tplan_name\tcoverage\tcname_zone\tcreate_time"
        ]
        for r in records:
            lines.append(
                "\t".join(
                    str(r.get(k) or "")
                    for k in [
                        "site_id", "site_name", "status", "access_type",
                        "plan_name", "coverage", "cname_zone", "create_time",
                    ]
                )
            )
        output = "\n".join(lines)

    if args.output:
        with open(args.output, "w", encoding="utf-8") as f:
            f.write(output)
    else:
        print(output)

    return 0


if __name__ == "__main__":
    raise SystemExit(main())


path: scripts/summary_sites_by_plan.py
content: #!/usr/bin/env python3
"""Summarize ESA sites by plan type.

Outputs TSV by default. Use --json for JSON output.
"""

from __future__ import annotations

import argparse
import json
import os
from collections import Counter

from alibabacloud_esa20240910.client import Client as Esa20240910Client
from alibabacloud_esa20240910 import models as esa_models
from alibabacloud_tea_openapi import models as open_api_models


def create_client() -> Esa20240910Client:
    config = open_api_models.Config(
        region_id="cn-hangzhou",
        endpoint="esa.cn-hangzhou.aliyuncs.com",
    )
    ak = os.getenv("ALICLOUD_ACCESS_KEY_ID") or os.getenv("ALIBABA_CLOUD_ACCESS_KEY_ID")
    sk = os.getenv("ALICLOUD_ACCESS_KEY_SECRET") or os.getenv("ALIBABA_CLOUD_ACCESS_KEY_SECRET")
    [REDACTED_SECRET]"ALICLOUD_SECURITY_TOKEN") or os.getenv("ALIBABA_CLOUD_SECURITY_TOKEN")
    if ak and sk:
        config.access_key_id = ak
        config.access_key_secret = sk
        if [REDACTED_SECRET] = token
    return Esa20240910Client(config)


def iter_sites(client: Esa20240910Client):
    page_number = 1
    page_size = 50
    while True:
        resp = client.list_sites(esa_models.ListSitesRequest(
            page_number=page_number,
            page_size=page_size,
        ))
        for site in resp.body.sites:
            yield site
        total = resp.body.total_count
        if page_number * page_size >= total:
            break
        page_number += 1


def main() -> int:
    parser = argparse.ArgumentParser(description="Summarize ESA sites by plan")
    parser.add_argument("--by-status", action="store_true", help="Further break down by status")
    parser.add_argument("--json", action="store_true", help="Output JSON")
    parser.add_argument("--output", help="Write output to file")
    args = parser.parse_args()

    client = create_client()

    if args.by_status:
        counter: Counter = Counter()
        for site in iter_sites(client):
            key = (site.plan_name or "unknown", site.status or "unknown")
            counter[key] += 1
        records = [
            {"plan_name": k[0], "status": k[1], "count": v}
            for k, v in sorted(counter.items())
        ]
        header = "plan_name\tstatus\tcount"
        keys = ["plan_name", "status", "count"]
    else:
        counter = Counter()
        for site in iter_sites(client):
            counter[site.plan_name or "unknown"] += 1
        records = [
            {"plan_name": k, "count": v}
            for k, v in sorted(counter.items())
        ]
        header = "plan_name\tcount"
        keys = ["plan_name", "count"]

    if args.json:
        output = json.dumps(records, ensure_ascii=False, indent=2)
    else:
        lines = [header]
        for r in records:
            lines.append("\t".join(str(r.get(k) or "") for k in keys))
        output = "\n".join(lines)

    if args.output:
        with open(args.output, "w", encoding="utf-8") as f:
            f.write(output)
    else:
        print(output)

    return 0


if __name__ == "__main__":
    raise SystemExit(main())


Security scan verdicts — ClawScan: suspicious | static analysis: clean | VirusTotal: clean | SkillSpector: suspicious (HIGH)