
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 | £2,400 – £6,000 |
| Integration with a documented third-party API | £1,000 – £2,400 |
| Integration with legacy or undocumented systems | £4,000 – £12,000 |
| API gateway, rate limiting, developer portal | £3,200 – £8,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.
Questions fréquentes
Une API ciblée avec quelques ressources et une authentification coûte généralement entre **£2,400 et £6,000**. L’intégration avec un système hérité ou tiers complexe coûte généralement **£4,000 à £12,000**, car la majeure partie des coûts provient du système externe plutôt que du vôtre. Une intégration unique bien documentée avec une API tierce peut coûter entre **£1,000 et £2,400**.
REST pour la plupart des cas : plus simple à mettre en cache, plus simple à déboguer, et tout développeur en comprend déjà le fonctionnement. GraphQL justifie sa complexité lorsque plusieurs clients différents ont besoin de formes différentes des mêmes données, ou lorsque la bande passante mobile rend le surdimensionnement coûteux. Choisir GraphQL pour un seul client web ajoute généralement de la complexité sans retour sur investissement.
Quatre à huit semaines pour une API ciblée incluant documentation et tests. Les intégrations dépendent presque entièrement de la qualité du système en face — une API moderne bien documentée peut prendre une semaine, un système hérité non documenté bien plus longtemps, et cette incertitude doit être traitée comme une étude exploratoire plutôt que devinée.
Il existe généralement des solutions : échange de fichiers planifié, intégration au niveau de la base de données si autorisé, ou dans certains cas l'automatisation de processus robotisés. Toutes sont moins robustes qu'une API propre, et nous vous expliquerions clairement les compromis plutôt que de présenter une approche fragile comme équivalente.
- API
- integration
- REST
- GraphQL
- architecture
