---
name: clinic-api
description: |
  Query PingCAP Clinic cluster metadata and observability data through canonical Data Proxy APIs plus capability-preserving compatibility helpers where migration is incomplete. Routes to sub-skills for metadata/components, SQL and Top RU, BYOC diagnostics, Prometheus metrics, Loki logs, activity events, continuous profiling, and diagnostic files. Requires Internal API Key authentication.
---

# Clinic API

Query TiDB Cloud cluster metadata and observability data from PingCAP Clinic. Concrete parameters and examples live in the matching `SUBSKILL.md`; start with `SUBSKILLS_INDEX.md`.

Paths below are relative to this `clinic-api` skill directory.

## Canonical API surface

For migrated capabilities, new workflows must use `/data-proxy/api/v1` routes. In every canonical org-scoped path, `{orgID}` is the **TiDB Cloud/DBAAS org ID**. If context came from the legacy Dashboard response, this value is `tenantID`, not Dashboard `orgID`.

| Area | Canonical method and path | Sub-skill |
|------|---------------------------|-----------|
| Exact metadata | `GET /data-proxy/api/v1/clusters/{clusterID}` | `cluster-metadata` |
| Org cluster list | `GET /data-proxy/api/v1/orgs/{orgID}/clusters` | `cluster-metadata` |
| Org cluster detail | `GET /data-proxy/api/v1/orgs/{orgID}/clusters/{clusterID}` | `cluster-metadata` |
| Components | `GET /data-proxy/api/v1/orgs/{orgID}/clusters/{clusterID}/components` | `cluster-metadata` |
| Activity events | `GET /data-proxy/api/v1/orgs/{orgID}/clusters/{clusterID}/activities` | `activity-hub` |
| Top RU | `GET /data-proxy/api/v1/orgs/{orgID}/clusters/{clusterID}/top-ru/{summary,summary-plan,user-stats}` | `data-proxy` |
| SQL, batch, schema | `POST /data-proxy/api/v1/orgs/{orgID}/clusters/{clusterID}/{sql-queries,sql-query-batches}`, `GET .../table-schemas` | `data-proxy` |
| Service capabilities | `GET /data-proxy/api/v1/capabilities` | `data-proxy` |
| Metrics | `GET/POST /data-proxy/api/v1/orgs/{orgID}/clusters/{clusterID}/metrics/{query,query-range,series}` | `metrics` |
| Business logs | `GET /data-proxy/api/v1/orgs/{orgID}/clusters/{clusterID}/logs/{query-range,labels,series}` and `GET .../logs/labels/{name}/values` | `loki` |
| BYOC Slow Query | `GET .../slow-queries`, `.../slow-queries/available-fields`, `.../slow-queries/detail` | `byoc-diagnostics` |
| BYOC Top SQL | `GET .../top-sql/{instances,summary}` | `byoc-diagnostics` |
| BYOC live Statements | `GET .../statements`, `.../statements/{available-fields,statement-types,plans,plan-detail}` | `byoc-diagnostics` |
| BYOC Continuous Profiling inventory | `GET .../continuous-profiling/group-profiles` and `GET .../group-profiles/{ts}` | `byoc-diagnostics` |
| System logs | `GET /data-proxy/api/v1/system-log-datasources` and datasource-scoped `/logs/...` routes | `loki` |
| TiCDC metadata | `GET /data-proxy/api/v1/orgs/{orgID}/clusters/{clusterID}/ticdc/changefeeds` and `GET .../{changefeedID}` | `ticdc` |

Persisted SQL Statements for supported Premium variants use the canonical SQL query route and logical table `clinic_data_proxy.statements`. BYOC live Statements are a different contract: they use the canonical `GET .../statements` routes and read customer-side O11y directly. Never use the persisted table, S3, or Athena as a BYOC fallback.

Canonical BYOC Continuous Profiling exposes read-only group inventory for a physical `byoc-host` ID only; it does not expose token/download/view/config. Dedicated/Premium profiling and Dedicated plan-replayer/OOM files retain their documented NGM contracts.

## Workflow

1. Read `SUBSKILLS_INDEX.md` and the matching `SUBSKILL.md`.
2. Source `scripts/clinic-shell.sh`, then call `get_clinic_auth`.
3. Resolve exact metadata with `clinic_get_cluster_by_id`; its `orgId` is the canonical DBAAS org ID.
4. For BYOC diagnostics, read `byoc-diagnostics/SUBSKILL.md` before choosing a data path or logical/physical ID.
5. Choose helpers by capability, not route generation. Use canonical helpers for exact metadata and ordinary org-scoped lists. Use route-neutral `clinic_search_clusters` for global discovery, tenant search, or advanced server-side filters; it hides the required compatibility transport. Use explicitly named `clinic_legacy_*` helpers only when maintaining a legacy caller contract.

