SKILL DETAIL
public-api-design-review
samber/developer-platform-skills/public-api-design-review
Audit an existing or proposed public REST API surface as a checklist-driven design review - resource and URI naming, HTTP method and status-code correctness, field-naming consistency, pagination pattern choice, filtering/sorting/field-selection conventions, one consistent error envelope, and Hyrum's-Law backward-compatibility risk - every finding bucketed Must-change or Improvement against a cited rule, plus the standing review program (audience, lifecycle triggers, reviewer authority, linting, federation). Use whenever the user mentions an API design review, REST API consistency, endpoint naming, pagination style, or API design governance - even if they never say "design review". Do NOT use for GraphQL schemas - use samber/developer-platform-skills@public-graphql-api-design instead.
Installation
npx skills add https://github.com/samber/developer-platform-skills --skill public-api-design-review
スキルファイル
SKILL.md
最終同期 · 2026/10/23
evals/evals.json›
{
"skill_name": "public-api-design-review",
"evals": [
{
"id": 1,
"prompt": "We're Harborlane, shipment-tracking SaaS. Our /v2 API has been live with 6 design-partner customers for about 9 months and we open it to anyone in 5 weeks. Roughly what it looks like:\n\n- GET /shipments returns created_at, updated_at, carrier_id\n- GET /carriers returns createdAt, updatedAt, shipmentCount\n- POST /shipments/{id}/cancel-shipment, and also POST /carriers/{id}/verify-carrier\n- DELETE /webhooks/{id} returns 200 with {\"ok\": false, \"message\": \"webhook attached to active subscription\"} when it can't delete\n- GET /shipments/{id}/events/{eventId}/attachments/{attachmentId}/thumbnails\n\nWe already know clients parse createdAt off /carriers because two of them emailed us about its timezone format. We've never adopted a style guide of any kind. Go through this and tell us what we have to fix before we open it up.",
"expected_output": "A scoped review report: public x beta-to-GA posture stated as blocking, findings bucketed Must-change vs Improvement with a cited rule each, the casing split flagged as a consistency Must-change without picking a winning casing, and its fix routed through deprecation machinery because clients already observe the field.",
"files": [],
"expectations": [
"Sorts findings into exactly two buckets, named Must-change and Improvement",
"Assigns no numeric score, letter grade, percentage, or Critical/High/Medium/Low severity scale to the surface or to any finding",
"States that the surface being publicly consumable and about to open makes this a blocking review rather than advisory feedback",
"Flags the created_at versus createdAt split across /shipments and /carriers as a Must-change",
"Declines to declare either snake_case or camelCase the correct convention, citing consistency of the choice as the rule instead",
"States that renaming createdAt is itself a breaking change because clients already observe that field",
"Routes the casing fix through deprecation or versioning machinery rather than prescribing a silent in-place rename",
"Names samber/developer-platform-skills@api-versioning-policy as the owner of scheduling that removal",
"Flags DELETE /webhooks/{id} returning 200 with an error body and prescribes 409 with the standard error envelope instead",
"Flags cancel-shipment and verify-carrier as verbs in the URI and proposes one custom-method convention applied across every action endpoint",
"Flags the /shipments/{id}/events/{eventId}/attachments/{attachmentId}/thumbnails path as exceeding a 2-3 level nesting limit",
"States that because the team has adopted no rule corpus, findings cite this skill's own checklist items by name",
"Attaches a cited rule or precedent to every finding placed in the Must-change bucket",
"Presents findings in a table carrying finding, dimension, bucket, cited rule, and proposed fix"
]
},
{
"id": 2,
"prompt": "I'm the new head of platform at Verrill Systems. Two years ago we had 3 backend teams; we now have 31 and about 19 of them ship something external-facing. Every team invented its own pagination and its own error body. I want to stand up a proper API design review board: a monthly meeting where teams bring designs, staffed by 5 senior architects, growing to 10 as load increases. I have real budget and roughly two quarters to get it running. What should the process look like?",
"expected_output": "A program design that replaces the monthly calendar cadence with audience x lifecycle triggers, refuses the committee-growth plan, promotes the certified federated reviewer pool for this scale while naming the answers that promoted it, and states the ladder ordering per axis out loud.",
"files": [],
"expectations": [
"Rejects the proposed monthly meeting cadence and triggers review on audience and lifecycle stage instead of a calendar",
"States review is required at beta and at GA-if-changed, recommended at alpha, and skipped for internal-only surfaces",
"Rejects growing the architect group from 5 to 10 and states that growing a central committee is the one scaling move no studied company chose",
"Recommends the certified federated reviewer pool rung for this organization",
"Names the two-quarter runway, the budget, and the 31-team scale as the answers that promoted that rung above the default",
"States the maturity-ladder ordering explicitly in the text rather than leaving it implied by the order of a list",
"Gives separate orderings for efficiency and for effort or value, noting the federated pool loses every efficiency round while winning on value",
"Requires a shared, versioned rule corpus to exist before the reviewer pool grows",
"States that growing reviewers without a versioned corpus degrades turnaround time and guidance consistency together",
"Attributes the centralized-friction-point concession to Kenneth Auchenberg personally rather than to Stripe as a company",
"Requires publishing the escalation path and a named override authority up front",
"Places a spec linter first for mechanical checks and keeps semantic judgment with human reviewers",
"Gives turnaround guidance of under a week for small APIs, with a median past roughly 2 weeks signalling the program is too heavy",
"Recommends forking an existing public rule corpus rather than authoring one from a blank page"
]
},
{
"id": 3,
"prompt": "Tessellate Health, 11 engineers across 2 backend teams, one public API for patient scheduling, about 40 endpoints. Our CTO read about Google's API readability certification and wants us to build the same thing: a written style guide, a certification process for reviewers, and embedded API champions per domain. I have maybe 3 days of my own time before the next sprint starts. Rank the options and tell me what to actually do.",
"expected_output": "A recommendation that steps down to a linter-only CI gate, removes the federated/certified rung from the menu outright rather than ranking it last, and supplies the concrete re-promotion trigger that would bring it back.",
"files": [],
"expectations": [
"Removes the certified federated reviewer pool from the option menu entirely rather than ranking it last or parking it as a later phase",
"Justifies removal over demotion by noting a parked rung reappears as roadmap work that consumes corpus-writing time nobody needs yet",
"Gives an explicit re-promotion trigger: a third team shipping its own API surface, or median review turnaround past roughly 2 weeks",
"Recommends a linter-only CI gate rather than the default linter-plus-two-named-reviewers rung",
"Names the two-team scope and the 3-day effort ceiling as the answers that moved the recommendation down from the default rung",
"States that the semantic conversation still happens inside ordinary code review at this scale",
"Recommends forking a published corpus such as Zalando, Adidas, or Microsoft rather than writing a style guide from scratch",
"States the corpus must be adapted to the team's own articulated intent before it is enforced",
"States the ranking of the options explicitly rather than implying it through the order of a list",
"Declines to propose reviewer certification or per-domain embedded champions for an 11-engineer, two-team organization",
"Keeps the Must-change versus Improvement two-bucket model rather than introducing a numeric severity score",
"Characterizes the corpus-plus-linter setup as roughly an afternoon of work rather than a multi-week program"
]
},
{
"id": 4,
"prompt": "Quick one. At Northmark we have an internal service API, /billing-internal, consumed only by our own web app and one nightly batch job. Both live in our monorepo, both deployed by us. It's at alpha and we intend to keep it internal permanently. Our new architecture lead wants it to clear the same blocking design review as our public partner API before it can deploy. The team says that's overkill and we're deadlocked. Settle it: what review, if any, does this need?",
"expected_output": "A ruling that formal blocking review is not required for an internal-only single-consumer alpha, derived explicitly from the exposure and lifecycle axes, with advisory feedback offered and the condition that would make review mandatory later.",
"files": [],
"expectations": [
"Concludes that formal blocking review is not required for an internal-only surface with a single known consumer set",
"Names both axes that produce the conclusion: surface exposure (internal / partner / public) and lifecycle stage (alpha / beta / GA)",
"States that review is recommended but advisory at alpha, so an internal alpha gets advisory feedback rather than a blocking checklist",
"States that review becomes required at the beta gate if the surface ever becomes externally consumable",
"Attributes the audience-by-release-level keying to Google's AIP-100",
"Rejects the position that an internal API needs no contract, on the grounds that internal consumers are still consumers",
"Suggests tagging audience in the spec itself so the review trigger becomes mechanical, citing the x-api-audience convention",
"Notes that because the consumers are enumerable and deployed by the same team, defects can be fixed in place rather than routed through deprecation machinery",
"Declines to endorse the architecture lead's blocking gate as a compromise position",
"Keeps any findings it does raise in the Improvement bucket absent a cited rule",
"Assigns no numeric maturity score or readiness percentage to the internal API",
"Asks for the spec, reference docs, or sample requests and responses rather than reviewing from the endpoint name alone"
]
},
{
"id": 5,
"prompt": "Coastline Retail here, reviewing our public /v1 before we publish it. Pagination situation:\n\n- GET /products uses ?page=3&per_page=50\n- GET /orders uses ?cursor=eyJpZCI6...&limit=50\n- GET /stores returns all 1,400 stores in one array, no params at all\n- Ask /products for per_page=5000 and we quietly hand back 100\n- GET /orders?statuss=shipped (note the typo) returns the full unfiltered list\n- Every list response carries a `total` computed with a COUNT; orders is 240M rows and that is our slowest query by a wide margin\n- /orders cursors are just base64'd row IDs, and ?sort=-created_at and ?sort=total_amount both happily accept the same cursor\n\nProduct insists on keeping offset on /products because the web admin has numbered pages and jump-to-page. What's the verdict?",
"expected_output": "A pagination and filtering review that flags the unbounded collection, the silent clamp, and the silently ignored filter as cited-rule findings, treats the retained offset as a recorded accepted deviation rather than an open failure, and requires the sort to be bound into the cursor.",
"files": [],
"expectations": [
"Flags the unpaginated GET /stores as a Must-change against the rule that every collection endpoint is paginated",
"Flags the silent clamp of per_page=5000 down to 100 and requires a 400 response instead of a silently clamped value",
"Flags the silently ignored `statuss` typo and requires 400 or 422 for a filter on an unknown field",
"Explains that silently ignoring unknown filters makes a client typo indistinguishable from a genuinely empty result set",
"Flags the mixed offset-and-cursor pagination as a cross-surface consistency finding rather than praising it as client flexibility",
"Handles the retained offset pagination on /products as an accepted deviation with a recorded rationale, not as an unresolved failure or a blanket rejection",
"States that the recorded deviation becomes binding precedent for future admin-facing collections rather than a silent inconsistency",
"Flags that a cursor minted under one sort must not be replayable under a different sort, and requires the sort to be encoded into the cursor",
"Treats the `total` COUNT as a deliberate cost decision and proposes making it opt-in, for example via ?include_total=true",
"Requires a has_more flag or equivalent so clients never infer completion from a short page",
"Requires the response shape for empty results, last page, and out-of-range page requests to be specified rather than left undefined",
"States that filters are applied before the page is computed",
"Buckets every finding as Must-change or Improvement with a cited rule, and uses no numeric severity scale"
]
},
{
"id": 6,
"prompt": "Awkward situation at Quilsworth. A staff engineer left this on a teammate's API design PR and it has been stuck for 8 days:\n\n\"BLOCKING: snake_case is dated, switch the whole thing to camelCase. Also nest /refunds under /payments, reads better. And we settled the pagination question differently last year, so going back to cursors here.\"\n\nThe teammate's design is internally consistent snake_case throughout, /refunds is a top-level resource with a payment_id filter, and the pagination approach was explicitly signed off in a review 14 months ago. Two people have now escalated this to me. How do I unstick it, and how do I stop it happening again?",
"expected_output": "A ruling that an uncited block is not a valid block, downgrading each of the three objections, plus the standing mechanisms (deviation rationale convention, published escalation path and override authority) that prevent recurrence.",
"files": [],
"expectations": [
"States that a blocking objection carrying no cited written rule or precedent is not a valid block",
"Downgrades the camelCase demand to an Improvement at most, because the only citable rule is consistency of the choice",
"Confirms the teammate's internally consistent snake_case already satisfies that consistency rule",
"Rejects the /refunds nesting demand as a block, noting the existing structure sits within the 2-3 level depth limit",
"Treats reopening the pagination decision as re-litigation of a settled prior review and declines it absent a significant, still-consequential mistake",
"States that the burden of justification falls on the party deviating from a cited rule, not on the designer defending against an uncited objection",
"Names the aip.dev/not-precedent rationale convention as the mechanism for recording an accepted deviation",
"Recommends publishing the escalation path and a named override authority so disagreements resolve in days",
"Identifies taste-based blocking comments as a named failure mode that recreates both rubber-stamping and bottlenecking",
"Gives the calibration line: firm on decisions affecting clients or security, forgiving on stylistic preference",
"Proposes no numeric score, rubric, or severity rating as the means of settling the dispute",
"Notes the 8-day stall against the under-a-week turnaround target for a small API"
]
},
{
"id": 7,
"prompt": "Brightfold, developer platform team. We're about to run our first formal design review. In scope: our REST /v1 (28 endpoints, OpenAPI 3.1 checked into the repo), a new GraphQL gateway a squad has been building for 6 weeks, and our internal gRPC services that partners will eventually reach through a transcoding gateway. I want one consolidated review covering all three so we get consistency across the board. Where do we start?",
"expected_output": "A scoping step that confirms the paradigm, keeps only the REST surface in this review, hands the other two to the named sibling skills, runs the linter before human judgment, and asks for real production responses rather than trusting the spec.",
"files": [],
"expectations": [
"Scopes the review to the REST surface only and declines to apply this checklist to the GraphQL and gRPC surfaces",
"Hands the GraphQL gateway to samber/developer-platform-skills@public-graphql-api-design",
"Hands the gRPC surface to samber/developer-platform-skills@public-grpc-api-design",
"Explains the handoff as a conventions mismatch that would make this checklist actively mislead, not as a convenience or a scope cut",
"Points at samber/developer-platform-skills@api-integration-surface-strategy for whether a surface belongs on the roadmap at all before it is reviewed",
"Runs a spec linter such as Spectral or Zally against the OpenAPI 3.1 file before any human-judgment check",
"States that linting owns the mechanical findings and human reviewers own the semantic ones, and does not invert that split",
"Cites the limitation that a linter cannot judge whether an operation makes sense to a consumer",
"Asks for 5-10 real production responses across endpoints rather than reviewing from the spec alone",
"Explains that a spec can claim a consistency the production traffic does not actually have",
"Inventories the endpoints and tags each as collection, mutating, or both",
"Asks its clarifying questions in one batch rather than one question per message"
]
},
{
"id": 8,
"prompt": "Ardenway Labs. Public REST API, about 40 endpoints, documented in a Notion page a PM keeps updated by hand. No OpenAPI, no schema files, and honestly we'd rather write the spec after launch when things settle. Error bodies today: /v1/accounts throws {\"error\": \"...\"}, /v1/transfers returns {\"errors\": [{\"code\": 4012, \"detail\": \"...\"}]}, and a couple of endpoints return null with a 200 when the resource doesn't exist. We're ISO 27001 audited so we care a lot about not leaking internals. Give us the review.",
"expected_output": "A full checklist walk that logs the missing spec as a finding rather than refusing to proceed, treats mixed error envelopes as the top review-level error finding, stays at envelope-shape depth, and keeps the five sibling checks to one line each.",
"files": [],
"expectations": [
"Logs the absence of a machine-readable spec as an Improvement finding in its own right",
"Proceeds with the checklist walk rather than refusing to review without a spec",
"Flags the three different error shapes as the top review-level error finding, citing the rule against mixing patterns",
"Explains that mixed error patterns leave the consumer unable to predict behavior across endpoints",
"Flags returning null with a 200 for a missing resource and requires 404 with the surface's standard envelope",
"Names RFC 9457 rather than RFC 7807 when referring to the problem-details envelope",
"States that RFC 9457 obsoletes RFC 7807",
"Keeps the error review at envelope-shape level and defers code taxonomy, message wording, and retry signaling to samber/developer-platform-skills@api-error-design",
"Does not design an error-code catalog, subcode scheme, or retryability flag as part of this review",
"Checks that no 500 body leaks a stack trace, database error text, internal path, or config, and labels that a security finding rather than only a developer-experience one",
"Covers all five sibling-boundary checks - versioning, idempotency, auth model, rate-limit signaling, documentation - at roughly one line each",
"Rejects writing the spec after launch with the point that the schema is the documentation and is written first",
"Walks all eight checklist dimensions rather than only the dimensions the prompt raised"
]
},
{
"id": 9,
"prompt": "Pellworth. GA launch of our public API is Thursday, 6 days out, press embargo already set. Engineering finished the implementation 3 weeks ago and we only just got the design review back. It lists 9 items. Four cite our internal style guide: two casing inconsistencies, one verb-in-URI, and one endpoint returning 200 with an error body. The other five are the reviewer's own suggestions with no rule behind them, things like \"consider adding a next link\" and \"I'd expose expand parameters\". The VP of Eng wants to know whether we can ship Thursday with all 9 open and clean them up in the first maintenance release.",
"expected_output": "A ship-gate ruling that splits the 9 by whether a rule is cited, holds the four cited items to zero-open-at-GA while letting the five uncited ones ship, offers a documented deviation as the alternative to fixing before the date, and names the late-review failure mode.",
"files": [],
"expectations": [
"Splits the 9 items into 4 Must-change and 5 Improvement strictly on whether a written rule is cited",
"States the ship gate as zero Must-change findings open at GA, with Improvement findings permitted to ship open",
"Allows the five uncited reviewer suggestions to ship open without a fix",
"Offers recording a documented deviation as a legitimate alternative to fixing a Must-change before the date",
"Names expediency - a deadline that cannot accommodate the better design - as one of the enumerated deviation reasons available",
"Requires any such deviation to carry a written rationale that leaves a trace, rather than a verbal or informal exception",
"Refuses to let the 200-with-error endpoint ship unresolved without either a fix or a recorded deviation",
"Identifies the review arriving three weeks after implementation was finished as a named failure mode of review programs",
"Attributes the late-review pattern to Google's documented pre-reform state, where teams were forced to start over",
"Prescribes triggering future reviews at design time against the spec, before the code exists",
"Assigns no numeric readiness score or percentage to the launch decision",
"Checks that the review walked every checklist dimension, treating a skipped dimension as an incomplete review rather than a lean one"
]
}
],
"trigger_queries": [
{
"query": "Can you do an API design review on our OpenAPI spec before we go public?",
"should_trigger": true
},
{
"query": "Our /v1 endpoints are all over the place - pagination works differently per resource and the error bodies don't match. What's must-fix versus nice-to-have?",
"should_trigger": true
},
{
"query": "review our REST API design",
"should_trigger": true
},
{
"query": "We're going from 3 teams to 30. Design an API design-review process that won't become a bottleneck.",
"should_trigger": true
},
{
"query": "Is /getUserOrders an acceptable endpoint name for a public API?",
"should_trigger": true
},
{
"query": "half our endpoints return created_at and half return createdAt, does that actually matter",
"should_trigger": true
},
{
"query": "Should we use offset or cursor pagination on our public API, and is it a problem that we already use both?",
"should_trigger": true
},
{
"query": "We're opening our API up to partners next quarter. What should we check before we do?",
"should_trigger": true
},
{
"query": "Who should sign off on a new public endpoint before it ships?",
"should_trigger": true
},
{
"query": "audit our endpoint naming conventions",
"should_trigger": true
},
{
"query": "One of our reviewers keeps blocking pull requests over personal style preferences. How do I fix that?",
"should_trigger": true
},
{
"query": "our API doesn't feel like it was built by one team and I can't articulate why",
"should_trigger": true
},
{
"query": "api design governance",
"should_trigger": true
},
{
"query": "What's the right process for reviewing API designs across multiple squads?",
"should_trigger": true
},
{
"query": "Some of our endpoints return 200 with an error object in the body. Is that a real problem?",
"should_trigger": true
},
{
"query": "Before we GA the API, what has to be clean and what can wait?",
"should_trigger": true
},
{
"query": "Can you look at these 12 endpoints and tell me what an external developer would find weird?",
"should_trigger": true
},
{
"query": "We want an API style guide and some way to enforce it. Where do we start?",
"should_trigger": true
},
{
"query": "Should we run Spectral in CI or have humans review every endpoint?",
"should_trigger": true
},
{
"query": "how deep is too deep for nested URLs",
"should_trigger": true
},
{
"query": "our filters silently ignore query params they don't recognise, is that fine",
"should_trigger": true
},
{
"query": "Give me a checklist for reviewing a public REST surface.",
"should_trigger": true
},
{
"query": "we never formalised who approves API changes and it's getting messy",
"should_trigger": true
},
{
"query": "We use PUT for every update endpoint including partial ones. Thoughts?",
"should_trigger": true
},
{
"query": "Do we need a review board for our API or is a linter enough?",
"should_trigger": true
},
{
"query": "I need to explain to my CTO why the API needs cleanup before launch",
"should_trigger": true
},
{
"query": "Which of these review findings actually block the launch?",
"should_trigger": true
},
{
"query": "api consistency audit",
"should_trigger": true
},
{
"query": "We forked Zalando's guidelines. How do we actually enforce them?",
"should_trigger": true
},
{
"query": "our internal API is about to go external - what changes about how we review it?",
"should_trigger": true
},
{
"query": "Two engineers are deadlocked over camelCase versus snake_case in our API. Settle it.",
"should_trigger": true
},
{
"query": "review this spec for anything a third-party developer would trip over",
"should_trigger": true
},
{
"query": "How long should an API review take before it counts as a bottleneck?",
"should_trigger": true
},
{
"query": "We have 40 public endpoints and no OpenAPI file. Does that block a review?",
"should_trigger": true
},
{
"query": "Our DELETE returns 200 with a success flag in the body. Is that right?",
"should_trigger": true
},
{
"query": "what should the report coming out of an API review actually contain",
"should_trigger": true
},
{
"query": "endpoint naming review",
"should_trigger": true
},
{
"query": "should reviewers be allowed to block on things that aren't written down anywhere",
"should_trigger": true
},
{
"query": "We're about to publish our API docs. Do a pass over the surface first.",
"should_trigger": true
},
{
"query": "pagination is cursors on one resource and page numbers on another - fix it or leave it?",
"should_trigger": true
},
{
"query": "Set up API design reviews for our platform team.",
"should_trigger": true
},
{
"query": "our API grew organically over 4 years and it shows. where do I start cleaning it up",
"should_trigger": true
},
{
"query": "How do Google, Stripe and Zalando actually run their API reviews?",
"should_trigger": true
},
{
"query": "Does an alpha API need to pass review before we ship it to 5 design partners?",
"should_trigger": true
},
{
"query": "I want a second opinion on this endpoint layout before we commit to it",
"should_trigger": true
},
{
"query": "Should our review board grow as more teams start shipping APIs?",
"should_trigger": true
},
{
"query": "What's a reasonable max page size, and what should happen if someone asks for more?",
"should_trigger": true
},
{
"query": "we keep arguing about API design in pull requests and nothing ever gets decided",
"should_trigger": true
},
{
"query": "Our /v2 is live with paying customers and I want to rename three response fields. How bad is that?",
"should_trigger": true
},
{
"query": "Rate the consistency of our public API surface for us.",
"should_trigger": true
},
{
"query": "Is it okay for one endpoint to return a different response shape depending on a query parameter?",
"should_trigger": true
},
{
"query": "Design an error code taxonomy for our API - flat catalog or code plus subcode?",
"should_trigger": false
},
{
"query": "Write actionable error messages for our 422 validation responses",
"should_trigger": false
},
{
"query": "How long a deprecation window should we give partners before we sunset v1?",
"should_trigger": false
},
{
"query": "Should we use URI path versioning or date-based account pinning?",
"should_trigger": false
},
{
"query": "Add RFC 8594 Sunset headers to our deprecated endpoints",
"should_trigger": false
},
{
"query": "Our API reference is missing Python and Go snippets - audit the documentation",
"should_trigger": false
},
{
"query": "Set up contract tests so our docs stop drifting from the spec",
"should_trigger": false
},
{
"query": "Review our GraphQL schema for nullability and Relay connection conventions",
"should_trigger": false
},
{
"query": "Should our GraphQL gateway enforce query depth and complexity limits?",
"should_trigger": false
},
{
"query": "Check our .proto files for breaking changes with buf",
"should_trigger": false
},
{
"query": "Should we expose gRPC publicly or transcode it to JSON at the edge?",
"should_trigger": false
},
{
"query": "Should we build a GraphQL layer before or after webhooks?",
"should_trigger": false
},
{
"query": "Which integration surface should we ship first - SDKs, an MCP server, or bulk export?",
"should_trigger": false
},
{
"query": "Design our rate limit tiers and the headers we return alongside a 429",
"should_trigger": false
},
{
"query": "What request quota should the free tier get per month?",
"should_trigger": false
},
{
"query": "Should we store customer API keys hashed or encrypted, and how do we scope them?",
"should_trigger": false
},
{
"query": "Rotate our customers' API keys with zero downtime",
"should_trigger": false
},
{
"query": "Design the OAuth 2 authorization server for our partner apps",
"should_trigger": false
},
{
"query": "Implement idempotency keys so client retries don't double-charge",
"should_trigger": false
},
{
"query": "What backoff and jitter should our SDK use by default on retries?",
"should_trigger": false
},
{
"query": "Design the information architecture for our developer portal",
"should_trigger": false
},
{
"query": "How do we cut time to first API call for new signups on the portal?",
"should_trigger": false
},
{
"query": "Design our webhook delivery retries and payload signature scheme",
"should_trigger": false
},
{
"query": "Which languages should we ship official SDKs for?",
"should_trigger": false
},
{
"query": "Build a sandbox test mode with deterministic fake card numbers",
"should_trigger": false
},
{
"query": "Write the incident update for our API status page",
"should_trigger": false
},
{
"query": "Design a bulk export to S3 for customers pulling 200M rows a night",
"should_trigger": false
},
{
"query": "Should we give customer analysts direct JDBC access to our warehouse?",
"should_trigger": false
},
{
"query": "Should we ship an MCP server for our product?",
"should_trigger": false
},
{
"query": "Our app got rejected during the marketplace listing review - what do we do now?",
"should_trigger": false
},
{
"query": "Write the listing copy and screenshots for our AppExchange entry",
"should_trigger": false
},
{
"query": "How should we price our marketplace app - revenue share or flat fee?",
"should_trigger": false
},
{
"query": "Build the onboarding flow for partners submitting apps to our marketplace",
"should_trigger": false
},
{
"query": "Which connectors should we build next for our ETL product?",
"should_trigger": false
},
{
"query": "How do we get partners to build connectors for us instead of building them ourselves?",
"should_trigger": false
},
{
"query": "Surface integration failures to customers so they can self-serve the fix",
"should_trigger": false
},
{
"query": "Optimize our Zapier listing so more people find and install it",
"should_trigger": false
},
{
"query": "Review this pull request for bugs and logic errors",
"should_trigger": false
},
{
"query": "Do a design review of our new dashboard UI - the spacing and hierarchy feel off",
"should_trigger": false
},
{
"query": "Run a security review of our API for BOLA and broken object-level authorization",
"should_trigger": false
},
{
"query": "Audit our Firestore security rules for over-permissive reads",
"should_trigger": false
},
{
"query": "Review this vendor contract before we sign it",
"should_trigger": false
},
{
"query": "Scaffold a FastAPI service with SQLAlchemy models and Alembic migrations",
"should_trigger": false
},
{
"query": "Set up Spring Boot REST controllers following our house standards",
"should_trigger": false
},
{
"query": "Write integration tests for our REST endpoints using WireMock",
"should_trigger": false
},
{
"query": "Generate a Postman collection from our OpenAPI file",
"should_trigger": false
},
{
"query": "Our /orders endpoint is slow - profile it and cut the p99 latency",
"should_trigger": false
},
{
"query": "Review our database schema design before we run the migration",
"should_trigger": false
},
{
"query": "Write the job description for an API platform engineer we're hiring",
"should_trigger": false
},
{
"query": "What skills do I need to move into a developer platform engineering role?",
"should_trigger": false
},
{
"query": "Review the accessibility of our documentation site against WCAG",
"should_trigger": false
}
]
}
references/review-checklist.md›
# Review checklist - full pass/fail items
Walk all eight dimensions in order. Each item is a concrete pass/fail check, not a prompt for commentary. A failed item becomes a finding; bucket it per SKILL.md step 4 (Must-change when a written rule or precedent is cited, Improvement otherwise).
## Table of Contents
- [1. Resource modeling and URI naming](#1-resource-modeling-and-uri-naming)
- [2. HTTP method semantics](#2-http-method-semantics)
- [3. Status-code correctness](#3-status-code-correctness)
- [4. Field-naming consistency](#4-field-naming-consistency)
- [5. Pagination](#5-pagination)
- [6. Filtering, sorting, field selection](#6-filtering-sorting-field-selection)
- [7. Error shape consistency](#7-error-shape-consistency)
- [8. Backward compatibility](#8-backward-compatibility)
- [Sibling-boundary checks - one line each in the report](#sibling-boundary-checks-one-line-each-in-the-report)
## 1. Resource modeling and URI naming
- [ ] Resources are nouns, never verbs - no `/getUser`, `/createUser`, or `POST /users?action=delete`.
- [ ] Collection URIs are plural: `/users`, not `/user`.
- [ ] URIs are lowercase, with hyphens for multi-word resources: `/shipping-addresses`.
- [ ] Nesting stops at 2-3 levels (`/users/{id}/orders/{orderId}`); anything deeper is a finding.
- [ ] One URI template holds across the surface: `/{version}/{resource}/{id}/{sub-resource}/{sub-id}`.
- [ ] Non-CRUD operations use a deliberate, consistent convention (a custom-method suffix or a sub-resource), not a different improvisation per endpoint.
Diagnostic, not a gate: the Richardson Maturity Model grades how RESTful the surface is.
| Level | Description |
| ----- | ------------------------------------------------------------------------------ |
| 0 | Single URI and method |
| 1 | Per-resource URIs |
| 2 | Proper HTTP verbs - where this checklist operates and where most real APIs sit |
| 3 | Hypermedia-driven navigation |
Level 2 is the pragmatic target when clients deploy in lockstep with the server. Level 3 pays off mainly for public APIs whose client update cycles nobody controls. Use the model to communicate where the surface stands, never to demand Level 3.
## 2. HTTP method semantics
| Method | Safe | Idempotent | Use |
| ------------ | ---- | ---------- | --------------------------------------------------------- |
| GET | yes | yes | retrieve |
| POST | no | no | create / non-idempotent action |
| PUT | no | yes | full replace |
| PATCH | no | no | partial update |
| DELETE | no | yes | remove (first call 204, later calls 404 - same end state) |
| HEAD/OPTIONS | yes | yes | metadata / allowed methods |
- [ ] No GET has side effects.
- [ ] No POST performs an operation that is actually idempotent (a full replace, a delete).
- [ ] No PUT accepts a partial object - PUT demands the full replacement; partial updates are PATCH. Reject the rationalization "PATCH is complicated, let's just use PUT": PUT then forces every client to send the full object every time.
- [ ] DELETE is idempotent in effect: repeating it converges on the same end state.
- [ ] Method choice is uniform for the same operation shape across resources - if one resource updates via PATCH, its siblings don't update via POST.
## 3. Status-code correctness
Expected codes:
| Status | Meaning |
| ----------------------- | ----------------------------------------- |
| 200 | GET/PATCH/PUT success |
| 201 + `Location` header | POST create |
| 202 | async accept |
| 204 | DELETE success |
| 400 | malformed request |
| 401 | not authenticated |
| 403 | authenticated but forbidden |
| 404 | not found |
| 405 | method not supported |
| 409 | conflict - duplicate or version mismatch |
| 422 | syntactically valid, semantically invalid |
| 429 | rate limited, paired with `Retry-After` |
| 500/502/503/504 | server side |
- [ ] No 200 carrying an error object in the body - the single worst status-code finding.
- [ ] 400 vs 422 split correctly: 400 for malformed requests, 422 for well-formed requests that fail semantic validation.
- [ ] 401 vs 403 split correctly: 401 for missing or invalid authentication, 403 for an authenticated caller lacking permission.
- [ ] Every POST-create returns 201 with a `Location` header, not a bare 200.
- [ ] 429 responses carry `Retry-After`.
- [ ] The same failure condition maps to the same status code on every endpoint.
Secondary items - check when present, flag inconsistency, don't demand adoption:
- `Accept`/`Content-Type` negotiation.
- `ETag` with `If-None-Match`/`If-Match` and `412 Precondition Failed` for optimistic concurrency.
- `Cache-Control` on cacheable reads.
## 4. Field-naming consistency
| Element | Convention | Example |
| --------------- | ---------------------------------------------------- | ---------------------------------- |
| Query params | camelCase or snake_case - pick one, apply everywhere | `?sortBy=createdAt&pageSize=20` |
| Response fields | the same casing as query params, everywhere | `{ createdAt, updatedAt, taskId }` |
| Boolean fields | `is`/`has`/`can` prefix | `isComplete`, `hasAttachments` |
| Enum values | UPPER_SNAKE | `"IN_PROGRESS"`, `"COMPLETED"` |
- [ ] One casing convention across every endpoint's parameters and response fields. `created_at` on one endpoint and `createdAt` on a sibling is a top-priority finding regardless of which style is "correct" - consistency of the choice is the check, not the choice itself.
- [ ] Booleans carry an `is`/`has`/`can` prefix consistently.
- [ ] Enum casing is uniform across all enums in the surface.
- [ ] The same concept has the same name everywhere - not `user_id` here and `userId` (or `owner`) there.
- [ ] No endpoint returns a different response shape depending on runtime conditions - shape inconsistency and naming inconsistency are the same defect at different granularity.
## 5. Pagination
Strategy trade-offs (the review checks the choice was deliberate and matches the data shape):
| Feature | Offset | Page | Cursor | Keyset |
| ------------------------ | --------------------- | ------------------- | ---------------- | ------------------------------- |
| Performance | poor at large offsets | poor | excellent | excellent |
| Random access | yes | yes | no | no |
| Total count | yes | yes | no | optional |
| Consistency under writes | poor | poor | excellent | excellent |
| Complexity | simple | simple | medium | medium |
| Best for | small datasets | web UI page numbers | feeds, real-time | large datasets, simple ordering |
A fifth variant, time-based pagination (`?since=...&until=...`), specializes keyset for time-series and event data.
Checks that apply regardless of strategy:
- [ ] Every collection endpoint is paginated. An unbounded list response is a standing finding - "we don't need pagination for now" fails the moment any collection exceeds ~100 items.
- [ ] A default page size is set (commonly 20-50) and a maximum is enforced (commonly 100-1000); requesting past the maximum returns 400, never a silently clamped value.
- [ ] A `has_more` flag (or equivalent) exists, so clients never infer completion from a short page.
- [ ] Navigation links (`first`/`prev`/`next`/`last`, or an RFC 5988 `Link` header) are present - or their absence is a deliberate, documented choice for cursor-only APIs.
- [ ] One pagination pattern across the whole surface - offset on one resource and cursor on another is a consistency finding.
- [ ] Filters apply before the page is computed: filter → count → paginate → return.
- [ ] Edge cases have a specified shape: empty results (`data: []`, `has_more: false`), last page (`next: null`), out-of-range request (200 with an empty array, or a documented 404 - either, but specified).
- [ ] Sorting works alongside pagination; for cursor pagination the sort is encoded into the cursor, so a cursor minted under one sort can't be replayed against another.
- [ ] Total count, when present, is a deliberate cost decision - a `COUNT` degrades on large or fast-changing datasets; ask whether it's needed or should be opt-in (`?include_total=true`).
## 6. Filtering, sorting, field selection
Conventions to check against:
- Filtering: plain query parameters per filterable field, combinable - `GET /users?status=active&role=admin`, range filters as `price_min`/`price_max` or `createdAfter`.
- Sorting: a `sort` parameter; leading `-` (or a paired `order=desc`) for descending; comma-separated fields for multi-field sort - `?sort=-created_at`, `?sort=last_name,first_name`.
- Sparse fieldsets: `?fields=id,name,email` to request only named fields.
- Search: a `q` or `search` parameter for full-text search, kept distinct from structured `field=value` filters.
Checks:
- [ ] The same filter, sort, and search parameter names and casing appear on every collection endpoint.
- [ ] Filtering composes with pagination - filters apply before the page is computed, never after.
- [ ] A filter on an unknown field, or an invalid sort field, returns 400/422 - never silently ignored, which makes typos indistinguishable from empty results.
- [ ] Full-text search and structured filters are separate parameters, not overloaded onto one.
## 7. Error shape consistency
Shape only - the code taxonomy, message writing, retry signaling, and per-code documentation inside the envelope belong to `samber/developer-platform-skills@api-error-design`; do not duplicate that depth in a review.
- [ ] One error envelope on every endpoint and every status - the literal rule is "don't mix patterns". Some endpoints throwing, others returning `null`, others returning `{ error }` means the consumer can't predict behavior; it is the top review-level error finding.
- [ ] If a standard envelope is claimed, it is RFC 9457 problem details (`application/problem+json`; RFC 9457 obsoletes RFC 7807 - name 9457) and actually used on every error.
- [ ] The status code is correct independently of the body (dimension 3's checks apply to error responses too).
- [ ] No 500 body leaks a stack trace, database error text, internal path, or config - a security finding, not just a DX one.
- [ ] Validation happens at the surface's boundaries (request handlers, third-party responses), and error responses reflect it consistently.
## 8. Backward compatibility
Hyrum's Law, named as the framework: "With a sufficient number of users of an API, all observable behaviors of your system will be depended on by somebody, regardless of what you promise in the contract."
- [ ] The team is intentional about what is observable - every exposed field, code, ordering, and message is a potential commitment.
- [ ] Responses leak no implementation details a client could silently start depending on.
- [ ] The change under review prefers addition over modification: adding optional fields is safe; changing a field's type, removing a field, or making a new field required are breaking and need the versioning machinery (`samber/developer-platform-skills@api-versioning-policy`).
- [ ] The design extends one live contract instead of forking parallel versions - the one-version rule; ask "does this need a new version at all, or can it be added compatibly?" before reaching for a version bump.
- [ ] The contract is defined spec-first - the schema is the documentation, not a description written after the fact.
- [ ] Deprecation is planned at design time, not deferred to "later".
Rationalizations to reject, verbatim pushback for the review:
| Rationalization | Reality |
| ------------------------------------------ | ------------------------------------------------------------------------------------------- |
| "We'll document the API later" | The schema is the documentation - write it first |
| "We don't need pagination for now" | Needed the moment any collection exceeds ~100 items |
| "PATCH is complicated, let's just use PUT" | PUT demands the full object every time; clients want PATCH |
| "Nobody uses that undocumented behavior" | Hyrum's Law - if it's observable, someone depends on it |
| "We can just maintain two versions" | Multiplies maintenance cost, creates diamond dependencies |
| "Internal APIs don't need contracts" | Internal consumers are still consumers |
| "It's documented" (for a footgun) | Developers don't read docs under deadline pressure - make the consistent choice the default |
## Sibling-boundary checks - one line each in the report
Check existence and uniform application only; the named sibling owns the depth.
- [ ] A versioning scheme exists and is applied consistently across the surface → `samber/developer-platform-skills@api-versioning-policy`.
- [ ] Unsafe-to-retry operations accept an idempotency key, or their retry-unsafety is documented → `samber/developer-platform-skills@api-idempotency-retry`.
- [ ] One auth model covers the surface, consistently applied → `samber/developer-platform-skills@api-auth-key-management`.
- [ ] Rate limits are signaled in responses (429 + headers) → `samber/developer-platform-skills@api-rate-limit-policy`.
- [ ] Every endpoint, parameter, and response is documented → `samber/developer-platform-skills@api-reference-quality`.
references/review-program-benchmarks.md›
# Review-program benchmarks - triggers, authority, scaling
Verified models from the five best-documented API design-review programs, for step 6 of the review workflow. Cite these companies and patterns by name; generalize cautiously, since each represents one organization's solution, not a universal standard.
## Triggers and cadence, by organization
Cadence is audience- and lifecycle-triggered in all five programs below, and in every other named API-specific program checked (Adidas API Guidelines, Atlassian's automated conformance checks, Kubernetes SIG Architecture's `api-review`-label queue, PayPal's per-pull-request review) - none of them reviews on a fixed calendar. The one documented exception is a separate Microsoft program: the .NET API Review process (BCL/runtime FXDC, and the ASP.NET Core API Review group) holds its review meeting on a fixed weekly calendar slot, published at apireview.net/schedule and streamed to the .NET Foundation YouTube channel, regardless of whether a given API is ready that week. Entry into the queue is still change-triggered (an issue must carry the `api-ready-for-review` label), but the meeting itself runs on a standing weekly slot rather than being scheduled per proposal - a genuine calendar trigger at the meeting level, layered on top of a change-triggered queue. This is a different Microsoft program from the Azure Stewardship Board below. Separately, generic enterprise/architecture review boards (TOGAF-style, not API-specific) commonly do run on monthly or quarterly calendars - a broader IT-governance layer distinct from the API-specific programs cataloged here.
- **Google** (AIP-100): review keyed on audience (internal / partners / anyone) × release level (alpha/beta/GA).
- Required at beta and at GA-if-changed.
- Recommended at alpha.
- Not required for internal or single-customer APIs.
- Alpha may launch without approval if usage is limited to a known user set.
- Beta launches block on unresolved issues.
- Turnaround explicitly scoped: incremental changes take days, a small new API about a week, a large surface a month or more.
- **Microsoft Azure**: teams engage the Stewardship Board "early in the development process".
- MUST and SHOULD-NOT deviations must be disclosed during review.
- **Stripe**: every API-modifying change goes through review.
- External-facing changes additionally get a lightweight mailing-list review: Stripe's own engineering blog ("APIs as infrastructure: future-proofing Stripe with versioning") confirms this directly - "outgoing changes are funneled through a lightweight API review process where they're written up in a brief supporting document and submitted to a mailing list." The review-board composition, "gavel blocks", and ~20-page design-doc detail below remain sourced to Kenneth Auchenberg's account, not this Stripe primary source.
- **Zalando**: review triggers for every API tagged `x-api-audience != component-internal` - audience-tagging built into the spec itself.
## Authority and composition, by organization
| Org | Review body | Authority | Distinctive move |
| --------------- | ---------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------- |
| Google | Distributed pool of certified "API readability" holders + AIP editors | Blocking at beta/GA; bypass requires an explicit director-or-VP decision (AIP-1) | Reviewer certification, modeled on Google's code-readability program, to grow scarce reviewer supply |
| Microsoft Azure | HTTP/REST Stewardship Board + SDK Architecture Board (≥3 architects from different language groups must approve) | Blocking; exceptions require contacting the board before implementation | Tier-1 languages reviewed together for cross-language consistency |
| Stripe | Cross-functional "API Review" team - "engineers who care about API design" | Blocking ("forcing function"); "gavel blocks" as stakeholder checkboxes in the design doc; disputes resolved async in-doc | Documents rejected design alternatives directly in the ~20-page design doc |
| Zalando | API Guild + two named mandatory reviewers, Zally linter as first pass | Lightweight; findings tracked in GitHub as Must-change vs Improvement | Open-sourced its linter (Zally); architects auto-notified by the CTO office |
| PayPal | Central API Design team (PPaaS) | Consultative, not blocking - Jayadeba Jena's four-step governance process | Public style guide with JSON-Schema field definitions |
Recurring cross-functional stakeholders:
- Security review - Microsoft pairs it with board engagement.
- Documentation writers - Erik Wilde flags their absence as a common composition gap.
- Stripe's stakeholder checkboxes in the doc itself.
## The precedent mechanism (Google AIP-100), verbatim anchors
The published answer to "must-fix or reviewer taste":
- Precedent defined: "decisions that have already been made by previous APIs, which generally should be binding upon newer APIs in similar situations" - covering naming, standard fields, pagination, long-running operations.
- Deviation burden: a rationale comment "must be prefixed with `aip.dev/not-precedent`", with the rationale drawn from documented exception reasons - the reviewer cannot block on taste, and the deviator cannot deviate without a trace.
- Deference: "Reviewers want to avoid causing you churn, and therefore usually give deference to previous reviews... reference the code review where the issue was decided." Reopen only for a significant, still-consequential mistake.
- Escalation: strong disagreement escalates per AIP-1; outright bypass requires "a director or VP [to] make an explicit choice to put these other concerns ahead of product excellence."
- AIP-200 names the enumerated deviation reasons a rationale must match: local consistency (breaking an API's own existing pattern is jarring even if the pattern itself deviates from the wider standard), pre-existing functionality (a violation shipped before AIP guidance existed and can't be easily changed), adherence to an external spec, adherence to an existing system ("meet the customer where they are"), expediency (a deadline that can't accommodate the better design), and technical concerns (internal-systems cases where compliance costs more than it's worth).
Note: AIP-100 enforces the must-fix/taste boundary structurally through the mechanism itself, not through exhortation. The precedent mechanism is what allows disagreement to resolve without reviewer taste blocking progress.
Erik Wilde arrives at the same test from the other direction - "I am not a big fan of just reusing existing rule sets" - a rule only earns must-fix status once the organization can articulate its own intent for it. Fork a public corpus to start. Adapt it before enforcing it.
## The scaling trajectory: centralized → federated → automated
All five programs scaled the same way: mechanical checks pushed into a linter, then a distributed pool of trained reviewers, then domain-embedded reviewers feeding a central standards team. Nobody scaled by growing a central committee.
- Linting substrate:
- Google's API Linter (in-editor, as-you-type).
- Zalando's Zally.
- The OpenAPI-ecosystem standards Spectral, Vacuum, and Redocly CLI.
- Adidas ships its own Spectral ruleset so any team lints locally.
Wilde's limitation applies to all: linters "check for structural conditions... they cannot tell you whether the description is written in a way that helps [a consumer] understand what the operation is for" - linting owns mechanical must-fixes, humans keep semantics.
- Federated authority: Shopify/GitHub's GraphQL practice has product teams (domain experts) design the schema while a central API team supports rather than blocks - "think domain >>> data" (Marc-André Giroux), with schema-diff tooling carrying consistency.
- The enterprise-named version is the API Center of Excellence: a central team sets standards, embedded "coaches" apply them per domain and feed issues back. Corroborated across vendor and practitioner sources. Nordic APIs (Bill Doerrfeld) publishes named case studies, e.g. Atlassian's.
## Staged launch playbook (program from scratch)
1. Write an RFC-2119-keyworded (MUST/SHOULD/MAY) rule corpus - fork Zalando or Adidas (most reusable) or Microsoft (most enterprise-complete) rather than starting blank. Adapt before enforcing (Wilde's warning).
2. Wire a linter (Spectral for OpenAPI; Zally runs Zalando's ruleset out of the box) into CI and the editor, so humans never spend review time on mechanical checks.
3. Trigger review by audience × lifecycle - required at beta/GA for externally consumable APIs, recommended at alpha, skipped for internal-only.
4. Publish the escalation path and named override authority up front, so disagreements resolve in days.
5. Require every blocking objection to cite a written rule or precedent, and every deviation to record a rationale.
6. When the central team becomes the friction point, federate and educate - never grow the committee.
## Program-health metrics and course corrections
- Rising turnaround, or teams shipping without review → too heavy: automate more, delegate more, federate.
- Defects reaching GA that a written rule would have caught → too light / rubber-stamping: add the rule to the linter, enforce citation.
- Reviewer guidance diverging between teams → codify the disputed decision as precedent in the versioned corpus.
- Benchmarks: under-a-week turnaround for small APIs. A median past ~2 weeks signals a bottleneck. Google's post-reform survey: about 60% of participants content with review pace, and of the ~80% who used the linter, they reported it made their API design more iterative - cite both as Google-specific measurements, not industry norms.
## Named failure evidence, for citation
- **Bottlenecking - Stripe's own admission.** Kenneth Auchenberg (built Stripe's developer platform): API Review "was challenging to manage as a centralized friction point for the company, particularly at scale with 1000s of engineers. If I were to do things differently today, I would probably pivot the concept away from 'review' to more of an education service." The strongest citable argument for federation.
- **Reviews starting too late.** Google's ICSE 2024 paper ("API Governance at Scale", DOI 10.1145/3639477.3639713): pre-reform, "reviews often began too late... the review process forced teams to start over when they thought they had done a good job."
- **Pool growth degrading consistency.** Same paper: "as the number of participants grew, so did the turnaround time... and the inconsistency in reviewer guidance, due to constantly evolving style guides and best practices."
- **Origin story.** Macvean, Maly & Daughtry, "API Design Reviews at Scale" (CHI 2016, DOI 10.1145/2851581.2851602): Google's users suffered "a death from a thousand papercuts", answered with "a lightweight, scalable, distributed design review process".
- **Why it's product excellence, not bureaucracy.** A study cited in Google's 2024 governance paper (Nadi et al.): 88% of Google Play apps using cryptographic APIs contain at least one API-misuse mistake - the standard citation for badly designed APIs causing measurable defects.
references/worked-review-excerpt.md›
# Worked review-report excerpt
An excerpt from a review of a fictional but representative surface - an orders/customers REST API heading for public beta. Every finding below is a documented real-world pattern (verb-in-URI, 200-with-error, mixed casing, unpaginated collections, silently ignored filters); the API itself is invented so the excerpt fits one page. Match this shape, not this content.
---
## Scope
- Surface: `api.example.com/v1` - 14 endpoints over `customers`, `orders`, `invoices`. OpenAPI 3.1 spec provided; 8 sampled production responses.
- Audience × stage: public × beta → blocking review; Must-change findings block launch.
- Rule corpus: none adopted by the team → findings cite this skill's checklist items by dimension and name.
## Dimension summary
| Dimension | Result |
| -------------------------------------- | ---------------------------------------------------------------------------------------------- |
| 1. Resource modeling & URI naming | FAIL - 2 findings |
| 2. HTTP method semantics | pass |
| 3. Status-code correctness | FAIL - 1 finding |
| 4. Field-naming consistency | FAIL - 1 finding |
| 5. Pagination | FAIL - 2 findings |
| 6. Filtering, sorting, field selection | FAIL - 1 finding |
| 7. Error shape consistency | pass (envelope uniform; depth deferred to `samber/developer-platform-skills@api-error-design`) |
| 8. Backward compatibility | pass with 1 accepted deviation (below) |
| Sibling-boundary checks | versioning ✓, idempotency ✓, auth ✓, rate limits ✓, docs gap → 1 Improvement |
## Findings
| # | Finding | Dimension | Bucket | Cited rule | Fix |
| --- | ---------------------------------------------------------------------------------------- | --------- | ----------- | ----------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| 1 | `POST /orders/{id}/cancel-order` - verb in the URI | 1 | Must-change | "Resources are nouns, never verbs" | `POST /orders/{id}/cancel` as the surface's one custom-method convention, applied to the two other action endpoints too |
| 2 | `/customers/{id}/orders/{id}/invoices/{id}/lines` - 4-level nesting | 1 | Must-change | "Nesting stops at 2-3 levels" | Promote `invoices` to a top-level resource filtered by `order_id` |
| 3 | `DELETE /customers/{id}` returns 200 with `{"success": false, "error": ...}` on conflict | 3 | Must-change | "No 200 carrying an error object" | Return 409 with the surface's standard error envelope |
| 4 | `orders` returns `created_at`; `customers` returns `createdAt` | 4 | Must-change | "One casing convention... consistency of the choice is the check" | Standardize on `created_at` (majority of the surface); alias-and-deprecate `createdAt` via `samber/developer-platform-skills@api-versioning-policy` machinery - clients already observe it |
| 5 | `GET /invoices` is unpaginated | 5 | Must-change | "Every collection endpoint is paginated" | Cursor pagination, matching the other two collections |
| 6 | No `has_more` equivalent on any collection | 5 | Improvement | no corpus rule adopted; checklist SHOULD-level item | Add `has_more`; cheap now, breaking to retrofit later |
| 7 | `GET /orders?staus=open` returns the full unfiltered list | 6 | Must-change | "A filter on an unknown field returns 400/422 - never silently ignored" | 400 with the unknown parameter named |
| 8 | Two endpoints undocumented in the reference | boundary | Improvement | one-line docs check | Hand to `samber/developer-platform-skills@api-reference-quality` |
## Accepted deviations
- **Offset pagination retained on `GET /customers`** despite the surface standardizing on cursors. Rationale recorded by the team: the admin UI needs numbered pages and random access; dataset is small and slow-changing, where offset's weaknesses don't bite (strategy table, dimension 5). Deviation documented here - binding as precedent for future admin-facing collections, not a silent inconsistency.
## Ship gate
Findings 1-5 and 7 (Must-change) open → beta launch blocked. Findings 6 and 8 (Improvement) may ship open. Finding 4's fix is itself a breaking change: schedule through `samber/developer-platform-skills@api-versioning-policy`, don't hot-swap the field.
---
## Negative example - the comment this format exists to prevent
> **BLOCKING:** I'd go with camelCase here, snake_case always looks dated to me. Also consider nesting invoices under orders, it reads better.
Everything wrong at once:
- It blocks on taste with no cited rule ("looks dated to me").
- It pushes casing preference where the only citable rule is consistency of the choice.
- "Consider..." is advisory phrasing inside a blocking comment.
- It re-litigates a structure the team may have settled in a prior review.
Written correctly, the casing point is finding 4 (cite the consistency rule, either casing acceptable), and the nesting point is either a cited-rule finding (if depth exceeds 3) or an Improvement - never a block.
SKILL.md›
---
name: public-api-design-review
description: Audit an existing or proposed public REST API surface as a checklist-driven design review - resource and URI naming, HTTP method and status-code correctness, field-naming consistency, pagination pattern choice, filtering/sorting/field-selection conventions, one consistent error envelope, and Hyrum's-Law backward-compatibility risk - every finding bucketed Must-change or Improvement against a cited rule, plus the standing review program (audience, lifecycle triggers, reviewer authority, linting, federation). Use whenever the user mentions an API design review, REST API consistency, endpoint naming, pagination style, or API design governance - even if they never say "design review". Do NOT use for GraphQL schemas - use samber/developer-platform-skills@public-graphql-api-design instead.
license: MIT
metadata:
author: Samuel Berthe
version: "1.0.2"
---
# Public API Design Review
You are a public API design reviewer. Walk a REST API surface - shipped or still on paper - through a pass/fail checklist of the conventions external developers expect, bucket every finding as Must-change or Improvement with a cited rule, and, when asked, design the standing review program around the audit.
Zalando's guideline states the goal in one line: "great RESTful APIs look like they were designed by a single team." Cross-surface consistency, not any single endpoint's cleverness, is what you review for.
**Memory (advised):** When memory lives in a file, consider using `developer-platform-context.md`; if a different memory system is in use, rely on that instead. The file is an advisory reference, not a mandatory requirement. Separate task info in different sections. Remove finished tasks. Add a date to a task; no date for general project context. Some interview responses may differ between 2 tasks.
## Clarifying questions
Ask these before reviewing anything; each answer changes a later step. Batch them - this is a tactical audit, not a strategy interview.
1. Existing production API or proposed design? A shipped surface makes every finding a Hyrum's-Law question: real clients may depend on the behavior you want fixed.
2. What material exists: an OpenAPI (or similar) spec, reference docs, or sample requests/responses? Ask for it - never review from the API's name alone.
3. One-off review, or a standing review program to design as well? If a program: how many teams ship APIs, by when must it operate, and what's the effort ceiling - a linter afternoon, a named reviewer pair, or a funded multi-quarter program? (re-ranks the maturity ladder - see step 6)
4. Who consumes the surface - internal teams only, named partners, or anyone - and what lifecycle stage is it at (alpha, beta, GA)? (see next section)
5. Which rule corpus does the team follow: their own guidelines, Zalando, Microsoft, Google AIPs, JSON:API - or none? A Must-change must cite a rule; the corpus is where citations come from.
6. Which behaviors have clients already observed and possibly built against - field names, error shapes, ordering, undocumented endpoints?
## Audience split: surface exposure and lifecycle
Two splits change the review:
- **Internal-only vs partner vs public.** Google's AIP-100 keys whether review is required on exactly this axis: internal or single-customer surfaces can skip formal review; anything externally consumable cannot. Public surfaces also carry the full Hyrum's-Law exposure - you cannot enumerate, contact, or migrate the clients.
- **Alpha vs beta vs GA.** Review is recommended at alpha, required (and blocking) at beta, and required again at GA if the surface changed. An alpha API may ship to a known user set without approval; a beta launch is where unresolved Must-change findings block.
Cross the two axes to set the review's severity posture:
- An internal alpha gets advisory feedback.
- A public beta gets the full blocking checklist.
## Workflow
1. Scope the surface.
2. Run mechanical checks first.
3. Walk the checklist, dimension by dimension.
4. Bucket findings through the precedent mechanism.
5. Deliver the report.
6. Design the standing program - only when question 3 said so.
Each step has a section below, in order.
## 1. Scope the surface
- Confirm the paradigm is REST/HTTP. Hand a GraphQL surface to sibling `samber/developer-platform-skills@public-graphql-api-design` and a gRPC surface to `samber/developer-platform-skills@public-grpc-api-design` - their conventions differ enough that this checklist misleads.
- Inventory the endpoints from the spec or docs. Tag each as collection (pagination and filtering checks apply), mutating (method-semantics checks apply), or both.
- Record the rule corpus from question 5. When the team has none, use [references/review-checklist.md](references/review-checklist.md) as the corpus and say so in the report - findings then cite this checklist's items by name.
- For a shipped API, collect 5-10 real responses across endpoints; a spec can claim a consistency its production traffic doesn't have.
## 2. Run mechanical checks first
If you can run tools and an OpenAPI spec exists, run a spec linter (Spectral-class; Zally ships Zalando's ruleset) before any human-judgment check. Linters check structural conditions; they cannot tell whether an operation makes sense to a consumer (Erik Wilde's limitation) - so linting owns the mechanical findings and you own the semantic ones, never the reverse split.
No spec, or no tooling available: proceed straight to step 3, and log the missing spec itself as an Improvement finding - every mature review program treats a machine-readable spec as the review's substrate.
## 3. Walk the checklist
Eight dimensions, walked in full and in order - a checklist, not a menu to pick from. Four independently developed rule corpora (Google AIPs, Zalando, PayPal, Adidas) converge on this same dimension set, so a skipped dimension is a hole a mature reviewer would notice.
1. Resource modeling and URI naming - nouns not verbs, plural collections, nesting depth.
2. HTTP method semantics - safe/idempotent contract per method, no POST for idempotent operations.
3. Status-code correctness - 400 vs 422, 401 vs 403, 201+Location, never 200-with-error.
4. Field-naming consistency - one casing everywhere, boolean prefixes, enum casing.
5. Pagination - a deliberate strategy, applied uniformly, with edge cases specified.
6. Filtering, sorting, field selection - shared parameter conventions across collection endpoints.
7. Error shape consistency - one envelope on every endpoint and status. Shape only: the code taxonomy, message writing, and retry signaling inside the envelope are sibling `samber/developer-platform-skills@api-error-design`'s job.
8. Backward compatibility - Hyrum's Law, addition-over-modification, what the change under review breaks.
The full pass/fail items per dimension live in [references/review-checklist.md](references/review-checklist.md) - load it for the walk; the list above is only the map.
Five adjacent concerns get an existence-and-consistency check only - one line each in the report, with depth deferred to the sibling that owns it:
- A versioning scheme exists and is applied uniformly - `samber/developer-platform-skills@api-versioning-policy`.
- Unsafe retries are addressed via idempotency keys or documented - `samber/developer-platform-skills@api-idempotency-retry`.
- One auth model covers the surface - `samber/developer-platform-skills@api-auth-key-management`.
- Rate limits are signaled in responses - `samber/developer-platform-skills@api-rate-limit-policy`.
- Every endpoint is documented - `samber/developer-platform-skills@api-reference-quality`.
## 4. Bucket findings through the precedent mechanism
Two buckets, no numeric score - Must-change vs Improvement is what every published program (Zalando, Haufe) actually uses, and severity lives in the rule itself as RFC 2119 keywords (MUST/SHOULD/MAY), not in reviewer judgment at grading time.
- **A Must-change finding must cite a written rule or precedent** - from the team's corpus, or from this skill's checklist when the team has none. This is Google AIP-100's mechanism: if a rule or precedent exists, a violation is must-fix; if none exists, the comment is advisory.
- **The burden flips onto the deviator, not the reviewer.** A team that wants to violate a cited rule records a rationale referencing a documented exception (Google's `aip.dev/not-precedent` convention) - see [references/review-program-benchmarks.md](references/review-program-benchmarks.md) for Google's own enumerated exception reasons. Taste never blocks; documented deviation is allowed but leaves a trace.
- **No cited rule → Improvement, always.** The calibration line practitioners converge on: firm on decisions that affect clients or security, forgiving on stylistic preferences.
- **Consistency beats correctness of style.** `created_at` on one endpoint and `createdAt` on a sibling is a top-priority Must-change regardless of which casing is "right" - the cited rule is "pick one, apply everywhere", not a casing preference.
- **Retrofit caution:** on a shipped API, a Must-change against behavior clients already observe (question 6) is a breaking change. Route it through the deprecation machinery (`samber/developer-platform-skills@api-versioning-policy`) instead of demanding a silent in-place fix - the review names the defect; the versioning policy schedules its removal.
- Defer to previous reviews: a settled deviation is only reopened when the earlier decision was a significant mistake that matters now. Re-litigation is churn, not rigor.
## 5. Deliver the report
Report shape: a scope header (surface, audience, lifecycle stage, corpus cited), a dimension-by-dimension pass/fail summary, then a findings table - finding, dimension, bucket, cited rule, proposed fix. Close with the deviations accepted and their recorded rationales.
See [references/worked-review-excerpt.md](references/worked-review-excerpt.md) for a worked excerpt, including a negative example of the taste-based blocking comment this format exists to prevent.
If your harness has persistent memory, record each accepted deviation and settled decision as a precedent - the next review over this surface then cites them instead of re-litigating.
## 6. Design the standing program
Run this step only when question 3 asked for it. A one-off audit needs none of this.
- **Trigger reviews by audience × lifecycle stage, never by calendar.** Required at beta and at GA-if-changed for externally consumable APIs; recommended at alpha; skipped for internal-only surfaces. Decouple review from ship deadlines everywhere except the beta gate.
- **Publish the escalation path and the named override authority up front** - at Google, bypassing review is an explicit director-or-VP decision. Disagreements then resolve in days, not weeks.
- **Review designs, not finished implementations.** Google's own pre-reform failure was reviews starting so late that teams had to start over; the review's input is the spec or design doc, before the code exists.
Program maturity ladder - ranked:
- efficiency: `linter + two named reviewers > linter-only CI gate > certified federated reviewer pool`
- effort: `certified federated reviewer pool > linter + two named reviewers > linter-only CI gate`
- value: `certified federated reviewer pool > linter + two named reviewers > linter-only CI gate`
- **Default rung: linter + two named reviewers** (Zalando's documented shape - Zally as first pass, two mandatory human reviewers per API, findings tracked as Must-change vs Improvement). Days to stand up on a forked public corpus, and it covers both mechanical and semantic findings. Move up when the human pair becomes the friction point: median turnaround past ~2 weeks, or teams shipping around review.
- **Step down to a linter-only CI gate** when one or two teams own the whole surface - the corpus-plus-linter is an afternoon's setup and the semantic conversation still happens in ordinary code review.
- **Promote to a certified federated reviewer pool** - a shared versioned rule corpus, reviewer certification (Google's "API Readability" model), domain-embedded reviewers feeding a central standards team - for many-team organizations. This is the starved option: highest value and highest effort (a quarter-scale program of corpus writing, training, and certification), so it loses every efficiency round - and organizations at the thousands-of-engineers scale need it anyway.
- Kenneth Auchenberg, who built Stripe's developer platform, conceded centralized review became "a centralized friction point... at scale with 1000s of engineers" and would pivot it toward an education service - attribute that to him, not to "Stripe says".
- Growing a central committee is the one scaling move no studied company chose.
- **Deleted, not demoted: the federated reviewer pool at one- or two-team scale.** Question 3 answering that one or two teams own the whole surface removes this rung from the menu rather than ranking it last - there are no domains to embed reviewers in, so certification and a versioned shared corpus are ceremony over a conversation two people already have. Not parked at the bottom, because "eventually we'll federate" reappears as a roadmap item that consumes corpus-writing time nobody needs yet. Re-promotion trigger: a third team shipping its own API surface, or median review turnaround past ~2 weeks.
- This ranking is a default, not a law. Re-rank against question 3's answers and the organization, and say which answer moved which rung:
- A near-term deadline or a low effort ceiling promotes the linter-only gate.
- An existing API guild makes the federated rung nearly free.
- A team already fluent in Zalando's or Microsoft's public corpus skips the corpus-writing cost.
- A platform with one flagship API may never outgrow the default rung.
Company-by-company trigger, authority, and scaling detail - with the staged launch playbook and program-health metrics - lives in [references/review-program-benchmarks.md](references/review-program-benchmarks.md).
## Failure modes
- **Rubber-stamping and bottlenecking** - the two named failure modes of review programs, and both avoidable.
- Too heavy: teams route around it.
- Too light: a rubber stamp.
- Calibration: firm on client-affecting and security decisions, forgiving on style, quick turnaround within a week.
- **Review as a late gate.** Feedback arriving after implementation forces teams to start over (Google's documented pre-reform state). Fix: trigger at design time, on the spec.
- **Growing reviewers without a shared versioned corpus.** Google measured turnaround and guidance consistency degrading together as the pool grew - more reviewers amplify inconsistency unless the rules are written down and versioned.
- **Taste-based blocking comments.** A block with no cited rule is the anti-pattern step 4 exists to prevent; it recreates both failure modes at once.
- **"It's documented" as a fix for an inconsistency.** Developers don't read docs under deadline pressure - make the consistent choice the default or only option; documenting the trap is not closing it.
- **Reviewing endpoints in isolation.** Consistency defects - mixed casing, mixed pagination, divergent error shapes - are invisible one endpoint at a time. Always review across the surface.
## Measurement
- **Audit gate: zero Must-change findings open at ship.** For a beta or GA launch, iterate - fix, or record a documented deviation - until the Must-change list is empty. Improvements may ship open.
- **Coverage gate: every checklist dimension walked**, plus the five one-line sibling checks. A skipped dimension makes the review incomplete, not lean.
- **Program health (standing programs only):**
- Target under-a-week turnaround for small APIs.
- Median turnaround past ~2 weeks, or teams shipping without review, means too heavy - automate or federate.
- Defects reaching GA that a written rule would have caught mean too light - add the rule to the linter and require citations.
- These thresholds are Google-derived benchmarks: treat the direction as sourced and the exact numbers as calibration points, not laws.
## Invocation examples
- "Review this OpenAPI spec before we take the API public - flag anything external developers will find inconsistent."
- "Audit our /v1 endpoints: pagination works differently per resource and the error bodies don't match. What's must-fix versus nice-to-have?"
- "We're going from 3 teams to 30 - design an API design-review process that won't become a bottleneck."
## References
- [references/review-checklist.md](references/review-checklist.md) - the full pass/fail checklist for all eight dimensions, plus the five sibling-boundary checks.
- [references/worked-review-excerpt.md](references/worked-review-excerpt.md) - a worked review-report excerpt with cited-rule findings, a recorded deviation, and a negative example.
- [references/review-program-benchmarks.md](references/review-program-benchmarks.md) - verified trigger/authority/scaling models at Google, Microsoft Azure, Stripe, Zalando, and PayPal; the staged program-launch playbook; program-health metrics.
- `samber/developer-platform-skills@api-integration-surface-strategy` - the umbrella decision that schedules the REST surface this skill reviews; a surface this skill has never seen scheduled there needs that decision first, not a review.
- `samber/developer-platform-skills@api-beta-program` - runs the preview this skill's beta-level review gates; no account enrolls while Must-change findings stay open.