On this page
Before you start: pin down the endpoint
Write the endpoint the way each search will see it. In the spec it is GET /orders/{id}/invoice. In code it may be a template string, a path built from pieces, or a generated client method named after the operationId. In logs it is a concrete path such as /orders/981/invoice?format=pdf. Note the service’s hostnames too: internal DNS names, the old name before the rename, the load balancer address.
Step 1: search the code
Search every repository you can read, not just the ones you know call the service. On GitHub, code search takes a regular expression between slashes, with the slashes inside escaped:
org:example-org /orders\/[^\/]+\/invoice/Search for the client method name as well, because a generated client hides the path. With the repositories cloned locally, ripgrep does both at once:
rg -n 'orders/[^/]+/invoice|getOrderInvoice' ~/src/- Default branch only. GitHub code search indexes the default branch of each repository, and skips files over 350 KiB. A client on a long-lived branch is not found.
- Paths built at runtime.
base + "/" + resource + "/" + id + "/invoice"matches neither search. Look for the service’s hostname or config key as well. - Code you cannot see. Scripts on someone’s laptop, notebooks, other organisations’ repositories, and spreadsheets that call the API are not in your search.
- Found is not called. A match may be dead code. Treat each hit as a team to ask, not as a confirmed consumer.
Step 2: read the access logs
The service, its ingress or its load balancer already logs requests. In nginx’s default combined format, splitting a line on double quotes puts the request line in field 2 and the user agent in field 6. This counts calls to the endpoint by source address and user agent:
awk -F'"' '$2 ~ /^GET \/orders\/[^\/ ?]+\/invoice[ ?]/ { split($1, a, " "); print a[1], $6 }' access.log \
| sort | uniq -c | sort -rn 2 10.42.3.17 checkout-web/2.3 okhttp/4.12
1 10.42.7.4 python-requests/2.32.3A user agent like checkout-web/2.3 names its service; a library default like python-requests/2.32.3 does not. For an address inside a Kubernetes cluster, find the Pod that holds it now:
kubectl get pods -A -o wide | grep 10.42.7.4- Addresses move. Kubernetes creates and destroys Pods, and each gets its own IP, so last week’s address may belong to another service today. Behind a proxy or NAT, many callers share one address.
- Retention. A job that runs once a quarter is not in thirty days of logs. Note the window you searched.
- If the logs cannot tell you who: add one log line to the endpoint’s handler that records a caller header and the source, and ask internal callers to send that header. Leave it for a full business cycle, including a month end.
Step 3: ask, but only the teams the evidence points at
A question in a company-wide channel gets silence, and silence is not a no. Ask in the channels of the teams that owned the code hits and the services behind the log entries. Be specific: name the endpoint, say what you plan to change and when, and ask for a yes or no by a date.
Name the endpoint and the change
GET /orders/{id}/invoicewill stop returningtotalin its response.Say why you are asking them
Your repository calls it, or your service called it last week. Link the hit or the log line.
Ask for a reply by a date
Yes, no, or not sure, before the date. Follow up with anyone who has not answered.
Why each step misses someone
| Step | Finds | Misses |
|---|---|---|
| Search the code | Callers in repositories you can read, including ones that have not shipped | Other branches, paths built at runtime, scripts and notebooks outside the repositories |
| Read the logs | Callers that actually ran in the window | Rare jobs, callers behind shared addresses, anything older than retention |
| Ask | Context: what the caller does with the field, and how long it needs | Teams that do not know they call it, and teams that do not answer |
Together they catch most callers. Not all: a script nobody remembers can still break. That is why the last step is the one that pays off next time.
Step 4: write it down where it stays current
At the end you have a list of teams for one endpoint. Put it somewhere the next person will find it, per endpoint, and let the consumer teams keep it: they know when they start or stop calling something. A wiki page kept by the producer goes stale; a subscription kept by the consumer gets fixed when it is wrong, because the consumer is the one who missed the alert. That list is a team-level API dependency map.
Where Graphwake fits
Graphwake keeps step 4 for you. Consumer teams subscribe to the endpoints they call, Graphwake reads your OpenAPI specs to know every endpoint that exists, and a breaking change alerts only the subscribed teams, in Slack, Microsoft Teams or email. Steps 1 to 3 stay useful for finding a team that has not subscribed yet, and Discovery suggests the likely ones.
Who calls it, in one place
Each endpoint shows its owner and its consumer teams. An endpoint no team subscribes to is flagged as an orphan.
Discovery suggests the likely teams
Rules over data Graphwake already holds, such as subscriptions to neighbouring endpoints and shared schemas, suggest teams with a reason and a score. Someone confirms before a team becomes a subscriber.
Ask from your editor
With Graphwake MCP on Pro and Enterprise, your AI assistant can ask who owns and who consumes an endpoint. See the MCP how-to.
Blind spots before the merge
A breaking change on an endpoint with no subscribers counts toward the impact score, so the missing team shows up while the change is a pull request.
To decide who gets told once you know, read API breaking change alerts.
When a gateway or tracing alone is enough
- You have a gateway with a key per caller. Its analytics already list the clients of each endpoint, with call counts.
- You run tracing on every service. A service graph built from traces shows the callers. See API consumer tracking for what traces miss.
- One team calls the API, and you know it. Ask them.
Questions
How do I find out who uses an API endpoint without a gateway?
Search every repository for the path and the generated client method, count callers of the endpoint in the service’s access logs, then ask only the teams those two point at. Write the result down per endpoint so it is not lost.
Can I find API callers from the logs alone?
Partly. Logs show calls that happened within retention, by source address and user agent. Addresses change and library user agents do not name a team, so logs give you leads to check, not a list.
Does GitHub code search find every call to an endpoint?
No. It searches the default branch of each repository, skips files over 350 KiB, and cannot match a path built at runtime. Search for the hostname and the client method name too.
How do I keep the list of consumers up to date?
Have the consumer teams keep it: each team subscribes to the endpoints it calls, and gets alerts for them. A list kept by the API owner goes stale because the owner does not see new callers.
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
- docs.github.com/en/search-github/github-code-search/understanding-github-code-search-syntax
- docs.github.com/en/search-github/github-code-search/about-github-code-search
- nginx.org/en/docs/http/ngx_http_log_module.html
- kubernetes.io/docs/concepts/services-networking/service/
Sources checked on 2026-11-09.