Reviews and designs API contracts — function signatures, REST endpoints, library interfaces — for usability, evolvability, and the principle of least surprise. Use when designing a new public interface, when reviewing an API PR, when the user asks whether a signature is well-designed, or when planning a breaking change.
80
100%
Does it follow best practices?
Run evals on this skill
Adds up to 20 points to the overall score
View guide
Passed
No findings from the security scan
An API is a promise to strangers. Every decision you make now constrains every user forever — or forces a breaking change. Design for the caller you'll never talk to.
For every public surface, ask:
| Smell | Why it hurts | Fix |
|---|---|---|
| Boolean parameter | save(true) — true what? The call site is unreadable. | Two methods, or an enum: save(Overwrite.YES) |
| Positional params > 3 | create(a, b, c, d, e) — which is which? | Named/keyword params, or a config object |
| Inconsistent naming | getUser() but fetchOrder() but loadProduct() | Pick one verb per concept. Grep before naming. |
| Stringly-typed | setMode("fast") — typo → silent no-op | Enum, or fail loudly on unknown strings |
Return null for "not found" | Every caller must check; most won't | Optional/Maybe, or throw, or a sentinel — be consistent |
| Out parameter | parse(input, &result, &error) | Return a struct/tuple. Out params are C legacy. |
| Required call order | Must call init() before use() | Make it impossible to get wrong: use() inits if needed, or the constructor inits |
| Property | Makes future changes… |
|---|---|
| Accepts an options object/struct | Easy to add fields. Positional args → every addition is breaking. |
| Returns an object, not a tuple | Can add fields. Tuple → new field breaks destructuring. |
Versioned (REST: /v1/, library: semver) | Possible. Unversioned → every change is scary. |
| Tolerates unknown input fields | Old clients can talk to new servers |
| Output fields nullable/optional by default | Can add a field old clients don't read |
Adding is cheap. Removing is forever. Every parameter, every field, every endpoint: could you live with it for 5 years?
The name IS the documentation most callers read. If behavior doesn't match the name:
getUser(id) that creates a user if missing → surprise. Call it getOrCreateUser.list() that returns the first 100 → surprise. Call it listPage or document loudly.setX(v) that also triggers a network call → surprise. Setters should be cheap.close() that can't be called twice → surprise. Idempotent close is the convention.Proposed:
def send_email(to, subject, body, html, cc, bcc, attachments, retry, async_):
...Review:
| Issue | Severity | Fix |
|---|---|---|
| 9 positional parameters | High — call sites will be unreadable | Required: to, subject, body. Rest: keyword-only with defaults. |
html is a boolean | Medium — send_email(..., True, ...) means what? | body_format: Literal["text", "html"] = "text" |
async_ — name with trailing underscore | Low — Python keyword collision, but ugly | background: bool or defer: bool |
attachments type unclear | Medium — list of paths? bytes? file objects? | Type annotation + docstring. Or a typed Attachment class. |
| No way to add headers later without a 10th param | High — evolvability | Wrap the optional stuff: send_email(to, subject, body, *, options: EmailOptions = None) |
Suggested:
def send_email(
to: str | list[str],
subject: str,
body: str,
*,
body_format: Literal["text", "html"] = "text",
cc: list[str] = (),
bcc: list[str] = (),
attachments: list[Attachment] = (),
retry: int = 0,
) -> EmailResult:Keyword-only after *. async_ dropped — make it a separate send_email_async(). Return a result object so you can add message_id, delivered_at, etc. later.
GET /users/42, not GET /getUser?id=42.200 {"error": "..."}.?limit=&cursor= — you'll need it once the table grows.{"error": {"code": "...", "message": "..."}} — clients pattern-match on it.PATCH for partial update, PUT for full replace — don't make PUT mean PATCH.## Usability
| Issue | Severity | Fix |
| ... | ... | ... |
## Evolvability
<what happens when you need to add X — can you, without breaking callers?>
## Surprise
<does behavior match the name? list mismatches>
## Suggested signature / contract
<concrete revision>47d56bb
If you maintain this skill, you can claim it as your own. Once claimed, you can manage eval scenarios, bundle related skills, attach documentation or rules, and ensure cross-agent compatibility.