Late API: Why Most Developers Build APIs Backwards
Late API flips traditional API design: observe first, build after. Learn how Stripe and Supabase use this pattern with production code examples.
Late API: The API You Build When You Need It, Not Before
*Most developers design APIs by guessing what their clients will need.* Then they spend months maintaining dead endpoints, deprecated versions, and constant changes because users wanted something different.
Late API is the opposite.
Instead of predicting your clients' needs, you run your application, observe what data actually flows, and build the API based on that. Not guesswork. Observation.
The Late API pattern isn't new — Stripe, Twilio, and Supabase have used it for years — but almost no one documents it explicitly. Here's how it works and why it drastically reduces API maintenance.
1. The Traditional Approach: Predict First, Suffer Later
Most teams follow this flow:
❌ The PM says: "We need an API for users and orders."
❌ You design 40 endpoints "just in case".
❌ Clients use 8 of them.
❌ They need 2 more you didn't include.
❌ You spend 3 months versioning old endpoints.
This pattern exists because it's what most API design documentation teaches. REST principles, OpenAPI specs, "design for extensibility". Everything assumes you know what the client will need in 2027.
You don't.
2. Late API: Observe First, Design After
Late API inverts the order completely:
✅ Run your application with internal data.
✅ Observe what queries your clients actually make.
✅ Extract the most common patterns (80/20).
✅ Build endpoints explicitly for those patterns.
✅ Version only when 60%+ of your users request changes.
For gestoriascercademi.com, for example, the initial system was monolithic. Clients needed:
Search gestoria providers by location.
View user reviews.
Compare typical prices by service.
Instead of building a generic "gestoria resources" API, it extracted exactly those 3 use cases and built 3 endpoints. Nothing more.
At 6 months, they requested an endpoint for "gestoria providers that speak specific languages". It was added.
At 12 months, one for "price history by region". It was added.
Each endpoint was built because 70%+ of queries needed it. Not because it seemed like "good architecture".
3. How to Implement Late API in Production
Step 1: Log All Data Queries Internally
Before exposing any public API, your internal application must log:
What data is requested.
How often.
What filters are applied.
What fields are returned.
Here's a simple logging pattern in TypeScript:
```typescript
interface QueryLog {
resource: string; // 'gestoria', 'review', 'service'
filters: Record<string, any>;
fields: string[];
timestamp: number;
userId?: string;
responseTime: number;
}
const queryLogs: QueryLog[] = [];
export async function fetchGestorias(filters: any, fields: string[]) {
const start = Date.now();
// Log before executing
queryLogs.push({
resource: 'gestoria',
filters,
fields,
timestamp: Date.now(),
responseTime: 0,
});
// Real fetch
const result = await db.gestoria.findMany({
where: filters,
select: fields.reduce((acc, f) => ({ ...acc, [f]: true }), {}),
});
// Update log
queryLogs[queryLogs.length - 1].responseTime = Date.now() - start;
return result;
}
```
After 1-3 months, analyze the logs:
```typescript
const patternAnalysis = queryLogs.reduce((acc, log) => {
const key = `${log.resource}_${JSON.stringify(log.filters)}`;
acc[key] = (acc[key] || 0) + 1;
return acc;
}, {} as Record<string, number>);
// Top 10 query patterns
const topPatterns = Object.entries(patternAnalysis)
.sort(([, a], [, b]) => b - a)
.slice(0, 10)
.map(([pattern, count]) => ({ pattern, count }));
```
Step 2: Extract the 80/20 Patterns
Tylerema, a tax management SaaS using Late API, found that 87% of queries were variations on just 4 patterns:
1. "Give me all active clients in this region" (43% of queries).
2. "Give me all services with reviews >4.5" (31%).
3. "Give me typical prices for this service in this city" (8%).
4. "Give me gestoria providers that speak this language" (5%).
It built exactly those 4 endpoints. Nothing else.
Step 3: Design Specific Endpoints, Not Generic Ones
Instead of:
```
GET /api/gestorias?region=Madrid&rating=4.5&language=es
```
Build:
```
GET /api/gestorias/top-rated-by-region
?region=Madrid&minRating=4.5&language=es
```
Why? Because that specific endpoint can be pre-indexed, cached more aggressively, and the client knows exactly what it gets. No surprises.
Here's the actual endpoint:
```typescript
app.get('/api/gestorias/top-rated-by-region', async (req, res) => {
const { region, minRating = 4.0, language } = req.query;
// Aggressive caching for this specific endpoint
res.set('Cache-Control', 'public, max-age=3600');
const gestorias = await db.gestoria.findMany({
where: {
region: region as string,
averageRating: { gte: minRating as number },
languages: language ? { has: language as string } : undefined,
},
select: {
id: true,
name: true,
region: true,
averageRating: true,
reviewCount: true,
typicalPrices: true,
},
orderBy: { averageRating: 'desc' },
take: 20,
});
res.json(gestorias);
});
```
Step 4: Version Only When Your Top 5 Patterns Change
Most APIs version out of nervousness. "What if someone needs something else?"
Late API says: version when 60%+ of your users request a change. Not before.
```typescript
// Versioning strategy
app.get('/api/v1/gestorias/top-rated-by-region', handler); // 2025
// In 2026, if 70% of users ask for "multiple languages in an array"
app.get('/api/v2/gestorias/top-rated-by-region', handlerV2); // Now
// Keep v1 for 6 months. Deprecate explicitly.
app.use('/api/v1/*', (req, res, next) => {
res.set('Deprecation', 'true');
res.set('Sunset', new Date(Date.now() + 630246060*1000).toUTCString());
next();
});
```
4. Real-World Cases: Where Late API Works
Supabase uses Late API implicitly. It built the REST API after seeing how users queried their database. Not before.
Result: 95,000+ users, and almost zero breaking API changes in 3 years.
Stripe does the same. The `/v1/charges/create` endpoint exists because literally thousands of integrations used it. Only then was it documented as "official".
Twilio started with 1 API. Now it has 50. Each was added after 70%+ of users explicitly requested it.
5. Real Risks and How to Mitigate Them
⚠️ "What if I need to scale without redesigning the API?"
Late API doesn't mean poor design. It means data-driven design, not speculation.
Once you identify your top 5 patterns, you still design them well: database indices, caching, rate limiting.
```typescript
// Assuming your top pattern is: "give me gestorias in Madrid with rating >4.5"
// This is what Late API does RIGHT:
db.gestoria.createIndex({ region: 1, averageRating: 1 });
app.use('/api/gestorias/top-rated-by-region', cache('1 hour'));
app.use('/api/gestorias/top-rated-by-region', rateLimit(100, 'per hour'));
```
⚠️ "What if my users use the API differently than expected?"
You see it in 4 weeks, not 4 months. And you adjust. That's the point.
6. Late API vs GraphQL: When to Use Each
❌ GraphQL as a "generic solution"
Many teams use GraphQL because "clients can request the fields they need". Then they discover that maintaining a GraphQL resolver for every possible query is more work than specific REST endpoints.
✅ Late API with REST
Identify your top 5 patterns. Build specific endpoints. Done.
GraphQL makes sense if you have >20 different clients with completely different query patterns. If you have <5 main clients, Late API (REST) is 10x simpler.
For gestoriascercademi.com: 3 main clients (web, iOS, partner dashboard). Late API was obvious.
7. Metrics That Matter in Late API
Query Coverage: What percentage of actual queries does your API cover? (Target: >95%)
Endpoint Usage: What's the distribution of usage across endpoints? (Target: Top 3 covers >80%)
Deprecation Impact: How many clients are affected if you deprecate an endpoint? (Target: <10%)
Cache Hit Rate: How many queries are served from cache? (Target: >70%)
```typescript
// Track this automatically
app.use((req, res, next) => {
const start = Date.now();
res.on('finish', () => {
metrics.push({
endpoint: req.path,
method: req.method,
status: res.statusCode,
responseTime: Date.now() - start,
cacheHit: res.getHeader('X-Cache-Hit') === 'true',
timestamp: Date.now(),
});
});
next();
});
```
The Reality of Late API
It's not revolution. It's pragmatism.
Stop designing APIs for use cases that don't exist. Run. Observe. Build. Then scale.
Every endpoint you build after observing real data is an endpoint you needed. Not one you thought you might need.
Those 3 specific endpoints serve 10,000 users. The 40 generic endpoints that nobody used were noise.
Lee el artículo completo en brianmenagomez.com


