An architecture review is where the work of this course becomes visible to someone else. Its job is not to prove the design is good. Its job is to put every assumption where a reviewer can disagree with it before production does: how many calls, how many credits, what happens when each dependency fails, where the personal data goes, and why each significant choice was made.
This lesson gives you a structure for that document, fills it in for a realistic system, and ends with the checklist a reviewer should run against any multi-API design, including yours. The worked example is a seller onboarding service for a marketplace: sellers sign up, verify their contact details and bank account, and are screened before they can receive payouts.
The shape of the document
Keep it short enough that people read it, and structured enough that missing sections are obvious. Seven sections cover what matters:
- Context. What the system does, for whom, and what is out of scope. Two paragraphs.
- Requirements and SLOs. What users are promised, as numbers.
- Dependency inventory. Every external API, with its cost, purpose, timeout and failure posture.
- Capacity estimate. Calls, credits and workers, derived from expected volume with the arithmetic shown.
- Failure-mode table. For each way each dependency can fail: detection, behaviour, user impact, recovery.
- Data handling. Which personal fields go where, how long they live, and what the audit trail records.
- Decision records. The choices that were contested or expensive to reverse, each with its alternatives and consequences.
A review then walks a checklist against those sections. The rest of this lesson takes them in order.
Context, requirements and SLOs
Context (worked example). Sellers register with an email, a phone number and a bank account (IBAN). Before activation we check that the email and phone are real and reachable, that the signup is not coming from an anonymising network, and that the IBAN is well-formed. Before any payout, the seller's legal name is screened against sanctions lists; active sellers are re-screened monthly. Out of scope: identity document verification and card payments.
Requirements. Write them as measurable promises, and write the ones about degradation as explicitly as the ones about success:
| Requirement | Target |
|---|---|
| Registration response time | p95 under 2.0 s, including all synchronous checks |
| Registration availability | 99.9% of submissions get a decision (activate, verify further, or reject) |
| Degraded registration | If a contact check is down, registration still completes and the check is retried within 1 hour |
| Payout screening | No payout to a seller without a clear screen newer than 35 days |
| Screening latency | New sellers screened within 15 minutes of registration |
| Re-screen | All active sellers re-screened within 72 hours of the monthly run starting |
Notice that "99.9% available" is defined as "gets a decision", not "every API answered". That is lesson one's posture work showing up as a requirement: a registration that completes with a deferred email check counts as served.
Dependency inventory
The inventory is the table reviewers return to most. One row per API, with the credit cost taken from the API's marketplace page and the fields sent taken from your outbound contract (lesson three):
| API | Credits | Purpose | Fields sent | Timeout | Path | Posture on failure |
|---|---|---|---|---|---|---|
emailvalidator | 20 | Reachable, not disposable | 1.5 s | Sync | Fail open, re-check queued | |
phonenumbervalidator | 10 | Valid, normalised to E.164 | number, country | 1.5 s | Sync | Fail open, re-check queued |
vpndetector | 10 | Anonymising network signal | ip | 1.5 s | Sync | Fail to extra verification |
ibanvalidator | 2 | Structural IBAN check | iban | 1.0 s | Sync | Fail open; payout blocked until valid |
sanctionscreening | 20 | Regulatory screen | legal name | 5 s | Async | Fail closed: pending |
Writing the table forces decisions that otherwise stay implicit. The IBAN check fails open at registration because a malformed IBAN cannot cause harm until a payout, and the payout path re-checks it. The sanctions screen is asynchronous because its posture is fail closed: putting it in the registration path would turn a screening outage into a registration outage, when all it needs to block is payouts.
Check what each call returns before relying on a field in the design. The phone check, for example, gives the normalised number you should store:
curl "https://api.apiverve.com/v1/phonenumbervalidator?number=%2B14155552671" \
-H "x-api-key: YOUR_API_KEY"isValid is true and formatted.e164 is the canonical form to store. location and carrier are enrichment, not requirements.
Capacity estimate
Show the arithmetic, so a reviewer can change one assumption and see what moves. The estimate needs three answers: credits per month, peak calls per minute, and workers for each queue.
Assumptions for the worked example: 300 new sellers a day, 15% of a day's registrations in the busiest hour, 12,000 active sellers re-screened monthly, and a p95 latency of 1.5 s for the screening API.
const inventory = {
emailvalidator: 20, phonenumbervalidator: 10, vpndetector: 10, ibanvalidator: 2, sanctionscreening: 20,
};
const perRegistration = ['emailvalidator', 'phonenumbervalidator', 'vpndetector', 'ibanvalidator', 'sanctionscreening'];
export function capacity({ newPerDay, peakHourShare, activeSellers, plan, p95Seconds, pacerShare = 0.9 }) {
const creditsPerRegistration = perRegistration.reduce((sum, api) => sum + inventory[api], 0);
const registrationCredits = newPerDay * 30 * creditsPerRegistration;
const rescreenCredits = activeSellers * inventory.sanctionscreening;
const monthlyCredits = registrationCredits + rescreenCredits;
const peakCallsPerMinute = (newPerDay * peakHourShare * perRegistration.length) / 60;
const pacedPerMinute = plan.perMinute * pacerShare; // leave room for live traffic
const rescreenMinutes = activeSellers / pacedPerMinute;
const rescreenWorkers = Math.ceil((pacedPerMinute / 60) * p95Seconds); // Little's law
return {
creditsPerRegistration,
monthlyCredits,
shareOfAllowance: +(monthlyCredits / plan.monthlyCredits).toFixed(2),
peakCallsPerMinute: +peakCallsPerMinute.toFixed(1),
rescreenMinutes: Math.ceil(rescreenMinutes),
rescreenWorkers,
};
}
console.log(capacity({
newPerDay: 300, peakHourShare: 0.15, activeSellers: 12_000, p95Seconds: 1.5,
plan: { perMinute: 180, monthlyCredits: 1_000_000 }, // Pro, at the time of writing
}));
// { creditsPerRegistration: 62, monthlyCredits: 798000, shareOfAllowance: 0.8,
// peakCallsPerMinute: 3.8, rescreenMinutes: 75, rescreenWorkers: 5 }Read the output the way a reviewer would. Peak registration traffic is under 4 calls a minute, so the per-minute limit is irrelevant to the synchronous path. The monthly re-screen takes about 75 minutes with 5 workers, comfortably inside its 72-hour promise. The binding constraint is credits: the design uses 80% of the allowance before growth, retries of billable calls, or anyone else on the account. That finding is the whole value of the estimate. It turns "should we be on Pro?" from a feeling into a decision record, below.
Failure modes and data handling
Failure-mode table. One row per realistic failure, not per API. The columns that matter are how you detect it, what the system does, and what a user or operator sees:
| Failure | Detection | System behaviour | User impact | Recovery |
|---|---|---|---|---|
Email check slow or 5xx | Timeout, breaker opens after 5 failures | Skip check, flag emailUnverified, queue re-check | None at signup | Re-check worker drains queue when breaker closes |
| Screening API down | Timeout or breaker open | Sellers stay pending; payouts held | Payout delayed; banner explains | Screens resume; oldest-pending alert after 15 min |
Rate-limit 429 | Status plus credits header above 0 | Pause the pool for retry-after | None; queue absorbs it | Automatic |
| Credits exhausted | 429 and x-api-remaining-credits is 0 | Stop all dispatch, page on-call | Contact checks deferred, payouts held | Upgrade or renewal; then drain |
Bad input (400) | Status | Return field error to the form; do not retry | User corrects input | None needed |
| Re-screen run overruns | Oldest-job age above 48 h | Raise re-screen priority, alert | None yet | Investigate before 72 h promise breaks |
Data handling. Reference the outbound contract rather than repeating it, then state what lesson three asked for: where each personal field is stored, the lifetime each store enforces, how deletion reaches caches and logs (pseudonymous tokens), and what the audit trail holds. For this design: projected results cached for 30 days keyed by HMAC tokens; operational logs redacted by field name and kept 14 days; sanctions audit entries append-only and hash-chained, with a retention period set by counsel; the processor list (every API above) and their published data terms linked from the document.
In the worked capacity estimate, peak registration traffic is under 4 calls per minute on a 180 per minute plan, and monthly usage is 80% of the credit allowance. What should the review conclude?
Decision records
A decision record (often called an ADR) captures one significant choice: the context, the options considered, the decision, and its consequences, including the bad ones. They are short, numbered and never edited after acceptance; a changed decision gets a new record that supersedes the old one. That history is what lets someone a year from now understand why the system looks the way it does.
# ADR-003: Plan choice and credit headroom
Status: Accepted, 2026-10-23
## Context
The capacity estimate puts monthly usage at 798,000 credits (80% of Pro's allowance)
before growth. Per-minute capacity is not a constraint (peak 3.8 calls/min of 180).
## Options
1. Stay on Pro, alert at 70% and 85% of the allowance, review monthly.
2. Move to the next plan now for headroom.
3. Reduce re-screen cost: screen only sellers with payouts in the last 90 days.
## Decision
Option 1 with option 3: the re-screen covers sellers with recent or pending payouts,
which cuts re-screen credits by an estimated 40%. Alerts at 70% and 85%.
## Consequences
- Dormant sellers are screened on their next payout instead of monthly. The payout
path already blocks without a clear screen newer than 35 days, so none are paid unscreened.
- Credit alerts become a paging condition; on-call owns the upgrade decision.
- Revisit when new registrations exceed 400 a day.Good candidates for records in a multi-API design: each fail-open versus fail-closed choice, synchronous versus asynchronous placement of each check, plan and headroom, any second provider and why it was or was not added, and retention periods.
The review checklist
A reviewer runs this against the document, and every "no" becomes either a change or a recorded decision to accept the risk.
Resilience
- Every dependency has a posture (fail open, fail closed, or degrade) chosen per feature, with the reason.
- Only availability failures (timeouts, network errors,
5xx, rate-limit429, open circuits) trigger fallbacks or count toward breakers. Answers (400, not-found404) do not. - Out-of-credits
429is detected from the credits header, stops dispatch and pages a human. - Each synchronous path has a total budget, and per-call timeouts are derived from it.
- Each dependency has a bulkhead sized to its importance.
- Degraded responses are labelled and counted.
Scale and fairness
- Volume assumptions are written down, with peak shape, not just daily totals.
- Workers are sized with Little's law at a high-percentile latency, and a shared pacer enforces the rate.
- Credits per month are estimated per API, with headroom stated as a percentage.
- Tenants or workloads cannot starve each other: rotation, concurrency caps, credit quotas, separate keys where useful.
- Every queue is bounded, and alerts fire on oldest-job age against a stated promise.
Data and compliance
- Each outbound call has an explicit field contract; responses are projected before storage.
- Logs, traces and error reports pass through redaction; outbound URLs are not logged with query strings.
- Caches are keyed by keyed hashes, not raw personal data.
- Every store has an enforced lifetime, and deletion reaches caches and logs.
- Regulatory controls fail closed and write an append-only audit trail that records what was actually checked.
- The processor list is current, and each processor's data terms have been read.
Operations
- Each failure in the table has a detection signal that exists today, not one that is planned.
- Someone owns each alert, and each decision record names its revisit trigger.
A design places the sanctions screen inside the registration request, with a 5-second timeout, and fails the whole registration if the screen errors. Which checklist item does it fail, and what is the fix?
You have finished the course
Across these four lessons you have designed failure behaviour on purpose, sized and shared capacity with arithmetic instead of hope, kept personal data to the minimum each step needs, and written it all down in a form someone else can challenge. That is the work of an architect: not knowing every answer, but making every assumption visible.
This was the last course in APV-400 Solutions Architecture. When you are ready, take the exam: start the APIVerve Certified Architect exam.