On this page
The four ways at a glance
| Way | What identifies the caller | What it costs you | Where it fails |
|---|---|---|---|
| Gateway keys | A key, token or application per client, checked by the gateway | A gateway in front of the API, and a credential per caller | Shared keys, keyless routes, and calls that skip the gateway |
| Access logs | Whatever the request carries: source IP, user agent, a header | Nothing new: logs you already keep | IPs and user agents rarely name a team; retention is short |
| Distributed tracing | The calling service’s service.name on its spans | Instrumenting every caller, and a backend to keep the data | Uninstrumented callers, sampling, and callers that did not run in the window |
| Declared subscriptions | A team says which endpoints it calls | Each team marks its endpoints once | A team that never subscribes is invisible until someone finds it |
The first three answer “who called?”. Only the fourth answers “who will be hurt?” for a caller that runs once a month or has not shipped yet. That difference decides which one to use for what.
1. Gateway keys
If every request goes through a gateway and every caller has its own credential, the gateway knows who called what. That is how external APIs are run. Kong Gateway identifies a Consumer through an authentication plugin such as key auth or OAuth. In Gravitee, consumers subscribe to a plan through an application. AWS API Gateway usage plans use API keys to identify clients.
- Good: exact call counts per client, per endpoint, with timestamps. The best evidence that a client still calls an endpoint, or stopped.
- A key is not a team. A key belongs to an application. Someone still has to keep the list of which team owns which key, and that list goes stale like any other.
- Shared and missing keys hide callers. Five services that share one key look like one. In Gravitee, a keyless plan needs no subscription, so its callers show up as “unknown application”.
- Internal calls often skip the gateway. Service-to-service traffic inside a cluster usually goes straight to the service. A gateway only sees what is routed through it.
- Keys are not security. AWS says not to use API keys for authentication or authorization. Use them to identify callers, alongside real authentication.
2. Access logs
Every service or load balancer already logs requests. nginx’s default combined format records the source address, the request line and the User-Agent header. That is enough to see that GET /orders/{id}/invoice was called last Tuesday, and often not enough to see by whom.
- Source IPs do not stay put. In Kubernetes, Pods are created and destroyed and each gets its own IP, so an address from last week may belong to a different service today. Behind a proxy or NAT, many callers share one address.
- The user agent is whatever the client sends. An HTTP library default, such as a language runtime’s name and version, tells you nothing about the team.
- Retention decides what you can see. A caller that runs once a quarter is not in thirty days of logs.
- A cheap fix: ask internal callers to send a header that names the calling service, and log it. It works as well as the discipline behind it.
3. Distributed tracing
With OpenTelemetry, each service names itself in the service.name resource attribute, usually set with the OTEL_SERVICE_NAME environment variable. Traces then show which service called which, and the Collector’s service graph connector (alpha) turns client and server spans into per-edge request metrics.
export OTEL_SERVICE_NAME=checkout-websum by (client) (increase(traces_service_graph_request_total{server="orders-api"}[14d]))- Good: the most accurate picture of which services call each other, with errors and latency, without asking anyone.
- Only instrumented callers. A service without instrumentation can appear only as a virtual node guessed from the other side, if at all. Batch jobs and scripts are often the ones missing.
- Sampling and windows. If you keep a fraction of traces, a caller that makes a few calls a month may not be in them. A service graph is a picture of a time window.
- The edge is a service, not an endpoint, not a team. The connector’s edges are client and server services. To know which endpoint, you go back to span attributes; to know which team, you map
service.nameto an owner.
4. Declared subscriptions
The consumer team says which endpoints it calls, in one shared place. Nothing is observed; the list is a statement by the people who know. This is how a team-level API dependency map is kept.
- Good: the edge is a team with a channel, per endpoint, before any traffic exists. A client that ships next month can subscribe today.
- The producer reads the list, it does not keep it. No one on the API team has to remember who called last year.
- It is only as complete as the teams make it. A team that never subscribed is invisible. That gap has to show up somewhere, for example as endpoints with no consumers.
- It needs a reason to exist. Teams subscribe when it gets them something: an alert before a breaking change merges, in their own channel.
Which one answers which question
| Question | Best source | Check it against |
|---|---|---|
| Who must hear about this change before it merges? | Declared subscriptions | Traces or logs, for callers the list missed |
| Who called this endpoint last week? | Gateway keys or traces | Access logs |
| Can I delete this endpoint? | Traffic over a long window | The subscribed teams, asked directly |
| Which team owns the caller I found in the logs? | Your service catalog or the team map | Asking in that team’s channel |
Most companies with internal APIs end up with a declared list for warnings and traffic for proof. How to find which teams use an endpoint walks through the search when you have no gateway.
Where Graphwake fits
Graphwake is the declared side. Consumer teams subscribe to the endpoints they call, per whole endpoint, and Graphwake reads your OpenAPI specs to know every endpoint that exists. It needs no traffic, logs or code access, and it alerts the subscribed teams when a breaking change comes.
Teams subscribe per endpoint
Each consumer team marks the endpoints it calls. A breaking change alerts only those teams, in Slack, Microsoft Teams or email.
Gaps are visible
An endpoint no team subscribes to is flagged as an orphan, and a breaking change there counts toward the impact score as a blind spot.
Discovery suggests teams
When nobody is mapped to a new or breaking-changed endpoint, rule-based Discovery suggests likely teams with a reason and a score. A team becomes a subscriber only when someone confirms it.
A follow is not a consumer
Anyone can follow an endpoint and get its alerts by email. A follow never counts as a consumer on the map or in the score.
Use it next to your traffic data: when traces show a caller that is not subscribed, ask that team to subscribe, and the next alert reaches them. More on routing in API breaking change alerts.
When traffic-based tracking alone is enough
- You need proof, not intent. Before deleting an endpoint, traffic over a long window is the evidence that nobody still calls it.
- Your consumers are external customers. They already have keys, and gateway analytics know each one.
- You bill or rate-limit per caller. That needs per-request counts from a gateway or your own metering.
Questions
What is API consumer tracking?
Keeping a current answer to who calls an API and which endpoints they use. It can come from traffic (gateway keys, access logs, traces) or from consumers declaring what they call.
Can I track internal API consumers without a gateway?
Yes. Use access logs with a header that names the calling service, distributed tracing with service.name set on every caller, or have consumer teams subscribe to the endpoints they call. Each misses something, so combine two.
Are API keys enough to know who my consumers are?
Only if every caller has its own key and every call goes through the gateway. Even then a key names an application, not a team, so you still need to map keys to teams.
Why not just use traces?
Traces show who called in the window you keep, from services that are instrumented. A monthly job, an unsampled call or a client that has not shipped yet is not there. Use traces to check a declared list, not to replace it.
Pick an endpoint. See who it reaches. 7-day free trial. Cancel before day 7 and you pay nothing.
Start free trialSources and last verified
- developer.konghq.com/gateway/entities/consumer/
- documentation.gravitee.io/apim/secure-and-expose-apis/subscriptions
- documentation.gravitee.io/apim/secure-and-expose-apis/plans/keyless
- docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-api-usage-plans.html
- nginx.org/en/docs/http/ngx_http_log_module.html
- kubernetes.io/docs/concepts/services-networking/service/
- opentelemetry.io/docs/specs/semconv/registry/attributes/service/
- opentelemetry.io/docs/languages/sdk-configuration/general/
- github.com/open-telemetry/opentelemetry-collector-contrib/blob/main/connector/servicegraphconnector/README.md
Sources checked on 2026-11-09.