config(git,claude,ssh): templated gitconfig; claude settings/commands/hooks/skills; ssh config

gitconfig: name/email from chezmoi data, gh credential helper by PATH
lookup (portable to darwin). claude: no credentials, history, projects,
sessions, or plugin caches. ssh config as private_ (0600).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Prad NukalaandClaude Fable 5 committed 2026-08-09 14:53:46 -04:00
1 parent 3c691e8dc5
commit 20891f3910
476 files changed
+59086 -7

No files matched your search

@@ -0,0 +1,147 @@
# Cloudflare GraphQL Analytics API
Query analytics data across all Cloudflare products via a single GraphQL endpoint. Covers HTTP requests, Workers metrics, DNS, Firewall events, Network Analytics, and 70+ other datasets.
## Overview
- **Single endpoint** for all analytics: `https://api.cloudflare.com/client/v4/graphql`
- **1,400+ schema types** spanning every Cloudflare product
- **Two scopes**: zone-level (per-domain) and account-level (cross-domain)
- **Adaptive sampling** on high-traffic datasets with confidence intervals
- **No mutations** - read-only analytics (the Mutation type is a stub)
- **Cost-based rate limiting** - default 300 queries per 5 minutes per user (max 320, varies by query cost)
## Quick Decision Tree
```
Need analytics data from Cloudflare?
├─ HTTP traffic (requests, bandwidth, cache) → httpRequestsAdaptiveGroups (zone or account)
├─ Workers performance (CPU, wall time, errors) → workersInvocationsAdaptive (account)
├─ Firewall/WAF events → firewallEventsAdaptive / firewallEventsAdaptiveGroups (zone or account)
├─ DNS query analytics → dnsAnalyticsAdaptive / dnsAnalyticsAdaptiveGroups (zone or account)
├─ Network layer (DDoS, Magic Transit) → *NetworkAnalyticsAdaptiveGroups (account)
├─ Storage (R2, KV, D1, DO) → r2OperationsAdaptiveGroups / kvOperationsAdaptiveGroups / etc. (account)
├─ AI (Workers AI, AI Gateway) → aiInferenceAdaptive / aiGatewayRequestsAdaptiveGroups (account)
├─ Load Balancing → loadBalancingRequestsAdaptiveGroups (zone)
├─ Custom high-cardinality metrics → Workers Analytics Engine (see ../analytics-engine/)
└─ Need raw logs, not aggregates → Logpush (see Cloudflare docs)
```
## Core Concepts
| Concept | Description |
|---------|-------------|
| **Endpoint** | `POST https://api.cloudflare.com/client/v4/graphql` |
| **Explorer** | [graphql.cloudflare.com](https://graphql.cloudflare.com/) - interactive query builder |
| **Viewer** | Root query object: `viewer { zones(...) { ... } }` or `viewer { accounts(...) { ... } }` |
| **Dataset (Node)** | A queryable table under a zone or account (e.g., `httpRequestsAdaptiveGroups`) |
| **Dimensions** | Fields to group by (time buckets, country, status code, script name, etc.) |
| **Metrics** | Aggregation fields: `count`, `sum { ... }`, `avg { ... }`, `quantiles { ... }`, `ratio { ... }` |
| **Filter** | Input object constraining results by time range, dimensions, etc. |
| **Limit** | Maximum rows returned per dataset node (required, max varies by dataset) |
| **OrderBy** | Enum-based sorting: `[field_ASC]` or `[field_DESC]` |
| **Adaptive Sampling** | Nodes with `Adaptive` in the name use ABR sampling; results are statistically representative |
## Query Structure
Every query follows this pattern:
```graphql
{
viewer {
# Zone-scoped
zones(filter: { zoneTag: "ZONE_ID" }) {
datasetName(
filter: { datetime_gt: "...", datetime_lt: "..." }
limit: 1000
orderBy: [datetimeFiveMinutes_DESC]
) {
count
dimensions { ... }
sum { ... }
}
}
# Account-scoped
accounts(filter: { accountTag: "ACCOUNT_ID" }) {
datasetName(filter: { ... }, limit: 100) {
count
dimensions { ... }
sum { ... }
}
}
}
}
```
## Dataset Naming Convention
Dataset names follow a consistent pattern visible in the schema:
| Pattern | Meaning | Example |
|---------|---------|---------|
| `*Adaptive` | Raw rows with adaptive sampling; some (e.g., `workersInvocationsAdaptive`) also support aggregation fields (`sum`, `quantiles`, `avg`) | `httpRequestsAdaptive`, `workersInvocationsAdaptive` |
| `*AdaptiveGroups` | Aggregated data with adaptive sampling | `httpRequestsAdaptiveGroups` |
| `*1hGroups` | Hourly rollups (pre-aggregated) | `httpRequests1hGroups` |
| `*1dGroups` | Daily rollups (pre-aggregated) | `httpRequests1dGroups` |
| `*1mGroups` | Minutely rollups | `httpRequests1mGroups` |
| `Zone*` prefix | Zone-scoped dataset | `ZoneHttpRequestsAdaptiveGroups` |
| `Account*` prefix | Account-scoped dataset | `AccountWorkersInvocationsAdaptive` |
**Prefer `*AdaptiveGroups` nodes** for most use cases - they support flexible time grouping via dimension fields (`datetimeFiveMinutes`, `datetimeHour`, etc.) and are the most commonly used.
## Key Datasets by Product
### Zone-Scoped (per-domain)
| Dataset | Description |
|---------|-------------|
| `httpRequestsAdaptiveGroups` | HTTP traffic: requests, bytes, cache status, bot scores, WAF scores |
| `httpRequests1hGroups` / `1dGroups` / `1mGroups` | Pre-aggregated HTTP rollups (hourly/daily/minutely) |
| `firewallEventsAdaptiveGroups` | WAF, rate limiting, bot management, firewall rule events |
| `dnsAnalyticsAdaptiveGroups` | DNS query volumes, response codes, query types |
| `loadBalancingRequestsAdaptiveGroups` | Load Balancer origin request metrics |
| `pageShieldReportsAdaptiveGroups` | Page Shield CSP reports |
### Account-Scoped (cross-domain)
| Dataset | Description |
|---------|-------------|
| `workersInvocationsAdaptive` | Workers: requests, errors, CPU time, wall time, subrequests |
| `durableObjectsInvocationsAdaptiveGroups` | DO invocations |
| `durableObjectsStorageGroups` / `durableObjectsPeriodicGroups` | DO storage and periodic metrics |
| `d1AnalyticsAdaptiveGroups` / `d1QueriesAdaptiveGroups` | D1 database analytics |
| `r2OperationsAdaptiveGroups` / `r2StorageAdaptiveGroups` | R2 operations and storage |
| `kvOperationsAdaptiveGroups` / `kvStorageAdaptiveGroups` | KV operations and storage |
| `aiInferenceAdaptiveGroups` | Workers AI inference metrics |
| `aiGatewayRequestsAdaptiveGroups` | AI Gateway request analytics |
| `pagesFunctionsInvocationsAdaptiveGroups` | Pages Functions metrics |
| `magicTransitNetworkAnalyticsAdaptiveGroups` | Magic Transit packet/byte analytics |
| `spectrumNetworkAnalyticsAdaptiveGroups` | Spectrum TCP/UDP analytics |
| `gatewayL7RequestsAdaptiveGroups` | Zero Trust Gateway HTTP metrics |
| `gatewayResolverQueriesAdaptiveGroups` | Zero Trust Gateway DNS metrics |
## Reading Order
| Task | Start Here | Then Read |
|------|------------|-----------|
| **First query** | [configuration.md](configuration.md) (auth) -> this README (structure) | [api.md](api.md) |
| **Build a dashboard** | [patterns.md](patterns.md) (time-series, top-N) | [api.md](api.md) (aggregation fields) |
| **Debug query issues** | [gotchas.md](gotchas.md) | [api.md](api.md) (filtering) |
| **Understand sampling** | [gotchas.md](gotchas.md) (sampling section) | [api.md](api.md) (confidence intervals) |
| **Product-specific metrics** | [patterns.md](patterns.md) (per-product examples) | [api.md](api.md) (dataset reference) |
## In This Reference
- **[api.md](api.md)** - Query structure, aggregation fields (sum/avg/quantiles/count), filtering operators, dimensions, dataset details
- **[configuration.md](configuration.md)** - Authentication, API tokens, client setup (curl, JS, Python), introspection
- **[patterns.md](patterns.md)** - Common queries: time-series, top-N, Workers metrics, HTTP analytics, firewall events, multi-zone
- **[gotchas.md](gotchas.md)** - Rate limits, sampling caveats, query cost, common errors, plan-based limits
## See Also
- [GraphQL Analytics API Docs](https://developers.cloudflare.com/analytics/graphql-api/)
- [GraphQL API Explorer](https://graphql.cloudflare.com/)
- [Observability Reference](../observability/) - Workers Logs, Tail Workers, console logging
- [Analytics Engine Reference](../analytics-engine/) - Custom high-cardinality analytics via Workers
- [Web Analytics Reference](../web-analytics/) - Client-side (RUM) analytics
- [API Reference](../api/) - REST API, SDKs, authentication basics
@@ -0,0 +1,175 @@
# GraphQL Analytics API Reference
## Query Root
The schema has a single entry point: `Query.viewer`. Mutations are not supported.
```graphql
{
cost # uint64 -- query cost (returned in response)
viewer {
budget # uint64 -- remaining budget
zones(filter: { zoneTag: "..." }) { ... }
accounts(filter: { accountTag: "..." }) { ... }
}
}
```
## Aggregation Fields
Aggregated dataset nodes (`*Groups`) return these field categories. Not every node has all — use introspection to check.
### count
Total events in the group. Available on `*Groups` nodes but **not** on raw `*Adaptive` nodes (e.g., `workersInvocationsAdaptive` — use `sum { requests }` instead).
### sum
Cumulative metrics. Fields vary by dataset:
```graphql
# HTTP requests
sum { edgeResponseBytes edgeRequestBytes visits edgeTimeToFirstByteMs originResponseDurationMs }
# Workers invocations
sum { requests errors subrequests cpuTimeUs wallTime duration responseBodySize clientDisconnects requestDuration }
```
### quantiles
Percentile distributions (on datasets like `workersInvocationsAdaptive`). Available percentiles: P25, P50, P75, P90, P95, P99, P999 for `cpuTime`, `wallTime`, `requestDuration`, `duration`, `responseBodySize`.
```graphql
quantiles { cpuTimeP50 cpuTimeP99 wallTimeP50 wallTimeP99 }
```
### ratio, avg, uniq, confidence
```graphql
ratio { status4xx status5xx } # float64 (0 to 1) -- HTTP datasets only
avg { sampleInterval } # useful for understanding sampling resolution
uniq { uniques } # unique IP count -- rollup datasets (*1hGroups, *1dGroups) only
confidence(level: 0.95) { # Adaptive datasets only; works on count and sum fields
count { estimate lower upper sampleSize }
}
```
## Dimensions
Dimensions are fields you can group by via the `dimensions` sub-selection.
### Time Dimensions
| Dimension | Granularity |
|-----------|------------|
| `date` | Day |
| `datetime` | Exact timestamp |
| `datetimeMinute` | 1 minute |
| `datetimeFiveMinutes` | 5 minutes |
| `datetimeFifteenMinutes` | 15 minutes |
| `datetimeHour` | 1 hour |
Workers datasets also support `datetimeSixHours`.
### HTTP Request Dimensions (httpRequestsAdaptiveGroups)
83 dimensions available. Key ones:
| Dimension | Description |
|-----------|-------------|
| `clientCountryName` | Country of origin |
| `clientRequestHTTPHost` | Requested hostname |
| `clientRequestHTTPMethodName` | HTTP method |
| `clientRequestPath` | URI path |
| `edgeResponseStatus` | Edge HTTP status code |
| `cacheStatus` | Cache status (hit, miss, dynamic, etc.) |
| `coloCode` | Cloudflare datacenter IATA code |
| `clientIP` / `clientAsn` | Client IP address / ASN |
| `botScore` / `botManagementDecision` | Bot management score (0-99) / verdict |
| `wafAttackScore` / `securityAction` | WAF score / firewall action taken |
| `ja3Hash` / `ja4` | TLS fingerprints |
| `sampleInterval` | ABR sample interval |
### Workers Dimensions (workersInvocationsAdaptive)
`scriptName`, `scriptTag`, `scriptVersion`, `environmentName`, `status`, `usageModel`, `coloCode`, `dispatchNamespaceName`, `isDispatcher`
### Firewall Dimensions (firewallEventsAdaptive)
`action`, `source`, `ruleId`, `clientCountryName`, `clientIP`, `clientAsn`, `userAgent`
## Filtering
### Scope Filters
```graphql
zones(filter: { zoneTag: "ZONE_ID" }) # up to 10 zones
zones(filter: { zoneTag_in: ["Z1", "Z2"] })
accounts(filter: { accountTag: "ACCOUNT_ID" }) # exactly 1 account
```
### Dataset Filters
**Always include a time range filter.** Multiple filters at the same level are implicitly AND-ed.
```graphql
httpRequestsAdaptiveGroups(
filter: { datetime_gt: "2025-01-01T00:00:00Z", datetime_lt: "2025-01-02T00:00:00Z", clientCountryName: "US" }
limit: 1000
)
```
### Filter Operators
| Operator | Meaning | Example |
|----------|---------|---------|
| (none) | equals | `clientCountryName: "US"` |
| `_gt` / `_lt` | greater / less than | `datetime_gt: "..."` |
| `_geq` / `_leq` | greater/less or equal | `datetime_geq: "..."` |
| `_neq` | not equal | `cacheStatus_neq: "hit"` |
| `_in` / `_notin` | in / not in list | `clientCountryName_in: ["US", "GB"]` |
| `_like` / `_notlike` | SQL LIKE with `%` | `clientRequestPath_like: "/api/%"` |
| `_has` / `_hasall` / `_hasany` | array contains | `botDetectionIds_has: "abc"` |
> `_notin` and `_notlike` are in the schema but not in official docs. Confirmed via introspection.
### Boolean Operators (AND / OR)
```graphql
# Explicit AND
filter: { AND: [{ datetime_gt: "..." }, { datetime_lt: "..." }, { clientCountryName: "US" }] }
# Explicit OR
filter: { datetime_gt: "...", OR: [{ edgeResponseStatus: 403 }, { edgeResponseStatus: 429 }] }
```
## Pagination & Sorting
No cursor-based pagination. Use `limit`, `orderBy`, and filter-based offsets:
```graphql
# First page
httpRequestsAdaptiveGroups(filter: { datetime_gt: "..." }, limit: 100, orderBy: [datetime_ASC])
# Next page: filter by last seen value from previous page
httpRequestsAdaptiveGroups(filter: { datetime_gt: "2025-01-01T01:35:00Z" }, limit: 100, orderBy: [datetime_ASC])
```
Sort with `orderBy: [field_ASC]` or `[field_DESC]`. Multiple sort fields supported.
## Settings Node
Query per-node limits and availability:
```graphql
viewer { zones(filter: { zoneTag: "..." }) { settings {
httpRequestsAdaptiveGroups { enabled maxDuration maxNumberOfFields maxPageSize notOlderThan }
} } }
```
## See Also
- [README.md](README.md) - Overview, decision tree, dataset index
- [configuration.md](configuration.md) - Authentication, client setup, introspection queries
- [patterns.md](patterns.md) - Common query patterns (time-series, top-N, per-product)
- [gotchas.md](gotchas.md) - Rate limits, sampling, troubleshooting
@@ -0,0 +1,118 @@
# GraphQL Analytics API Configuration
## Authentication
### API Token (Recommended)
| Permission | Scope | Use Case |
|------------|-------|----------|
| **Account Analytics: Read** | Account-wide | Workers, R2, KV, D1, DO, AI, Network Analytics |
| **Zone Analytics: Read** | Per-zone | HTTP requests, Firewall, DNS, Load Balancing |
| **All zones - Analytics: Read** | All zones | Multi-zone HTTP/Firewall/DNS queries |
Create tokens at: [dash.cloudflare.com > Account API Tokens](https://dash.cloudflare.com/?to=/:account/api-tokens)
```bash
# Verify token
curl -s https://api.cloudflare.com/client/v4/graphql \
-H "Authorization: Bearer $CF_API_TOKEN" \
-H "Content-Type: application/json" \
--data '{"query":"{ viewer { zones(filter: {zoneTag: \"ZONE_ID\"}) { httpRequestsAdaptiveGroups(limit: 1, filter: {datetime_gt: \"2025-01-01T00:00:00Z\"}) { count } } } }"}'
```
### API Key + Email (Legacy)
Not recommended. Use `X-Auth-Email` + `X-Auth-Key` headers instead of `Authorization: Bearer`.
## Client Setup
### curl
```bash
curl -s https://api.cloudflare.com/client/v4/graphql \
-H "Authorization: Bearer $CF_API_TOKEN" \
-H "Content-Type: application/json" \
--data '{
"query": "query($zoneTag: string!, $start: Time!, $end: Time!) { viewer { zones(filter: {zoneTag: $zoneTag}) { httpRequestsAdaptiveGroups(filter: {datetime_gt: $start, datetime_lt: $end}, limit: 10, orderBy: [datetimeFiveMinutes_DESC]) { count dimensions { datetimeFiveMinutes } } } } }",
"variables": { "zoneTag": "ZONE_ID", "start": "2025-01-01T00:00:00Z", "end": "2025-01-02T00:00:00Z" }
}' | jq .
```
### TypeScript / JavaScript
```typescript
const GRAPHQL_ENDPOINT = "https://api.cloudflare.com/client/v4/graphql";
async function queryGraphQL<T>(query: string, variables: Record<string, unknown> = {}): Promise<T> {
const response = await fetch(GRAPHQL_ENDPOINT, {
method: "POST",
headers: { Authorization: `Bearer ${process.env.CF_API_TOKEN}`, "Content-Type": "application/json" },
body: JSON.stringify({ query, variables }),
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${response.statusText}`);
const json = await response.json() as { data: T | null; errors?: { message: string }[] };
if (json.errors?.length) throw new Error(json.errors.map((e) => e.message).join("; "));
return json.data!;
}
```
### Python
```python
import requests, os
def query_graphql(query: str, variables: dict = None) -> dict:
r = requests.post("https://api.cloudflare.com/client/v4/graphql",
headers={"Authorization": f"Bearer {os.environ['CF_API_TOKEN']}", "Content-Type": "application/json"},
json={"query": query, "variables": variables or {}})
r.raise_for_status()
result = r.json()
if result.get("errors"):
raise Exception("; ".join(e["message"] for e in result["errors"]))
return result["data"]
```
### From a Cloudflare Worker
Store the API token as a secret (`CF_API_TOKEN`). Use standard `fetch` to POST to `https://api.cloudflare.com/client/v4/graphql` with the same JSON body format as above. Always check `response.errors` — GraphQL returns 200 even on query failures.
## GraphQL API Explorer
Interactive explorer at [graphql.cloudflare.com](https://graphql.cloudflare.com/) — provides schema docs, autocomplete, variable panel, and shareable queries. Authenticates via your Cloudflare dashboard session.
## Schema Introspection
```graphql
# List zone-scoped datasets
{ __type(name: "zone") { fields { name description } } }
# List account-scoped datasets
{ __type(name: "account") { fields { name description } } }
# Discover dimensions for a dataset
{ __type(name: "ZoneHttpRequestsAdaptiveGroupsDimensions") {
fields { name type { name kind } }
} }
# Discover filter operators for a dataset
{ __type(name: "ZoneHttpRequestsAdaptiveGroupsFilter_InputObject") {
inputFields { name type { name kind } }
} }
```
## Finding Your Zone and Account IDs
- **Zone ID**: Dashboard > select zone > Overview (right sidebar), or via API
- **Account ID**: Dashboard > Account Home URL, or via API
```bash
curl -s https://api.cloudflare.com/client/v4/zones -H "Authorization: Bearer $CF_API_TOKEN" | jq '.result[] | {name, id}'
curl -s https://api.cloudflare.com/client/v4/accounts -H "Authorization: Bearer $CF_API_TOKEN" | jq '.result[] | {name, id}'
```
## See Also
- [README.md](README.md) - Overview, decision tree, dataset index
- [api.md](api.md) - Query structure, aggregation fields, filtering operators
- [patterns.md](patterns.md) - Common query patterns (time-series, top-N, per-product)
- [gotchas.md](gotchas.md) - Rate limits, sampling, troubleshooting
@@ -0,0 +1,110 @@
# GraphQL Analytics API Gotchas & Troubleshooting
## Rate Limits
| Limit | Value |
|-------|-------|
| GraphQL queries per user | **Default 300 per 5 minutes** (max 320, at least 1/sec) |
| General API rate limit | 1200 requests per 5 minutes (shared across all API calls) |
| Zone scope per query | Up to **10 zones** |
| Account scope per query | Exactly **1 account** |
The GraphQL rate limit is separate from the general API limit. Exceeding either results in `HTTP 429` and blocks all API calls for 5 minutes. Enterprise customers can contact support to raise limits.
### "429 Too Many Requests"
**Cause:** Exceeded rate limit.
**Solution:** Batch multiple datasets into single queries, cache results, increase intervals between queries. Use `{ viewer { budget } }` to monitor remaining budget.
## Sampling & Data Accuracy
### Adaptive Bit Rate (ABR) Sampling
Datasets with `Adaptive` in the name use adaptive sampling:
- Results are **statistically representative**, not exact
- Same query may return **slightly different numbers** each run
- Higher traffic = higher sampling rate = more accurate
- `sampleInterval` dimension shows the ratio (1 = no sampling, 10 = ~1-in-10 sampled)
For high-confidence numbers, use `confidence(level: 0.95)` to get estimate bounds. For exact counts, use rollup nodes (`httpRequests1hGroups`, `httpRequests1dGroups`) which are pre-aggregated without sampling.
### Rollup vs. Adaptive
| Feature | Rollup (`*1hGroups`, `*1dGroups`) | Adaptive (`*AdaptiveGroups`) |
|---------|-----------------------------------|-----------------------------|
| Sampling | No (pre-aggregated) | Yes (ABR) |
| Flexibility | Fixed time buckets | Any granularity |
| Dimensions | Fewer | Many more |
| Accuracy | Exact | Statistical estimate |
## Common Errors
### "Access denied" / "authentication error"
**Cause:** Token lacks required permission or wrong scope.
**Solution:** Account-scoped queries need **Account Analytics: Read**. Zone-scoped queries need **Zone Analytics: Read**. Verify: `curl -s https://api.cloudflare.com/client/v4/user/tokens/verify -H "Authorization: Bearer $TOKEN"`
### "field not found" / "Cannot query field"
**Cause:** Wrong dataset name, nonexistent field, or wrong scope (zone vs. account).
**Solution:** Names are case-sensitive camelCase (`httpRequestsAdaptiveGroups`). Zone datasets go under `zones(...)`, account datasets under `accounts(...)`. Use introspection to verify.
### "filter is required" / empty results
**Cause:** Missing required time range filter or incorrect zone/account tag.
**Solution:** Always include `datetime_gt` / `datetime_lt` (or `_geq` / `_leq`).
### "limit is required" / "limit exceeds maximum"
**Cause:** Missing `limit` or exceeding node's max page size.
**Solution:** Always specify `limit`. Max varies by dataset (typically 10,000 for groups, 100 for raw events). Check via settings query.
### "query is too complex" / "query exceeds budget"
**Cause:** Too many fields, datasets, or too broad a time range.
**Solution:** Reduce time range, request fewer dimensions/metrics, break into smaller queries. Monitor `cost` and `budget` in responses.
### 200 Response with Errors
GraphQL returns HTTP 200 even on failures. **Always check `response.errors`:**
```json
{ "data": null, "errors": [{ "message": "filter is required for httpRequestsAdaptiveGroups" }] }
```
## Plan-Based Availability
Not all datasets are available on all plans. Higher plans get more datasets, longer retention (`notOlderThan`), wider time ranges (`maxDuration`), more fields, and larger page sizes.
### "node is not available" / "node is disabled"
**Cause:** Dataset not on your plan, or product not enabled.
**Solution:** Check `settings { <nodeName> { enabled } }`. Some datasets require specific subscriptions (e.g., Network Analytics requires Magic Transit/Spectrum).
## DateTime & Timezone Handling
- All times are **UTC only** (ISO 8601: `"2025-01-15T10:30:00Z"`)
- `Date` type: `"2025-01-15"` (used in `date_geq`/`date_leq` for storage datasets)
- `Time` type: `"2025-01-15T10:30:00Z"` (used in `datetime_gt`/`datetime_lt`)
- Filters are start-inclusive: events that start within the window are included
## Performance Tips
- **Narrow time ranges** are faster and cheaper
- **Select only needed dimensions** — each additional dimension increases cost
- **Use rollup nodes** (`*1dGroups`) for simple daily totals without dimension breakdowns
- **Batch datasets** into one query instead of separate HTTP requests
## See Also
- [README.md](README.md) - Overview, decision tree, dataset index
- [api.md](api.md) - Query structure, aggregation fields, filtering operators
- [configuration.md](configuration.md) - Authentication, client setup, introspection queries
- [patterns.md](patterns.md) - Common query patterns (time-series, top-N, per-product)
@@ -0,0 +1,225 @@
# GraphQL Analytics API Patterns & Best Practices
## Time-Series Queries
Use time dimension granularity matching your range (see Best Practices below).
```graphql
query TrafficTimeSeries($zoneTag: string!, $start: Time!, $end: Time!) {
viewer {
zones(filter: { zoneTag: $zoneTag }) {
httpRequestsAdaptiveGroups(
filter: { datetime_gt: $start, datetime_lt: $end }
limit: 1000
orderBy: [datetimeFiveMinutes_ASC] # or datetimeHour_ASC for longer ranges
) {
count
dimensions { datetimeFiveMinutes }
sum { edgeResponseBytes }
ratio { status4xx status5xx }
}
}
}
}
```
## Top-N Queries
### Top Countries by Request Count
```graphql
query TopCountries($zoneTag: string!, $start: Time!, $end: Time!) {
viewer {
zones(filter: { zoneTag: $zoneTag }) {
httpRequestsAdaptiveGroups(
filter: { datetime_gt: $start, datetime_lt: $end }
limit: 10
orderBy: [count_DESC]
) {
count
dimensions { clientCountryName }
}
}
}
}
```
Use `orderBy: [sum_edgeResponseBytes_DESC]` for top paths by bandwidth. Add `edgeResponseStatus_geq: 400` to the filter for top error status codes.
## Workers Analytics
```graphql
query WorkersOverview($accountTag: string!, $start: Time!, $end: Time!) {
viewer {
accounts(filter: { accountTag: $accountTag }) {
workersInvocationsAdaptive(
filter: { datetime_gt: $start, datetime_lt: $end }
limit: 100
orderBy: [sum_requests_DESC]
) {
sum { requests errors subrequests wallTime }
quantiles { cpuTimeP50 cpuTimeP99 wallTimeP50 wallTimeP99 }
dimensions { scriptName }
}
}
}
}
```
Filter by `scriptName` for a specific Worker. Add `datetimeFiveMinutes` dimension + `orderBy: [datetimeFiveMinutes_ASC]` for error rate over time.
## Firewall / Security
```graphql
query RecentFirewallEvents($zoneTag: string!, $start: Time!) {
viewer {
zones(filter: { zoneTag: $zoneTag }) {
firewallEventsAdaptive(
filter: { datetime_gt: $start }
limit: 50
orderBy: [datetime_DESC]
) {
action source clientIP clientCountryName userAgent
clientRequestHTTPHost clientRequestPath ruleId datetime
}
}
}
}
```
For aggregated firewall stats, use `firewallEventsAdaptiveGroups` with `action: "block"` filter and group by `ruleId`, `source`, `datetimeHour`.
## DNS Analytics
```graphql
query DNSQueryVolume($zoneTag: string!, $start: Time!, $end: Time!) {
viewer {
zones(filter: { zoneTag: $zoneTag }) {
dnsAnalyticsAdaptiveGroups(
filter: { datetime_gt: $start, datetime_lt: $end }
limit: 500
orderBy: [datetimeFiveMinutes_ASC]
) {
count
dimensions { datetimeFiveMinutes }
}
}
}
}
```
## Storage Analytics (Account-Scoped)
R2, KV, and D1 use `date` (Date type) filters instead of `datetime` (Time type).
```graphql
# R2 operations
r2OperationsAdaptiveGroups(filter: { date_geq: $start, date_leq: $end }, limit: 100, orderBy: [date_DESC]) {
dimensions { date bucketName actionType }
sum { requests }
}
# KV operations
kvOperationsAdaptiveGroups(filter: { date_geq: $start, date_leq: $end }, limit: 100, orderBy: [date_DESC]) {
dimensions { date actionType }
sum { requests }
}
# D1 analytics
d1AnalyticsAdaptiveGroups(filter: { date_geq: $start, date_leq: $end }, limit: 100, orderBy: [date_DESC]) {
dimensions { date databaseId }
sum { readQueries writeQueries rowsRead rowsWritten }
}
```
## Cache Analytics
```graphql
query CacheStatusBreakdown($zoneTag: string!, $start: Time!, $end: Time!) {
viewer {
zones(filter: { zoneTag: $zoneTag }) {
httpRequestsAdaptiveGroups(
filter: { datetime_gt: $start, datetime_lt: $end }
limit: 20
orderBy: [count_DESC]
) {
count
dimensions { cacheStatus }
sum { edgeResponseBytes }
}
}
}
}
```
For cache hit ratio over time, use aliases to query the same dataset twice — once with `cacheStatus: "hit"` filter and once without — then compute the ratio client-side.
## Multi-Dataset Queries
A single request can query multiple datasets, avoiding extra HTTP round-trips:
```graphql
query DashboardOverview($zoneTag: string!, $start: Time!, $end: Time!) {
viewer {
zones(filter: { zoneTag: $zoneTag }) {
httpTraffic: httpRequestsAdaptiveGroups(
filter: { datetime_gt: $start, datetime_lt: $end }, limit: 1
) { count sum { edgeResponseBytes } ratio { status4xx status5xx } }
firewallEvents: firewallEventsAdaptiveGroups(
filter: { datetime_gt: $start, datetime_lt: $end }, limit: 5, orderBy: [count_DESC]
) { count dimensions { action source } }
dnsQueries: dnsAnalyticsAdaptiveGroups(
filter: { datetime_gt: $start, datetime_lt: $end }, limit: 1
) { count }
}
}
}
```
## AI & Gateway Analytics
```graphql
# Workers AI inference
aiInferenceAdaptiveGroups(
filter: { datetime_gt: $start, datetime_lt: $end }, limit: 100, orderBy: [datetimeHour_DESC]
) {
count
sum { totalInputTokens totalOutputTokens totalRequestBytesIn }
dimensions { modelId datetimeHour }
}
# AI Gateway requests
aiGatewayRequestsAdaptiveGroups(
filter: { datetime_gt: $start, datetime_lt: $end }, limit: 100, orderBy: [datetimeHour_DESC]
) {
count
dimensions { gateway provider model datetimeHour }
sum { cachedTokensIn cachedTokensOut uncachedTokensIn uncachedTokensOut }
}
```
Both are account-scoped — nest under `accounts(filter: { accountTag: $accountTag })`.
## Best Practices
**Always include time filters.** Queries without time filters scan all data and are slow/expensive.
**Match time granularity to range:**
| Time Range | Recommended Dimension |
|------------|----------------------|
| < 6 hours | `datetimeMinute` or `datetimeFiveMinutes` |
| 6-48 hours | `datetimeFiveMinutes` or `datetimeFifteenMinutes` |
| 2-14 days | `datetimeHour` |
| 14+ days | `date` |
**Use aliases** for querying the same dataset with different filters in one request.
**Request only needed fields.** Extra dimensions and metrics increase query cost.
## See Also
- [README.md](README.md) - Overview, decision tree, dataset index
- [api.md](api.md) - Query structure, aggregation fields, filtering operators
- [configuration.md](configuration.md) - Authentication, client setup, introspection queries
- [gotchas.md](gotchas.md) - Rate limits, sampling, troubleshooting