
Why API design outlives the code
Once something consumes your API, its shape is effectively frozen. You can rewrite everything behind it; you cannot casually rename a field that a mobile app in the wild depends on, because old app versions live on people's phones for years.
That asymmetry is the reason API work should be design-first. A week spent agreeing the contract before implementation saves considerably more later, because the alternative is versioning your way out of decisions made in an afternoon.
The process that works
1. Consumer inventory
Who calls this, and what do they actually need? A web front end, a mobile app, a partner integration and an internal dashboard have genuinely different requirements. Mobile clients care about payload size and round trips in a way a web client on broadband does not.
2. Contract first
Write the specification — OpenAPI for REST, a schema for GraphQL — and agree it before implementing. This produces two immediate benefits: front-end and back-end work can proceed in parallel against a mock, and disagreements surface in a document review rather than in integration testing.
3. Resource modelling
Endpoints should reflect your business domain, not your database tables. Exposing table structure directly is convenient at first and becomes a cage: every schema change becomes a breaking API change.
4. Authentication and authorisation
Decide early, because retrofitting is painful:
- API keys for server-to-server partner access
- OAuth 2.0 / OIDC where users grant access to their own data
- Short-lived JWTs with refresh tokens for first-party apps
And keep authorisation distinct from authentication. Knowing who someone is does not tell you which records they may see, and conflating the two is a common source of data-exposure bugs.
5. Errors, pagination, versioning
The unglamorous parts that determine whether the API is pleasant to build against:
- Consistent error shape with a machine-readable code and a human message
- Cursor pagination rather than offset, so results stay stable while data changes
- Versioning from day one —
/v1/costs nothing now and saves a migration later - Rate limiting with clear headers, so consumers can back off rather than being cut off silently
6. Documentation as a deliverable
Generated reference plus worked examples, including how to authenticate and what the common errors mean. If a competent developer cannot make a successful call within twenty minutes of reading it, the documentation is not finished.
REST or GraphQL
REST for the large majority of projects. HTTP caching works, debugging is straightforward, every developer already knows it, and tooling is universal.
GraphQL when you have several different clients wanting different shapes of the same data, or when over-fetching on mobile connections is a measurable cost. It brings real complexity — caching, query cost limiting, protection against deeply nested queries — that has to be actively managed.
Choosing GraphQL for one web client is usually complexity without return. Choosing REST when you have four clients each fetching a different subset means either many endpoints or a lot of wasted payload.
What it actually costs in the UK
| Scope | Typical range |
|---|---|
| Focused API, a few resources, authentication | £6,000 – £15,000 |
| Integration with a documented third-party API | £2,500 – £6,000 |
| Integration with legacy or undocumented systems | £10,000 – £30,000 |
| API gateway, rate limiting, developer portal | £8,000 – £20,000 |
The pattern worth noticing: integration cost is driven by the other system, not yours. A modern documented API is a week. An undocumented legacy system with inconsistent data and no test environment can absorb a month. Which is why we scope those as a time-boxed spike first rather than quoting blind — a fixed price on an unknown system is a guess someone eventually pays for.
Integrating legacy systems
Most real projects are not greenfield. They involve something old that nonetheless runs the business.
If it has an API, expect it to be inconsistent, slow, and to rate-limit in undocumented ways. Build defensively: retries with exponential backoff, circuit breakers, and a queue so a slow dependency does not take your application down with it.
If it has no API, the options in descending order of robustness are a scheduled file exchange (CSV or XML over SFTP), a read-only database integration where the vendor permits it, or — in constrained cases — robotic process automation driving the interface. The last is genuinely fragile and breaks whenever the vendor changes a screen. We will implement it if it is the only route, while being clear about what you are accepting.
Always add an anti-corruption layer. Translate the legacy system's model into yours at the boundary rather than letting its quirks spread through your codebase. That single decision determines whether replacing the legacy system later is a project or an ordeal.
Common mistakes
- Exposing database tables as endpoints — couples your API to your schema permanently
- No versioning — the first breaking change becomes an emergency
- Authentication bolted on later — invariably means a rewrite of every endpoint
- No rate limiting — one badly written consumer takes down the service for everyone
- Inconsistent errors — every consumer writes bespoke handling per endpoint
- Documentation written last — so it is written badly or not at all
What good looks like when delivered
- OpenAPI specification agreed before implementation began
- Automated tests covering the documented contract
- Authentication and authorisation as separate, tested concerns
- Rate limiting with informative headers
- Structured logging and error tracking
- Documentation with worked examples
- A monitored staging environment consumers can develop against
Next steps
If you are planning an integration and the system at the other end is an unknown quantity, the sensible first step is a short paid spike to establish what is actually possible. It usually costs a fraction of the project and occasionally reveals that the integration you were quoted for cannot be built the way it was described.
Frequently asked
A focused API with a handful of resources and authentication typically runs £6,000 to £15,000. Integrating with an awkward legacy or third-party system usually £10,000 to £30,000, because most of the cost sits in the other system rather than in yours. A single well-documented third-party integration can be £2,500 to £6,000.
REST for most cases: simpler to cache, simpler to debug, and every developer already understands it. GraphQL earns its complexity when many different clients need different shapes of the same data, or when mobile bandwidth makes over-fetching genuinely costly. Choosing GraphQL for a single web client usually adds complexity without return.
Four to eight weeks for a focused API including documentation and tests. Integrations depend almost entirely on the quality of the system at the other end — a well-documented modern API might take a week, an undocumented legacy system considerably longer, and that uncertainty should be scoped as a spike rather than guessed at.
There are usually options: scheduled file exchange, a database-level integration where permitted, or in constrained cases robotic process automation. All are less robust than a proper API, and we would tell you the trade-offs plainly rather than presenting a fragile approach as equivalent.
- API
- integration
- REST
- GraphQL
- architecture