## Authentication and environments

All requests use an internal Clinic access key as a Bearer token:

```bash
curl -H "Authorization: Bearer ${CLINIC_API_KEY}" \
  "${CLINIC_ENDPOINT}/data-proxy/api/v1/capabilities"
```

Prerequisites: internal user (`internal=true`) and an access key from [clinic.pingcap.com](https://clinic.pingcap.com) → User Profile → Access Key. Store the key in the environment or an uncommitted `.env` file.

| Environment | `CLINIC_ENV` | Endpoint | Suggested env file |
|-------------|--------------|----------|--------------------|
| Staging | `staging` | `https://staging-clinic.pingcap.com` | `.env.staging` |
| Production | `prod` (default) | `https://clinic.pingcap.com` | `.env.prod` |

```bash
# Production (default)
source scripts/clinic-shell.sh
get_clinic_auth

# Staging
CLINIC_ENV=staging ENV_FILE=.env.staging source scripts/clinic-shell.sh
get_clinic_auth
```

The shell entrypoint resolves `CLINIC_ENDPOINT` and `CLINIC_API_BASE`. It works when sourced from bash or zsh.

## Quick start

```bash
source scripts/clinic-shell.sh
get_clinic_auth

CLUSTER_ID="1234567890123456789"
METADATA=$(clinic_get_cluster_by_id "$CLUSTER_ID")
DBAAS_ORG_ID=$(printf '%s' "$METADATA" | jq -r '.orgId')

clinic_get_cluster "$DBAAS_ORG_ID" "$CLUSTER_ID"
clinic_get_topology "$CLUSTER_ID"
clinic_dataproxy_schema "$CLUSTER_ID" "slow_query_logs"
```

For diagnostic downloads without an explicit output path, set `CLINIC_DIAGNOSTIC_DOWNLOAD_DIR`; otherwise files go to `$PWD` at call time.

## Shell helpers

`source scripts/clinic-shell.sh` loads all of these modules:

| File | Canonical/default helpers |
|------|---------------------------|
| `scripts/clinic-auth.sh` | `get_clinic_auth`, `check_clinic_auth` |
| `scripts/clinic_api.py` | route-neutral `search_clusters`; `query_range_batch`; canonical `logs_*`; `byoc_slow_query_*`, `byoc_top_sql_*`, `byoc_statements_*`, `byoc_continuous_profiling_group_*`; `top_ru_summary_plan` |
| `cluster-metadata/scripts/clinic-cluster.sh` | exact `clinic_get_cluster_by_id`; org-scoped `clinic_list_clusters`; global/advanced `clinic_search_clusters`; detail/components/topology helpers |
| `activity-hub/scripts/clinic-activity.sh` | `clinic_get_activities` |
| `data-proxy/scripts/clinic-data-proxy.sh` | SQL/schema/capability helpers; `clinic_top_ru_summary`, `clinic_top_ru_summary_plan`, `clinic_top_ru_user_stats`; LogStats and NGM shared slow-query helpers |
| `byoc-diagnostics/scripts/clinic-byoc-diagnostics.sh` | `clinic_byoc_slow_query_*`, `clinic_byoc_top_sql_*`, `clinic_byoc_statements_*`, `clinic_byoc_continuous_profiling_group_*` |
| `metrics/scripts/clinic-metrics.sh` | `clinic_metrics_query`, `clinic_metrics_query_range`, `clinic_metrics_series` |
| `loki/scripts/clinic-loki.sh` | canonical-name `clinic_logs_query_range`, `clinic_logs_labels`, `clinic_logs_label_values`, `clinic_logs_series` aliases; stable canonical `clinic_loki_{query_range,labels,label_values,series}` retained; datasource-scoped `clinic_system_logs_*`; `clinic_loki_query` remains a legacy-only instant-query alias |
| `ticdc/scripts/clinic-ticdc.sh` | `clinic_ticdc_changefeeds_list`, `clinic_ticdc_changefeed_get` |
| `continuous-profiling/scripts/clinic-continuous-profiling.sh` | existing NGM `clinic_continuous_profiling_*` list/token/download helpers |
| `diagnostic-files/scripts/clinic-diagnostic-files.sh` | `clinic_diagnostic_list_plan_replayer`, `clinic_diagnostic_list_oom_record`, `clinic_diagnostic_download_file` |

## Environment variables

| Variable | Description |
|----------|-------------|
| `CLINIC_API_KEY` | Internal API key (required) |
| `CLINIC_ENV` | `staging` or `prod` (default `prod`) |
| `ENV_FILE` | Environment file path (default `.env`) |
| `CLINIC_ENDPOINT` | Endpoint resolved from `CLINIC_ENV` |
| `CLINIC_API_BASE` | Legacy Clinic base, resolved for compatibility helpers |
| `CLINIC_SYSTEM_LOGS_DATASOURCE` | Optional system-log datasource UID override; discover values with `clinic_system_logs_datasources` |
| `CLINIC_DIAGNOSTIC_DOWNLOAD_DIR` | Optional default output directory for NGM downloads; falls back to `$PWD` |

## ID and query rules

- Canonical `{orgID}` means DBAAS/TiDB Cloud org ID. Canonical metadata exposes it as `orgId`; the legacy Dashboard field for the same identity is `tenantID`.
- Dashboard `orgID` is a Clinic-internal database ID and is valid only for legacy Clinic routes. Never put it into `/data-proxy/api/v1/orgs/{orgID}/...`.
- The global/advanced search helper uses Dashboard `/clusters2`, explicitly sends `include_total=false` by default, and pages through `has_more`. Send `include_total=true` only when an exact count is explicitly required.
- BYOC Slow Query, Top SQL, Statements, logs, and metrics accept logical and physical `byoc-host` IDs. Canonical BYOC Continuous Profiling accepts only the physical ID; resolve it from logical metadata before calling that route.
- BYOC Slow Query, Top SQL, Statements, and Continuous Profiling inventory read customer-side O11y directly. Do not create storage tables, send data-source placement, or switch them to SQL/Athena when a valid response is empty.
- Data Proxy SQL accepts the logical TiDB-compatible dialect. Do not send Athena/Presto identifiers, physical table names, or backend-specific cast types. Common logical tables are `slow_query_logs`, `topsql`, `log_stats`, and `clinic_data_proxy.statements`.
- Exact BYOC data-plane metrics do not receive `tidb_cluster_id` injection. Scope Kubernetes/node queries with their physical namespace, normally `tidb{physicalParentID}`. Other tiers and BYOC cluster-store metric scoping are unchanged.
- Starter/Essential/shared slow query still uses `clinic_ngm_slow_query_list` when warehouse slow-query storage is unavailable; the helper handles the required NGM context.
- For Loki, stage searches from panic/fatal to error to warning to the full stream. Put `|~` regexes in LogQL backticks and pass the full LogQL argument in bash single quotes. Keep `limit` around 1000 or lower.
- System logs are datasource-scoped control-plane logs. Do not add a fake org or cluster path parameter; resolve the datasource with `clinic_system_logs_datasources_table` or `clinic_system_logs_datasource_for_region`.
- Never expose access keys, downstream storage URLs, customer SQL/log payloads, NGM JWTs, or raw profile bytes. Summarize profile analysis instead.

## Legacy compatibility

Existing routes remain available for compatibility. Default skill workflows use canonical paths where the migrated contract has the required capability; route-neutral helpers may hide a compatibility path where parity is incomplete.

- Cluster metadata/components/activity legacy paths remain under `/clinic/api/v1/...`; those paths use Clinic-internal Dashboard `orgID` where applicable.
- Legacy SQL/batch/schema/info routes are `/data-proxy/query`, `/data-proxy/batch-query`, `/data-proxy/schema`, and `/data-proxy/info`.
- Legacy cluster metrics and logs use `/data-proxy/metrics/{clusterID}/api/v1/...` and `/data-proxy/loki/{clusterID}/api/v1/...`.
- Legacy system logs use `/data-proxy/system-logs/api/v1/...` with `datasource` in the query string.
- Explicit compatibility helpers are prefixed `clinic_legacy_`. `clinic_search_clusters` is the public route-neutral global/advanced discovery helper. Historical `clinic_search_cluster_by_id` remains a legacy Dashboard alias because NGM-only workflows still need that context.

## Troubleshooting

| Issue | Resolution |
|-------|------------|
| 401 / 403 | Verify environment/key pairing and `internal=true` |
| 404 on an org-scoped canonical route | Confirm `{orgID}` is DBAAS org ID (`orgId`/legacy `tenantID`), not Dashboard `orgID` |
| BYOC Continuous Profiling rejects a logical ID | Resolve and use the physical `byoc-host` parent ID; canonical BYOC profiling is inventory-only |
| BYOC data-plane metric is empty | Compare the Grafana query and use the physical `tidb{parentID}` namespace; do not add `tidb_cluster_id` to `kube_*` or `node_*` |
| Python reports `CERTIFICATE_VERIFY_FAILED` | Point `SSL_CERT_FILE` at an environment-approved CA bundle, or use the curl-backed shell helpers. Never disable TLS certificate verification. |
| Missing partition filter | Add the table-specific `date` predicate documented in `data-proxy/SUBSKILL.md` |
| SQL syntax / `invalidQuery` | Use the logical Data Proxy SQL dialect, not execution-engine SQL |
| Query timeout | Narrow time/partition filters, aggregate, or raise the supported timeout |

## Changelog

See `changelogs.md`.
