Designing RESTful APIs: Naming, Methods, and Error Handling
When you design an HTTP API, clients need to know which address identifies a resource, which HTTP method tells the server what…
When your service relies on another company’s software, it usually communicates through an application programming interface, or API: a defined way for one piece of software to request data or actions from another. Connecting to the API is only the first step. The provider can limit requests, change its interface, reject credentials, or become temporarily unavailable, and your service must handle those conditions without losing data or repeating actions.
Treat a third-party API as an ongoing operational dependency. Before launch, verify that the provider fits your needs; during implementation, isolate its behavior and protect credentials; after launch, monitor failures and prepare for changes. Those choices help you keep vendor-specific problems from spreading through your code and give you a way to recover when a request’s outcome is uncertain.
Confirm that the API supports the exact work your service needs. Read the documentation to learn how requests identify themselves and what they are allowed to access. Check the request limits and error responses. Check how the API splits large results across pages, labels changes to its interface, and provides test environments. A small test should check the API endpoints, the addresses your service sends requests to, and the data your service will use. It should also check at least one failure response. Documentation and examples can be incomplete or inconsistent: Postman’s 2024 survey reported that 39% of respondents identified inconsistent documentation as a major roadblock. That figure describes the survey respondents, not every API integration, but it supports checking documented behavior against real requests.
A description of the API in a format that software can read can make those checks easier. The OpenAPI Specification defines a format for describing HTTP API endpoints, inputs, responses, and authentication. Tools can use the description to create documentation or check whether requests follow the API’s rules. The description still needs comparison with the provider’s actual responses because a specification does not prove that the provider’s implementation matches it.
Also decide who will manage credentials, read provider notices, respond to incidents, and handle upgrades. That responsibility continues after launch. In a 2024 Lunar survey of engineering decision-makers at companies with 100 to 1,000 employees, 88% said third-party API issues required attention every week. The result reflects that particular survey sample, but it reinforces that integration work continues beyond the first successful request.
When it fits the codebase, put the provider’s request formats and response handling behind an internal interface. For example, a billing service should call an internal createPayment operation rather than constructing a provider’s URL and parsing its response in several unrelated code paths. The adapter turns that internal operation into a request the provider understands. It then maps the provider’s response or error to a form the rest of the service can use.
This boundary gives developers one place to update how fields map between systems, how errors are understood, and how API-version changes are handled. It also makes tests less dependent on the provider’s exact response format. An adapter adds code that someone must maintain. It makes the most sense when several parts of the codebase use the provider or when developers need to keep the provider’s behavior separate from the rest of the code.
Check every response from the provider as input from outside your service. Validate required fields and types before storing data or taking action, and decide how the service handles missing or unexpected values. For example, if a provider returns an unknown status, the client should avoid silently treating it as a successful payment or completed task.
Authentication verifies who or what is making a request. Authorization controls what that verified identity can access. A valid token does not automatically grant permission to every customer record or field. When a request includes an account or customer identifier, the service should check that the authenticated user or integration is allowed to access that specific object. OWASP identifies broken object-level authorization as a major API security risk.
OAuth 2.0 is a widely used way to give software limited access to a user’s or service’s data. When using it, request only the permissions the integration needs. The OAuth 2.0 security best practices recommend limiting token privileges to the relevant use case. Store credentials in a protected secret store, and keep them out of source code and logs. Define how an operator will rotate or revoke them. Check the provider’s supported authorization flow before choosing how to implement OAuth, because provider requirements vary.
Set a time limit for each request. If the provider stalls, the limit stops your service from waiting indefinitely. Before trying again, identify what kind of failure occurred. A timeout or temporary service error may clear on another attempt; an invalid request or permission denial generally requires a code or configuration change. Retrying every error wastes capacity and can add pressure to a provider that is already struggling.
A retry is another attempt to send a request after it fails. For failures that appear temporary, retry only a limited number of times and wait longer between attempts. This pattern is called exponential backoff. Set a maximum number of attempts and an overall deadline so the client stops rather than retrying indefinitely. The AWS retry guidance explains why retries need limits: repeated requests can increase load and contention during an outage.
Before retrying an action that changes data, consider whether repeating it could create another side effect. HTTP defines an operation as idempotent when repeating the same request has the same intended effect as making it once. A request that reads data is generally safe to repeat; a request that creates a payment or order may not be. HTTP semantics identify methods such as PUT and DELETE as idempotent, while POST is not inherently idempotent.
After a timeout, the client cannot tell whether the provider completed the action before the connection failed. For a state-changing request, retry only when the operation is safe to repeat or the provider offers a deduplication mechanism, such as an idempotency key. Confirm how that provider handles the key, how long it remembers it, and what happens when a repeated request has different data. Those rules vary by provider and endpoint.
A rate limit sets how many requests a client can make during a set period or under another rule the provider defines. Use the provider’s documentation and response headers to decide how the client should respond. APIs use different status codes, quota periods, and signals for when limits reset.
When a provider signals throttling, pause or slow requests according to its instructions instead of retrying immediately. GitHub’s REST API guidance, for example, tells clients to use the provider’s rate-limit headers and honor retry-after or reset information when supplied. Those details apply to GitHub, so check the corresponding instructions for your own provider. If your service often checks for changes, see whether the provider offers webhooks. Webhooks send notifications when events occur and can reduce unnecessary requests. Webhooks have their own delivery and duplicate-handling requirements, which you should verify in the provider’s documentation.
Test how the service responds to failures as well as successful responses. Exercise expired credentials, permission errors, malformed or partial data, timeouts, rate limits, and provider outages. For state-changing requests, test what happens when the client times out after sending the request and then tries to recover. Mock responses to test local handling, then use the provider’s test environment where available to check real request and response behavior.
After release, record request latency and failure rates. Track retry counts and rate-limit responses too. Use request identifiers or provider correlation identifiers to investigate a specific failure, but do not log access tokens or sensitive payloads. Set alerts for symptoms that affect your service. Examples include a rise in failed requests or a growing queue of work waiting for the provider. Choose thresholds based on your service’s requirements and observed baseline rather than assuming one threshold suits every API.
For important data, compare your service’s records with the provider’s and correct any differences. This comparison helps when a timeout leaves the result of an action unclear or a notification arrives late. The right method depends on what the API exposes, such as searchable records or event history; verify those capabilities before relying on reconciliation.
Keep a record of the API version and client library your integration uses. Providers define versioning differently: Azure DevOps documents its own REST API version model, while Stripe documents version behavior for its API and related integrations. Treat each provider’s version and deprecation policy as specific to that provider.
When the provider announces a change, test the affected requests and check how your code maps the responses before deploying an upgrade. Keep the current version in place while validating the change where the provider allows it, and assign someone to review provider notices. A stable internal interface can limit the amount of your code that needs adjustment, but it does not remove the need to check whether the provider’s behavior has changed.
Give Vroni a GitHub issue, bug report, spec, or rough idea. It reads the repo, plans the change, writes code, runs checks, and works toward a review-ready pull request.
Take a look at vroni.com