← All posts
Personal Blog  ·  Build
Build · AI Tooling · Internal Services

I stopped hunting for service endpoints, and taught a skill to do it instead.

Three environments, more internal services than I can count, and endpoints scattered across a dozen repos, wikis, and old Slack threads. Here's the first Claude Code skill I built that actually stuck, a router that figures out where an endpoint lives instead of memorizing it.

I moved to the services side of the org earlier this year, and the single most frustrating part of the job had nothing to do with code. It was endpoints. Three environments, more internal services than I can hold in my head, and every one of them scattered across its own repo. Finding the right URL, every single time, felt like the actual job some days.

So I built a skill for it. Not a wiki page, not a spreadsheet, an actual Claude Code skill that I can ask "what's the endpoint for this thing, in this environment" and it just figures it out. It's the first thing I've built with an agent that I reach for daily without thinking about it, so it felt worth writing up.

The problem nobody warns you about

Nobody tells you, when you move into a services-heavy role, that the hardest part isn't the services themselves. It's the bookkeeping. Every service exists in dev, stage, and prod. Every service lives in its own repo, sometimes several. And the actual answer to "what's the URL" is never in one consistent place.

📄
The wiki page

Exists, technically. Last updated two reorgs ago.

💬
The teammate who knows

Correct answer, if they're online and remember which repo.

📁
The README

In a repo you haven't cloned, for a service you don't own.

🧵
The old Slack thread

Somewhere. You remember the gist, not the search terms.

None of these are wrong exactly. They're just slow, and none of them scale past a handful of services before you stop trusting your own notes.

What I built instead

The tempting shortcut is a big lookup table: service name in, URL out. I didn't do that, on purpose. A hardcoded table of URLs goes stale the moment someone reorganizes a repo or spins up a new environment, and then you've just built a second wiki page that lies to people, faster.

Instead the skill treats the git repo that owns a service's deployment config as the one source of truth, every single time, rather than caching an answer and trusting it later. It asks itself one question first: which internal platform does this service actually belong to? There happen to be a couple of different platform families internally, each with its own conventions for naming things per environment. Once it knows which family a service is in, it goes and reads that service's current configuration fresh, instead of recalling what the answer used to be.

→ What happens when I ask for an endpoint
1
Ask

I name a service and, optionally, an environment. Most of the time I don't even specify the environment, it defaults to something sensible and I correct it if needed.

2
Classify

The skill figures out which platform family the service belongs to. This is the part that used to live entirely in my head.

3
Read

It pulls the current state of the repo that owns that service's deployment config, rather than trusting anything it (or I) looked up before. If the config changed yesterday, that's what it sees.

4
Resolve

It parses the Helm chart and values for the environment I asked about to work out the actual host and path, plus anything useful nearby, like a health check or debug view.

5
Verify

Before handing back an answer, it can check with kubectl that the resolved endpoint actually corresponds to something running in the cluster, not just what the config claims should be there.

6
Answer

I get a URL back, not a shrug and a suggestion to "check with the team that owns it."

The skill isn't smart because it remembers URLs. It's useful because it always goes back to the repo and the cluster instead of trusting what it answered last time.

The part that mattered more than I expected

Some of these services need a short-lived auth credential before you can actually call them. Early on I had the skill do the obvious thing: fetch a credential, then print the full curl command so I could see exactly what it was about to run. That's fine for reading, it's a bad habit for running.

So the skill draws a hard line between the two. If I want to see what a request would look like, it shows me the shape of it with the credential left as a placeholder, never the real value. If I actually want to run the request, it fetches the credential itself, uses it, and never prints it to the screen at all. Small distinction, but it's the difference between a tool I trust pasting output into a group chat and one I have to double-check first.

→ A note on scope

Everything above is intentionally high level. No service names, no repo layouts, no internal URLs, no auth mechanics. Just the shape of the problem and the shape of the fix, because the specifics belong to the company, not a blog post.

What running one actually looks like

Finding the URL was only ever half of it. Most of these services don't answer a plain GET, calling one is a real request with a body, an input file or two, and a short-lived credential. So the skill goes the whole distance: from a casual service name to a request I can either read or actually run. Here's the shape of that, with every company-specific detail swapped for a placeholder, because the point is the flow around the request, not the request.

It asks before it assumes

A call like this needs three things only I can supply: the input file (or files) to send, somewhere to write whatever comes back, and any parameters that tune the run. So the skill asks for exactly those, in that order, and only falls back to a placeholder or a default when I stay quiet. It never quietly invents an input path or picks an output directory behind my back.

→ What it collects from me

Inputs — the real file path for each named input the service expects.
Output directory — where the response gets written; it defaults to a per-service folder if I skip it.
Parameters — any knobs the service takes; each one defaults to the exact value already baked into that service's own code when I leave it blank, not to a guess.

The command it hands me to read

If I just want to see what would go out, it prints the whole thing, but with the credential left as a live lookup instead of a value, and a request id wired in for later:

# 1 · fetch a short-lived token into the shell — never printed as a value
export AUTH_TOKEN=$(fetch-service-token --env <environment>)
REQ_ID=$(uuidgen)

# 2 · one call, with inputs / params / output shape already filled in for me
curl -s https://<service-gateway>/<endpoint> \
  -H "Authorization: Bearer $AUTH_TOKEN" \
  -H "x-request-id: $REQ_ID" \
  -F 'request={ …inputs, params, output shape… }' \
  -F "file=@/path/to/input.jpg" \
  -o out/response.multipart

# 3 · surface the id I'll actually search the logs by
echo "x-request-id: $REQ_ID"

Two small details there earn their place. The token is pulled into the shell by that first line and never rendered as text, so the command is safe to read aloud or paste into a review. And the request id is minted up front and echoed after the call, because that id, not my user id, is the key I'll actually search the logs by when something looks off.

Running it, and reading the answer back

When I ask it to actually run rather than print, it doesn't shell out to that command at all. It fetches the token itself, in process, for whichever environment I named, attaches my input files, sends the request, and then does the part I always used to do by hand: it takes the response apart. The reply comes back as one bundle with several pieces packed into it, so the skill splits them, pretty-prints the data pieces as readable files, writes any images out as real image files, and drops the whole set into the output directory I gave it. Then it prints the request id one last time. I go from a service name to a folder of results without ever assembling a request or unpacking a response myself, and without a real credential ever touching my clipboard.

What actually changed

I stopped pinging teammates to ask "what's the URL for this in stage again." I stopped keeping my own half-accurate scratch notes of endpoints I'd looked up before. And when a new environment or a new service shows up, I don't have to relearn anything, the skill's job was never to know every URL, just to know how to find one.

It's a small thing. It's also the first skill I've built that earned a permanent spot in my daily workflow instead of getting used once and forgotten. That distinction turned out to matter more to me than how clever any single piece of it is.

The best internal tools aren't the ones that know everything. They're the ones that know what to ask, and where to look, so you don't have to remember either. Thanks for reading ✦

Comments