DocsRecipes

Recipes

API product and developer-platform analytics

4 min readReviewed July 2026

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:

  1. developer journey and time to first value
  2. active applications and accounts
  3. endpoint adoption breadth and depth
  4. success, latency, and rate-limit outcomes
  5. API-version migration
  6. quota pressure and cost concentration
  7. support and product outcomes
  8. 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.
Need a hand?

Tell us what you’re building and we’ll point you in the right direction.

Contact support