api-error-check¶
Read-only conformance check of a web API's error-handling surface (body shape, status codes, internal-detail leakage) against the project's declared error contract.
Statically checks a web API's error-handling surface for conformance against the project's own declared error contract — uniform error-body shape, populated required fields, a dynamically-generated error/correlation id, correct HTTP status-code semantics, and no internal-detail leakage (stack traces, raw driver messages, rendered queries, secrets). Detects the web framework (FastAPI / Flask / Django REST / Express / NestJS / Spring and comparable) and the error contract from project signals, falling back to RFC 9457 defaults only when none is declared. Invoke after adding or changing endpoints, before a release, or as a pre-PR error-handling gate; also on requests like "check the API error handling", "audit error responses", or equivalent German-language requests. Read-only: reports and recommends, never edits handler code. Don't use for the whole-codebase security audit (code-security-reviewer) or general code review (review skill).
- Plugin:
nolte-engineering - Phase: 6 Quality (
quality) - Tags:
review - Source: skills/api-error-check/SKILL.md
Use when¶
- you added or changed API endpoints and want to confirm their error handling conforms
- you want a pre-PR or pre-release error-handling conformance gate
- you want to confirm error responses don't leak stack traces, driver messages, or queries
Don't use when¶
- You want a whole-codebase OWASP security audit →
code-security-reviewer
See also¶
API Error-Handling Conformance Check¶
Statically check a web API's error-handling surface against the project's own error contract, and produce a single severity-sorted report. This skill reports and recommends; it never edits handler code.
Implements spec/project/api-error-handling/ — the spec defines the conformance dimensions, severity mapping, and discovery rules. This skill binds those rules to the on-disk procedure. When the spec and this skill disagree, the spec wins.
German trigger phrases¶
This skill also triggers on equivalent German-language requests, including:
- "API-Fehlerbehandlung prüfen"
- "Fehlerantworten auditieren"
- "Leaken die Endpunkte interne Details?"
User-language policy¶
Detect the user's language from their message and respond in it. The report itself uses English section headings (so downstream tooling can parse it reliably); prose around the report is localised.
Inputs¶
- Target: an explicit handler/router file or directory path, or a requirement/feature identifier the project uses to group endpoints (for example
REQ-013). Default is the current working directory's API source root. - Severity floor: defaults to
info(report every finding). Caller may narrow towarningorcriticalto de-noise a release gate.
Operations¶
1. Resolve the target and discover project conventions¶
- If the target is a path, read it directly. If it is a requirement/feature identifier, resolve it to the owning handler files through the project's own layout (locate handler/router files and filter by the module the identifier names) — never assume a hard-coded path.
- Detect the web framework from the dependency manifest plus import/decorator signals:
| Signal | Framework | Handler / raise patterns |
|---|---|---|
fastapi, starlette; @router.<verb>, APIRouter |
FastAPI/Starlette | raise HTTPException(...), @app.exception_handler |
flask; @app.route, abort(...) |
Flask | abort(code), @app.errorhandler |
djangorestframework; APIView, @api_view |
Django REST | raise <APIException>, custom exception_handler |
express; app.<verb>(...), next(err) |
Express | res.status(c).json(...), error middleware |
@nestjs/common; @Controller, throw new HttpException |
NestJS | exception filters |
spring-web; @RestController, @ExceptionHandler |
Spring | @ControllerAdvice |
When the framework can't be determined, state the assumed pattern set in the report and continue.
- Discover the error-handling standard: look for a spec/NFR/ADR/doc that governs error responses (under
spec/,docs/, or ADRs). Record which document was used; absent one, fall back to the HTTP defaults below. - Discover the error-response contract (the canonical error-body model): a shared error-model module, a global exception handler, or an OpenAPI
components.schemaserror type. Record where it was found and treat its fields as the required shape. Absent one, the contract is "a single consistent shape across the surface," with RFC 9457 problem-details as the recommended baseline. - Record, per resolved input, whether the value came from an argument, discovery, or a default.
2. Check error-body uniformity and required fields¶
For every error path (each raise / abort / res.status(...) / thrown exception):
- Confirm the response body matches the declared contract shape; flag divergence.
- Confirm the contract's required fields are populated. A common contract has: a unique error/correlation id, a stable machine-readable error code, a human-readable message, optional field-level details, and request context (path, method, timestamp). Only check fields the project's contract actually declares.
- Confirm any declared unique error id is generated dynamically (per-occurrence, e.g. a fresh UUID), never a static constant — a constant id defeats log correlation.
3. Check HTTP status-code semantics¶
Compare each handler's status code against the situation it responds to:
| Situation | Expected status |
|---|---|
| Resource not found | 404 |
| Request / validation error | 400 or 422 (per framework convention) |
| Unauthenticated | 401 |
| Authenticated but forbidden | 403 |
| Domain / business-rule violation | 409 or 422 (per project convention) |
| Duplicate / conflicting state | 409 |
| Unhandled server error | 5xx (no internal detail in the body) |
Report each contradiction with a file:line attribution.
4. Run the leakage scan (security-relevant)¶
Search the error paths for internal detail reaching the response body. Anti-patterns:
```python
❌ stack trace / traceback in the response¶
raise HTTPException(detail=traceback.format_exc())
❌ raw driver / DB exception message exposed¶
raise HTTPException(detail=str(db_exception))
❌ rendered query or internal config in the response¶
raise HTTPException(detail=f"query failed: {sql_query}")
❌ bare framework default — no error handler, falls through and leaks¶
```
Each hit is critical, attributed to file:line, and flagged as a pointer into spec/project/code-security-audit/ (this skill is not a substitute for the whole-codebase security audit). Also report handlers whose error paths have no coverage at all — an unhandled path falls through to the framework default, which commonly leaks a stack trace.
Treat statically-undecidable error paths (status or body assembled from an unresolvable runtime variable) as dynamic, not statically verifiable — neither conforming nor diverging.
5. Render the report¶
```
API Error-Handling Conformance: ¶
Framework:
Summary¶
| Endpoints checked | Conforming | Diverging | Leakage hits | Dynamic-skipped |
|---|---|---|---|---|
Findings (critical → warning → info)¶
critical — ¶
(file:line):→ see spec/project/code-security-audit/
warning — ¶
(file:line):| static error id>
info — ¶
(file:line):
Verdict¶
- ✅ Conformant / ❌
findings ( critical, warning, info) ```
Cap each category at the first N entries and summarise the remainder as "… and {n} more". Sort findings by severity, then by file path, so the report diffs cleanly across runs.
Gotchas¶
- The error contract is project-specific, not the Kamerplanter NFR-006 model. Don't assume an
error_id/error_code/details[]shape — discover the project's actual contract first and check against that. Falling back to a fixed schema produces false "missing field" findings on a project that legitimately uses a different shape. - A clean happy-path test suite says nothing about error paths. The whole point of this check is the branch the tests don't cover; never infer conformance from "tests pass."
- A missing error handler is worse than a wrong one. A handler with no failure branch falls through to the framework default, which in most frameworks renders a stack trace in non-production mode and a generic 500 in production — report the absence as critical, not as "no findings."
- Status
422vs400is framework-convention, not a defect. FastAPI/Starlette uses422for request validation; many other stacks use400. Read the project's convention from existing handlers before flagging a status code. - This skill is static. It reads source; it does not call the running API. A leakage path guarded by a runtime debug flag is still reported (the flag can be misconfigured in production), but mark it as conditional.
Hard rules¶
- Never edit handler code, the error-contract module, or any other file. This skill reports; fixes are a follow-up step the developer owns.
- Always discover the project's own error contract and framework before checking; never hard-code one application's schema, requirement ID, or driver-exception strings.
- Always attribute every finding to a file:line so it is actionable.
- Always treat an internal-detail leakage hit as
criticaland point it atspec/project/code-security-audit/; this check surfaces leakage, it does not replace the whole-codebase security audit. - Always report a statically-undecidable error path as
dynamic, not statically verifiablerather than guessing conformance. - Never report findings below the requested severity floor; keep the report signal-heavy.
- Always state which standard document and error contract the check measured against (or the default it fell back to), so the scope is reproducible.
- When
spec/project/api-error-handling/and this skill disagree, the spec wins; this skill needs the update.
Why this is a skill, not an agent¶
Per spec/claude/skill-vs-agent/, this capability stays a skill rather than an agent:
- Invocation shape: it is a developer-invoked, slash-command conformance gate run as one step inside a larger flow (after implementing endpoints, pre-PR, pre-release) — the same shape as the sibling
quality-gateanddependency-auditskills, whose report flows back into the main conversation for triage. - Target argument: it takes a caller-supplied target (a path or a requirement identifier) and tailors the run to it, which is the skill-style argument-driven interaction rather than an autonomous fan-out.
- Counter-dimension: the read-only, summary-returning shape pulls toward an agent (context-window isolation), as it does for the adjacent
code-security-reviewerandi18n-completeness-checkeragents. That pull is real, but the check's output is short (a single severity-sorted report, not a verbose scan dump), so the isolation benefit is small, while the slash-command, targeted, in-the-main-flow usage outweighs it. If the leakage scan later grows context-heavy, splitting that phase into a helper agent (the hybrid patterndependency-audituses) is the documented escape hatch.