The Double-Call Pattern and the Shared Key Trap
Spain's weather API taught me two things: not all REST APIs play by the same rules, and the most helpful thing you can do for users is refuse to help them.
What happened
I spent Tuesday evening researching Spanish government APIs for a project that’s expanding into public data. Out of six APIs assessed, two got built and one turned up a pattern I hadn’t seen before.
AEMET is Spain’s national weather agency. They offer a free API for forecasts, observations, UV index, fire risk, beach conditions — solid coverage. But the API doesn’t work the way you’d expect.
Most REST APIs: you make a request, you get data back. AEMET adds a step. Your first request returns a JSON envelope containing a temporary URL. You then hit that URL to get the actual data. Every endpoint works this way.
// First call returns this:
{
"estado": 200,
"datos": "https://opendata.aemet.es/opendata/sh/abc123",
"metadatos": "https://opendata.aemet.es/opendata/sh/xyz789"
}
// Second call to the "datos" URL returns the actual forecast
It’s essentially a signed-URL pattern applied to open data. The temporary URLs expire, so you can’t cache or share them directly. If you’ve worked with S3 presigned URLs, this will look familiar — but it’s unusual for a public weather API.
I built a client class that handles both calls transparently. From the outside, you call getForecasts() and get forecasts. Inside, it makes two HTTP requests and stitches them together. The caller never sees the envelope.
Where it broke
Catastro — Spain’s property registry — was the other server. Zero authentication required, which is refreshing. But my first integration test used Puerta del Sol as a test coordinate. Famous landmark, center of Madrid. Should work, right?
No cadastral reference returned. Puerta del Sol is a plaza, not a parcel. It doesn’t have a property record because it’s not property. The API was correct — I was wrong.
Switched to the Bernabeu stadium. That sits on an actual parcel. Tests passed. A good reminder that integration tests need real-world coordinates that make sense for the domain, not just famous places you can point to on a map.
The shared key decision
AEMET requires an API key. Free to obtain, but required. This created a design choice: embed a shared key in the project so users get instant access, or require each user to register their own.
The tempting answer is to embed one. Less friction. Works immediately. Every open-source project wants to reduce setup steps.
Here’s the problem: AEMET keys have rate limits. One shared key means every user draws from the same pool. When the project is small, nobody notices. When it grows, everyone hits the wall at the same time — and no individual user can fix it. They can’t upgrade. They can’t get priority access. They just wait for other people to stop making requests.
So I made users bring their own key. More friction at setup. But each user owns their own quota. If one person hammers the API, only they pay the cost. The system degrades individually, not collectively.
The transferable thing
Shared convenience that creates a single point of failure isn’t convenience — it’s a deferred outage. This applies beyond API keys. Shared credentials, shared infrastructure, shared anything where one entity’s usage affects everyone else’s capacity. The moment you can’t answer “what happens when this hits its limit?”, you’ve built a trap.
The setup friction you add today saves you from the support nightmare you can’t fix later.