Day 8: Designing the API contract first
Day 8 of 30. Week 2 begins. Today we define the interface before building the implementation.
Day 8 of 30. Week 2 begins. Today we define the interface before building the implementation.
One of the most challenging things to change when developing an application like ours is our API contract. Any change to the API might affect one or more clients, so once developers integrate with your API, changing the API is painful. Our API requires a bit of thinking.
So, let’s try to design something we won’t regret too much.
API design principles we’re following
There are different technologies and approaches for designing remote APIs. One of the most common approaches is a REST API over HTTP, but even with REST, there are different maturity levels. While we value a REST approach, our focus is more on making an API which makes integration as easy as possible.
We use the following criteria for our API:
1. Be predictable. Follow REST conventions as much as we can. We use standard HTTP methods and status codes, and we don’t want to surprise developers.
2. Be consistent. If one endpoint returns created_at, all endpoints have to return created_at with the same naming, same formats, everywhere.
3. Be explicit. We shouldn’t introduce magic. If a parameter affects behavior, we will require it explicitly rather than inferring from a context.
4. Be versioned. While maintaining multiple versions of our API is not our goal, we will start with /api/v1/. While we hope to never need v2, and we don’t really want to cater for all “what if” scenarios, the API is quite important, and having the option to add a v2 in the future could prove useful.
5. Return useful errors. One of the most frustrating things is getting errors without a clear explanation on how to resolve the error. We had our fair share in that, so we don’t just want to say “Bad Request.” Instead, we’ll say what’s wrong and how to fix it, with a link to our documentation portal where needed.
The actual API
We’ll spare you the full spec, but here’s the gist. Try it out below:
{"url": "example.com", "device": "desktop", "format": "png", "fullPage": false} // Click "Send Request" to see response The four endpoints:
POST /api/v1/screenshots- submit a URL, get back a job IDGET /api/v1/screenshots/{id}- check if it’s done, get the image URLGET /api/v1/screenshots- list your recent screenshotsGET /api/v1/usage- see how many screenshots you’ve used this month
Four endpoints. That’s it. We can always add more, but it will be harder to take them away in the future.
Implementation notes
A few decisions that affect how we’ll build this:
IDs are prefixed. scr_ for screenshots makes debugging easier. You immediately know what kind of ID you’re looking at.
Timestamps are ISO 8601 UTC. All our timestamps are in ISO 8601 (RFC 3339) to prevent ambiguity.
Pagination uses offset, not cursors. Using pagination is simpler to implement, and cursor-based pagination is beyond our use case at this scale.
Image URLs are signed and temporary. Image URLs expire after 24 hours by default. For usecases where more than 24 hours is required (or shorter), we will allow a configuration option.
What we built today
- Wrote the full API specification
- Created request/response DTOs in Kotlin
- Set up validation annotations
- Stubbed out the controller endpoints (returning mock data)
- Tested with curl to verify the contract
Besides our prototype, we don’t have actual screenshot integration yet, that comes tomorrow. But the interface is forming. When we wire up the real implementation, we have some guidelines on how to implement the API.
Tomorrow: building the screenshot queue
Tomorrow, on day 9, we will build the real thing. Job queue in Postgres, worker processing, status updates. The contract we defined today becomes a real implementation.
Book of the day
REST API Design Rulebook by Mark Massé
Short, practical, opinionated. This book gives you concrete rules for designing REST APIs that don’t suck.
Some highlights: use nouns not verbs in URLs, use HTTP methods correctly, be consistent with naming conventions, design for extensibility without breaking changes.
We don’t agree with everything (the book is a bit dogmatic about HATEOAS), but as a quick reference for “what’s the right way to do X in a REST API,” it’s invaluable.
If you’re designing an API and want to avoid common mistakes, read this first. It’s only 100 pages.