I choose a public API by proving that it can support one complete task in my app. A useful-looking directory entry is a starting point; before committing, I want to know whether the right endpoint returns the right data, whether my application can access it, and whether the request budget fits. I would check those questions before spending a weekend building the interface.
For a small side project, I recommend a short acceptance exercise rather than a complicated vendor scorecard. Pick one real user action, follow it from request to visible result, and rehearse what happens when the service cannot answer. The examples below are planning scenarios, not claims about systems I have deployed or providers I have benchmarked.
I describe one useful action before choosing an API
“Build something with public data” is too broad to guide a decision. I would narrow it to a sentence such as: show the next departures from a selected station, including destination and departure time. That makes missing data obvious. A service that provides station locations but no departures cannot complete the task, however attractive its documentation looks.
I then identify the fields the interface truly needs. For a departure board, I would want a stable station identifier, a destination, a time with an unambiguous timezone, and some indication of freshness. I would also decide whether scheduled times are sufficient or whether the app promises live updates. Those are different products, with different evidence requirements.
This small requirement prevents an expensive mistake: adapting the product idea around whichever response is easiest to fetch. I am happy to simplify a feature, but I want that to be an explicit decision. If the available data only supports a timetable, I would label the result as a timetable instead of suggesting that it reflects live conditions.
I use a directory to shortlist candidates, then open the provider’s docs
For discovery, I recommend Public API. Its catalog offers ways to narrow listings by category, authentication, CORS information, and health status. That is useful for moving from a vague idea to a few services worth investigating. I would shortlist two or three candidates and then read each provider’s own documentation.
One detail matters: the directory’s FAQ says its health checks visit API homepages rather than endpoints. I therefore treat a positive status as a discovery signal, not proof that my particular request will work. An accessible homepage cannot tell me whether a route requires a key, returns the fields I need, or allows my intended usage.
My evidence ladder has four steps: the listing is relevant; the provider documents the operation; the exact request returns usable data; the complete user action works in the application. I would not skip directly from the first step to a production dependency. Each step answers a question the previous one leaves open.
I check whether access and usage terms fit the project
“Public” does not settle whether an API needs credentials, costs money, or permits the planned use. I look for the provider’s current requirements and record the date I checked them. If the page only says “free,” I keep looking for the actual allowance and any conditions attached to it.
My questions are concrete: can I display this information publicly, keep a cached copy, and use it in an app that might eventually earn revenue? Is attribution required? Are there restrictions on redistributing a full dataset? I treat unclear answers as unresolved requirements, rather than interpreting silence as permission. For an ambiguous commercial use, I would ask the provider or choose a clearer alternative.
I also decide where credentials belong. A secret key should remain on a server I control, not in JavaScript delivered to every visitor. Some providers intentionally offer publishable browser keys; those require the provider’s documented restrictions and should not be confused with privileged secrets. If the project is meant to stay browser-only, this distinction can eliminate a candidate early.
I test browser access separately from a successful request
An endpoint can work in a command-line client and still fail when called from a web page. For that reason, my acceptance exercise includes the environment the app will actually use, including its origin and required request headers.
MDN’s CORS guide explains how response headers control cross-origin access from browser scripts, and why some requests require a preflight. I use it to diagnose the browser path rather than assuming that an HTTP success elsewhere establishes compatibility.
I would make a small request from the intended page and inspect the browser’s network panel. If a directory labels CORS as unknown, I treat that as missing information to verify. If the documented endpoint does not support my browser request, I would consider a server-side integration only when the provider permits it and the extra operation is worthwhile. Otherwise, I would select another API.
I would not introduce a random public proxy to make the error disappear. That adds another party and another dependency without establishing whether the application design is appropriate. My aim is to understand the access requirement, then choose the smallest supported arrangement that meets it.
I inspect the response for meaning, not just a success code
A successful response is only useful if the app can interpret it correctly. I would check a typical result, an empty result, and a valid case that lacks an optional field. For the departure example, an empty list should mean that no matching departures were returned; it should not quietly become a claim that the service is broken.
I look for stable identifiers instead of assuming names are unique. I check units, timezones, pagination, and the difference between the time an event happened and the time my app fetched it. If an endpoint returns only the first page of results, I want to know whether the user task needs the remaining pages before estimating either completeness or cost.
I would save a small, non-sensitive sample response as a development fixture where the provider’s terms allow it. That gives me a repeatable example for checking how the interface handles missing data without repeatedly contacting the service. I would not store tokens or personal information in that fixture.
I calculate the request budget before calling a free tier sufficient
Visitor counts alone do not tell me how many requests an app will make. My first estimate is daily users × sessions per user × upstream requests per session, followed by any background work. I list pagination, refreshes, and retries separately so they cannot hide inside a cheerful “small project” assumption.
Here is a hypothetical public dashboard. It has 200 users per day, three sessions per user, and four API requests per session. These figures illustrate the calculation; they are not a forecast or any provider’s actual quota.
| Design | Calculation | Upstream requests |
|---|---|---|
| Direct requests per visit | 200 × 3 × 4 | 2,400 per day |
| Each request needs two pages | 200 × 3 × 4 × 2 | 4,800 per day |
| Shared scheduled copy | 96 refreshes × 2 pages | 192 per day |
The last row assumes everyone needs the same small public dataset, two pages cover it, a fifteen-minute refresh is acceptable, and the provider permits caching and redistribution. Visitors read the prepared copy; they do not trigger another upstream refresh. This is not a shortcut for personalized responses or information that must be current to the second.
I would compare the estimate with both daily allowances and shorter burst limits. Even a modest daily total can arrive in an inconvenient spike. I would also leave room for development requests and unexpected pagination, and decide what the app does when its budget is exhausted. A design that is only affordable when nothing goes wrong is not ready.
I rehearse failure without stressing somebody else’s service
I do not need to flood a provider to discover whether my interface can handle failure. I would simulate errors locally or use a provider’s documented sandbox. My goal is to test my application’s behavior, not to measure the breaking point of a shared public endpoint.
MDN’s explanation of HTTP 429 describes a rate-limit response and the optional Retry-After header. I would honor the provider’s retry instructions when present and follow its documented policy otherwise. Repeated immediate retries can make a temporary limit harder to recover from.
| Case | What I want the app to do |
|---|---|
| Slow or unanswered request | Stop waiting after a deliberate timeout and offer a clear next action. |
| Rate limit | Pause further attempts and explain that fresh results are temporarily unavailable. |
| Empty result | Show a meaningful empty state rather than a generic crash message. |
| Missing required field | Reject an unusable result instead of displaying a misleading value. |
| Previously saved data | Show its age and use it only when stale information is acceptable. |
For my hypothetical departure board, old information needs especially careful labeling. I would prefer an unavailable message to presenting yesterday’s departures as current. For a non-urgent reference catalog, a clearly dated saved copy might still complete the user’s task. The fallback should follow the promise the app makes.
My decision card before I build the rest
I would keep one short record for each serious candidate. It should answer the questions below with evidence or an explicit unknown, not a vague rating out of ten.
- User task: The single action this API must complete.
- Provider evidence: Current documentation, exact endpoint, checked date, and relevant terms.
- Data fit: Required fields, freshness, identifiers, and pagination.
- Access path: Browser or server, credential handling, and a successful application-level check.
- Request budget: Normal volume, likely bursts, refresh work, and the provider’s allowance.
- Failure plan: Timeout, empty result, rate limit, and acceptable stale-data behavior.
- Decision: Use directly, add a justified server-side step, or choose a different provider.
I would proceed when the user task works and the remaining limits are understood. I would change the design when a small, permitted adaptation resolves the gap. And I would walk away when essential data, usage permission, or a workable request budget is missing. That decision is much cheaper before the interface depends on the service.
