Api and interface design
Skill ats4321/claude-engineering-skills/skills/api-and-interface-design
26 repository-agnostic engineering skills for Claude Code — debugging, design, review, validation, and AI engineering as operational runbooks.
npx -y skills add ats4321/claude-engineering-skills --skill api-and-interface-designAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
One thing to look at
- 0 stars0 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
What its author says it does
Copied from the file, not written here
Design functions, modules, CLIs, REST/RPC APIs, and data contracts that are easy to use correctly and hard to misuse. Auto-load when designing or reviewing any public interface — function signatures, module boundaries, CLI commands and flags, HTTP/RPC endpoints, event schemas, or data contracts; when evolving an existing API without breaking consumers; when designing error responses; or when deciding versioning strategy. NOT for whole-system architecture (system-design), NOT for analyzing existing seams (architecture-analysis), and NOT for LLM output contracts (llm-integration-reliability).
SKILL.md
15.5 KB, as published. Nobody here has run it
API and Interface Design
Purpose
Every interface is a promise: consumers will build on exactly what you expose, forever. This skill is the craft of making good promises — consumer-first design, minimal surface area, explicit error contracts, and evolution rules that add without breaking — so an interface stays easy to use correctly and hard to misuse across years of change.
Metadata
- Prerequisites:
architecture-analysis(identifies WHERE a contract/seam belongs; this skill designs the contract itself);engineering-minimalism(surface minimization is its doctrine applied to interfaces). - Related Skills:
system-design(the system around the interface),documentation-practices(documenting the contract),refactoring-playbook(changing implementations behind stable interfaces),build-and-release(shipping versioned artifacts). - Owns: API evolution and versioning; error contract design; interface surface minimization; backwards-compatible change design; naming for interfaces.
When to Use / When NOT to Use
Use when:
- Creating any surface another party (person, team, service, or agent) will call: function, class, module, CLI, endpoint, event, schema.
- Changing an existing interface that has consumers.
- Reviewing a diff that touches a public signature, response shape, or flag.
- Designing what errors an interface returns.
Do NOT use (load the sibling instead):
- Deciding overall system shape and components →
system-design. - Locating where seams/boundaries belong in an existing system →
architecture-analysis. - The "contract" is an LLM's output format →
llm-integration-reliabilitystep 1. - Purely internal code no one else calls → design freely; interfaces earn this rigor when they have consumers (
engineering-minimalism).
Definitions & Mental Model
- Surface area: everything a consumer can observe and depend on — names, parameters, return shapes, error types, ordering, timing, and defaults. If it is observable, someone depends on it (Hyrum's Law).
- Error contract: the enumerated, documented set of failure shapes an interface can return — as much a part of the API as the success shape.
- Breaking change: any change that makes a previously-valid consumer invalid — removing/renaming, tightening input requirements, loosening output guarantees.
- Additive change: a change every existing consumer survives — new optional parameter, new field in output, new endpoint.
- Idempotency (concept owned by
architecture-analysis): here it appears as a contract property you document and design for on mutating operations.
Mental model: design the calls before the code. A principal engineer writes five realistic consumer call-sites first — the ugly ones included — and only then designs the signature that makes those call-sites clean. The interface serves the consumer's grammar, not the implementation's convenience. And because consumers bind to everything observable, the second instinct is subtraction: every parameter, field, and flag you do not expose is a future you will never have to support. Expose the minimum; you can always add, you can almost never remove.
Core Methodology
- Write the consumer's code first. Before designing the signature/endpoint, write 3–5 realistic call-sites: the common case, an edge case, an error-handling case. If a call-site reads awkwardly, the design is wrong — fix the interface, not the example.
- Minimize the surface.
- Expose one way to do each thing. Two overlapping methods double the support burden and split consumer idioms.
- Default aggressively: the common case should need the fewest arguments; rare knobs get optional parameters with safe defaults.
- Keep types at the boundary simple and explicit (primitives, small typed records/dataclasses) — clever generic signatures are a tax on every consumer.
- Anything you are unsure about: leave it OUT. Adding later is additive; removing later is a breaking change.
- Name for the reader at the call-site.
retry_count=3beatsn=3;delete_after_daysbeatsttlwhen consumers aren't systems programmers. Booleans that read ambiguously at the call-site (process(True)) become enums or keyword-only arguments. Consistency beats cleverness: match the naming grammar of the surrounding interface family. - Design the error contract explicitly (as deliberately as the success path):
- Enumerate every failure the consumer can observe; give each a distinct, documented shape (typed exception, error code, HTTP status + body schema).
- Distinguish caller errors (bad input — fix your call) from system errors (retry later) — consumers handle these differently, so the contract must separate them.
- Error messages name the remedy ("Run
initfirst"; "fieldemailmust match ..."), not just the fault (actionable-message craft:observability-and-diagnostics). - Never leak internals (stack traces, SQL, file paths) across a public boundary — they become de facto API and a security finding (
security-review-playbook).
- Make mutating operations idempotent where the transport can retry. Networks retry; consumers retry; design so that repeat = no-op (idempotency keys, PUT semantics, delete-then-write). Document the idempotency property in the contract (concept owned by
architecture-analysis). - Design pagination and limits into any collection-returning interface from day one. An unbounded list endpoint is a breaking change waiting to happen — retrofitting pagination breaks every consumer. Cursor or page+size, plus a maximum, from the first version.
- Evolve additively; version only when you must (decision tree):
You need to change an interface with consumers.
├─ Can it be ADDITIVE? (new optional param with a default, new output
│ field consumers can ignore, new endpoint/command alongside the old)
│ → DO THAT. No version bump beyond semver-minor. The default path.
├─ Must you change meaning/shape of something existing?
│ ├─ Can you add the new form alongside and deprecate the old?
│ │ → Add new + mark old deprecated (docs + warning) + set a
│ │ removal horizon. Two supported forms is temporary debt
│ │ with an expiry date, not a permanent state.
│ └─ Truly incompatible and coexistence impossible?
│ → BREAKING CHANGE: new major version / v2 endpoint.
│ Migration notes required. Never silently in place.
└─ Is it a bug fix that consumers may DEPEND on? (Hyrum's Law)
→ Treat the fix as a change: check observable behavior deltas,
announce, and consider a compatibility flag if blast radius
is real. "It was a bug" does not unbreak consumers.
Semver as the signaling convention where artifacts are versioned: patch = fixes, minor = additive, major = breaking (release mechanics: build-and-release).
8. Run the misuse test before shipping. For each interface ask: what is the most damaging plausible misuse? Then make it impossible (types, validation at the boundary), loud (fail fast with an actionable error), or at minimum documented. "Easy to use correctly, hard to use incorrectly" is the acceptance bar.
Interface review checklist
- 3–5 consumer call-sites written before/alongside the design, and they read cleanly
- One way per capability; no overlapping methods
- Common case needs minimal arguments; rare knobs default safely
- Names read correctly at the call-site; no ambiguous booleans
- Error contract enumerated; caller-vs-system errors distinguishable; remedies in messages; no internals leaked
- Mutating operations idempotent (or the non-idempotency documented loudly)
- Collection returns paginated and bounded from v1
- Every change classified additive / deprecating / breaking, handled per the tree
- Deprecations carry a removal horizon
- The most damaging plausible misuse is impossible, loud, or documented
Discovery & Audit Commands
Audit an existing interface surface before changing it:
# Enumerate the public surface (adjust per ecosystem)
grep -rn "^def \|^class \|export function\|export const\|export class" --include="*.py" --include="*.ts" . | grep -v node_modules | grep -v "^_\|def _" | head -40
grep -rn "@app\.\|@router\.\|app\.get\|app\.post" --include="*.py" --include="*.ts" . | grep -v node_modules # HTTP surface
grep -n '"bin"\|\[project.scripts\]' package.json pyproject.toml 2>/dev/null # CLI surface
# Who consumes what you're about to change? (breaking-change blast radius)
grep -rn "the_function_name(" --include="*.py" --include="*.ts" . | grep -v node_modules
# Error contract inventory: what can consumers observe on failure?
grep -rn "raise \|throw \|status_code\|HTTPException" --include="*.py" --include="*.ts" . | grep -v node_modules | head -30
# History of past interface changes (were they additive?)
git log --oneline -20 -- path/to/interface/file
git log -p -S "def the_function_name" --oneline | head
Failure Modes & Anti-patterns
| Symptom | Mistake | Correction |
|---|---|---|
| Consumers write wrapper functions around your API | Interface serves the implementation, not the caller | Write consumer call-sites first; redesign until they're clean (step 1) |
"What does process(data, True, None, 7) mean?" | Positional booleans and magic numbers | Keyword-only args, enums, named constants (step 3) |
| Minor release breaks three consumers | Change assumed harmless because "it was a bug" | Hyrum's Law: classify by observable behavior delta, not intent (step 7) |
| Every consumer parses your error strings | No structured error contract | Typed/coded errors; caller-vs-system split; strings are prose, not API (step 4) |
| Retried request charged the customer twice | Mutating op not idempotent | Idempotency keys / PUT semantics; document the property (step 5) |
| Adding pagination breaks all clients in v3 | Unbounded collection endpoint shipped in v1 | Paginate and bound from the first version (step 6) |
| Two half-deprecated ways to do everything, forever | Deprecation without a removal horizon | Every deprecation gets a date/version for removal (step 7) |
| Stack traces in production API responses | Internals leaked across the boundary | Map internal errors to contract errors at the edge (step 4) |
| 14 config parameters, 12 never used | Speculative knobs "for flexibility" | Expose the minimum; add later if demanded (step 2) |
| Same concept named 3 ways across the surface | No naming grammar | Match the family's conventions; one term per concept (step 3) |
Worked Example
Task: design a module interface for text-file snapshotting — other teams will call it.
Consumer call-sites first:
snap = snapshots.create("configs/app.yaml") # common case
snap = snapshots.create("configs/app.yaml", label="pre-deploy") # labeled
snapshots.restore(snap.id) # restore
for s in snapshots.list_for("configs/app.yaml", limit=20): ... # bounded listing
try:
snapshots.restore("nonexistent")
except snapshots.SnapshotNotFound as e: # error handling
print(e) # "Snapshot 'nonexistent' not found. List available with list_for(path)."
Design decisions the call-sites forced: create needs only the path (labels optional, defaulted); restore takes an id, not a path+index puzzle; listing is bounded (limit) from v1; errors are typed (SnapshotNotFound = caller error; StorageUnavailable = system error, retryable) with remedies in messages. restore is idempotent — restoring the same snapshot twice yields the same file state. Surface = 3 functions + 2 exception types; the tempting diff(), prune(), and export() are left out until demanded.
Evolution, one year later: consumers want compression. Additive path: create(path, label=None, compress=False) — every existing call-site survives; semver-minor. The rejected alternative (changing create to return a different record shape) was breaking and unnecessary.
Repository Examples
Repo facts below are point-in-time illustrations (as of 2026-07-04) — examples, never assumptions about your system.
- ragit (
~/ragit) — error contracts with remedies: custom exceptions (IndexingError,RetrievalError,OllamaConnectionError,OllamaModelsError) whose messages name the fix — "Runragit index {path}first" — the step-4 craft in a CLI surface. Also a minimal-surface CLI: exactly four commands (models/index/chat/clear). - agentix (
~/agentix) — extension-point interface design: theTooldataclass (name, description, args_schema, fn) plus a self-registeringregister()is a stable, minimal contract that makes adding a tool additive by construction — one new file, zero dispatch edits. - prism (
~/prism) — pinning an upstream contract: the GitHub API version is fixed in headers ("2022-11-28"), insulating the code from upstream interface evolution — the consumer side of versioning discipline. - ruflo (
~/ruflo) — versioned-artifact coupling as an interface property: three npm aliases must ship at identical versions with coordinated dist-tags; a version skew between the umbrella and scoped packages is a broken contract for consumers (the slip is recorded in commit "fix: @claude-flow/browser peer dep, dist-tags, bump to alpha.3").
Validation Criteria
You applied this skill correctly when:
- The consumer call-sites exist and read cleanly — a stranger can use the interface from them alone.
- The public surface contains nothing you cannot justify a consumer needing today.
- The error contract is enumerated, typed/coded, caller-vs-system separable, and remedy-bearing.
- A retried mutating call provably produces the same state (test it).
- Every shipped change is classified additive/deprecating/breaking, and breaking changes carry a version bump + migration note.
- The misuse test was run and its worst case is impossible, loud, or documented.
Provenance & Maintenance
- Sources:
~/ragit,~/agentix,~/prism,~/ruflo— investigated 2026-07-04. Skill authored 2026-07-06; methodology is repo-independent. - Assumptions: semver as the versioning convention is an industry default, not universal — repos with different conventions (calver, API date-versioning) keep the tree's logic and swap the signaling. Repo facts are point-in-time.
- Re-verification commands:
grep -rn "class .*Error" ~/ragit/ragit grep -rn "class Tool\|def register" ~/agentix/agentix | head grep -rn "2022-11-28" ~/prism/prism - Likely to drift: example repos' surfaces; the GitHub API version pin; versioning conventions per ecosystem.
- Maintenance checklist:
- Re-run re-verification; re-stamp Repository Examples.
- Confirm cross-referenced skills still exist under their directory names.
- If an owner repo ships a real deprecation cycle, add it as a case study.