An API product has two experiences: the integration journey of the developer and the runtime experience of every request. Measure both without turning GraphJSON into the request path or the authoritative usage ledger.
This recipe is for teams operating a customer-facing API or developer platform. For building a customer-visible usage page, use the API usage customer portal.
Define the analytical grains#
Do not collapse every question into one request table.
| Grain | Identifier | Questions |
|---|---|---|
| API request | request_id |
Reliability, latency, endpoint volume |
| API consumer | api_consumer_id |
Authentication and integration behavior |
| Application | application_id |
Production integrations and environments |
| Account | account_id |
Adoption, retention, plan, value |
| Credential lifecycle | credential_id |
Created, rotated, revoked; never the secret |
| API version | api_version |
Migration and deprecation |
Use opaque identifiers. Never log the credential value, authorization header, complete request body, complete response body, or sensitive query parameters.
Emit one terminal request fact#
Record a compact event after the request reaches a terminal outcome:
{
"event": "api_request_completed",
"event_id": "reqevt_01J...",
"request_id": "req_01J...",
"account_id": "acct_42",
"application_id": "app_prod_7",
"api_consumer_id": "consumer_91",
"endpoint_template": "POST /v1/reports",
"api_version": "2026-06-01",
"status_family": "2xx",
"outcome": "succeeded",
"duration_ms": 184,
"response_bytes": 9210,
"rate_limit_remaining_bucket": "high",
"release": "api-2026.07.26",
"environment": "production",
"schema_version": 1
}
Use an endpoint template, not a raw path containing resource IDs. Use a small outcome taxonomy:
succeeded
rejected_auth
rejected_permission
rejected_rate_limit
invalid_request
dependency_failed
internal_failed
timed_out
HTTP status remains useful, but an explicit product outcome is easier to govern.
Keep delivery off the request path#
The API response must not wait on GraphJSON. Recommended flow:
API request
→ authoritative application work
→ response outcome fixed
→ durable operational record or outbox
→ analytical event delivered asynchronously
For low-criticality facts, a bounded non-blocking sender may be sufficient. For billable usage, entitlements, or contractual limits, write the authoritative record first and use GraphJSON only for analysis. Follow Reliable event delivery.
Instrument the developer journey#
Request telemetry cannot show why an integration never sent its first request. Emit lifecycle facts:
{
"event": "api_application_created",
"event_id": "evt_app_01J...",
"account_id": "acct_42",
"application_id": "app_prod_7",
"environment": "production",
"schema_version": 1
}
Useful milestones include:
- developer account created
- documentation example copied
- API application created
- credential created
- first authenticated request
- first successful production request
- first value-producing operation
- second active week
Do not treat credential creation as activation. Activation should represent an outcome the customer values.
Measure time to first successful value#
Define the start and value events explicitly:
WITH starts AS (
SELECT
JSONExtractString(json, 'account_id') AS account_id,
min(timestamp) AS started_at
FROM developer_events
WHERE JSONExtractString(json, 'event') = 'api_application_created'
GROUP BY account_id
),
value AS (
SELECT
JSONExtractString(json, 'account_id') AS account_id,
min(timestamp) AS valued_at
FROM api_requests
WHERE
JSONExtractString(json, 'event') = 'api_request_completed'
AND JSONExtractString(json, 'outcome') = 'succeeded'
AND JSONExtractString(json, 'endpoint_template') =
'POST /v1/reports'
GROUP BY account_id
)
SELECT
quantileExact(0.50)(valued_at - started_at) AS p50_seconds,
quantileExact(0.90)(valued_at - started_at) AS p90_seconds,
count() AS activated_accounts
FROM starts
INNER JOIN value USING account_id
WHERE valued_at >= started_at
Keep accounts that have not activated in the funnel denominator. A latency query over converters alone cannot describe activation rate.
Measure endpoint adoption#
Count accounts, not only calls:
SELECT
JSONExtractString(json, 'endpoint_template') AS endpoint,
uniqExact(JSONExtractString(json, 'account_id')) AS active_accounts,
count() AS requests,
round(requests / nullIf(active_accounts, 0), 2) AS requests_per_account
FROM api_requests
WHERE
JSONExtractString(json, 'event') = 'api_request_completed'
AND JSONExtractString(json, 'outcome') = 'succeeded'
AND timestamp >= now() - INTERVAL 30 DAY
GROUP BY endpoint
ORDER BY active_accounts DESC
Display breadth and depth together. One high-volume customer can make an endpoint look broadly adopted when only one account uses it.
Build a reliability view#
For each endpoint and release, show:
- request opportunities
- success rate
- invalid request rate
- authentication and permission rejection rates
- rate-limit rejection rate
- p50, p95, and p99 latency
- timeout rate
- active accounts affected
Keep customer-caused invalid requests separate from server failures, but do not use that distinction to hide a confusing API design. A sudden increase in invalid requests after a documentation or version change is a product signal.
SELECT
JSONExtractString(json, 'endpoint_template') AS endpoint,
count() AS requests,
countIf(
JSONExtractString(json, 'outcome') = 'succeeded'
) AS succeeded,
round(100 * succeeded / nullIf(requests, 0), 3) AS success_pct,
quantileExact(0.95)(
JSONExtractInt(json, 'duration_ms')
) AS p95_ms
FROM api_requests
WHERE
JSONExtractString(json, 'event') = 'api_request_completed'
AND timestamp >= now() - INTERVAL 7 DAY
GROUP BY endpoint
ORDER BY requests DESC
Use a dedicated observability system for trace waterfalls and paging. Use GraphJSON to connect reliability with account, plan, release, and product outcomes.
Track version migration and deprecation#
Attach the effective API version to every request. Then report:
- active accounts by version
- request share by version
- deprecated-version traffic
- accounts that returned to an older version
- endpoint compatibility failures
- days since an account's last old-version request
Create an account-level migration table before contacting customers:
account
current versions
last deprecated request
deprecated endpoints used
request volume
owner
migration status
Do not infer completion from one new-version request. Require a settled window with new-version activity and no old-version activity.
Measure quota pressure without enforcing from analytics#
GraphJSON can show:
- accounts approaching quota
- rate-limit rejections
- burst patterns
- unused paid capacity
- endpoint-level cost concentration
The API gateway, billing ledger, or entitlement service must remain authoritative. Bucket remaining capacity rather than logging sensitive contract details on every request when an exact value is unnecessary.
Use Usage metering and quota analytics when usage affects billing or contractual limits.
Connect API use to customer value#
Join account-level API behavior to mature outcomes:
- retained account after 30 or 90 days
- production activation
- expansion
- support demand
- successful customer workflow
- cost-to-serve
Control for account maturity and size. Heavy usage can be evidence of success, inefficient integration, retries, or abuse. Do not label volume as value without a product outcome.
Build the operating dashboard#
Recommended sections:
- developer journey and time to first value
- active applications and accounts
- endpoint adoption breadth and depth
- success, latency, and rate-limit outcomes
- API-version migration
- quota pressure and cost concentration
- support and product outcomes
- instrumentation health
Keep the reporting time zone and definition version visible.
Launch checklist#
- Request, application, consumer, account, and credential grains are distinct.
- Credential secrets and complete payloads are never logged.
- Endpoint names use bounded templates rather than raw paths.
- One terminal event represents each request opportunity.
- Analytics delivery cannot delay or fail the API request.
- Activation represents customer value, not credential creation.
- Adoption reports include active accounts and request depth.
- Reliability distinguishes outcomes without hiding product friction.
- Version completion requires a settled old-version-free window.
- Quotas and billing remain authoritative outside GraphJSON.
- API behavior is connected to mature customer outcomes carefully.
- Control totals reconcile with the gateway or request authority.