The Discovery
Documentation says an API returns{ subscriber_count: integer }. Real live API returns { subscriber_count: number, last_updated_at: ISO8601_string }. You write types based on docs. You deploy. Users see NaN for last_updated_at. You debug by reading undocumented source code. You lose a day.
Better way: Before writing a single type, call the real API (or use mocked responses from real API calls) and inspect the JSON. Let TypeScript infer the shape. Commit the JSON to git so future developers see exactly what the API returned on day 1.
The Workflow
- Call real API — Use
curlor Postman, save response to JSON file - Commit response —
git add fixtures/buttondown-emails-response.json - Infer types from response — Use
as const+ TypeScript’stypeofinference - Write unit tests — Every test uses the mocked JSON, not hypotheticals
- Document deviations — If API schema later drifts, the test failure surface is immediately obvious
Code Pattern
Step 1: Save real API responseWhy This Matters
Documentation Drift Is Real
| Date | Documentation Says | Real API Returns | Impact |
|---|---|---|---|
| 2026-01-01 | open_rate: number (0–100) | ✓ Matches | None |
| 2026-02-15 | (unchanged) | open_rate: number (0–1) (schema changed) | Code computes 100 * open_rate, gets NaN for new emails |
| 2026-03-01 | (still says 0–100) | Adds analytics_version: string | Code ignores it, works fine (but wastes API bandwidth) |
Three Benefits
- Catch schema drift at test time, not runtime — Fixture outdated? Type mismatch fails the test.
- Future developers have ground truth — “What does the API actually return?” → Look at the fixture. No ambiguity.
- Onboarding speed — New eng wants to add a Buttondown field. They look at the fixture, see all available fields, add the type. No guessing.
Real Example: Buttondown Integration
Session: CASA Downlink integration, 2026-04-01Before (Documentation-based types)
After (Empirical fixture-based types)
Testing Maturity
| Level | Approach | Risk |
|---|---|---|
| Level 1 (Hypothetical) | Write types from docs. No tests. | High. Schema drift, missing fields, wrong types in production. |
| Level 2 (Integration tests) | Write types from docs. Test against live API. | Medium. Catches drift, but expensive (live API calls in CI). |
| Level 3 (Empirical fixture) | Write types from fixture JSON. Test against fixture. Verify fixture periodically against live API. | Low. Fast tests, ground truth in git, periodic verification catches slow drift. |
Periodic Verification
Once per quarter, run a script that:- Calls live API
- Compares response to committed fixture
- Flags schema differences
- Updates fixture if needed + commits to git
Recommendation
For every third-party API integration in devarno-cloud:- Day 1: Call real API, save response to
fixtures/[service]-[endpoint].json - Day 1: Infer types from fixture using TypeScript’s
typeofinference - Day 1: Write tests using mocked fixture responses
- Monthly: Run verification script to detect schema drift
- Quarterly: Review fixture changes and update docs if needed
Verified in production: CASA Buttondown integration, 19 unit tests, all passing. 2 schema changes detected during integration (analytics endpoint location, subscriber history unavailability) — caught during fixture-based testing, fixed before code deploy.