Install any skill in seconds. Free to start, no credit card required.
Get Started Free →Use when designing or writing REST API endpoints: wrap every response in the {data,error,meta} envelope and map errors to this project's exact status codes.
.claude/skills/house-api-conventions/SKILL.md| Model | Eval pass | Runs |
|---|---|---|
| gemini-3.6-flash | 100% | 43 |
| Model | Lift | Δ tokens | Δ turns | Cases | Verified |
|---|---|---|---|---|---|
| gemini-3.5-flashbest | +63% | — | 0% | 24 | 86d ago |
| gemini-3.6-flash | +45% | +130% | 0% | 22 | 54d ago |
| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-19 | ✗→✓ | ▲ Improved | — | — |
| case-03 | ✗→✓ | ▲ Improved | — | — |
| case-05 | ✗→✓ | ▲ Improved | — | — |
| case-11 | ✗→✓ | ▲ Improved | — | — |
| case-01 | ✗→✓ | ▲ Improved | — | — |
Enforce this project's REST conventions on every endpoint you design, write, or review: the {data, error, meta} response envelope, plural kebab-case versioned URLs, and the exact status-code map below. These are arbitrary house standards, not language defaults — apply them even when a more common convention exists.
Every response body is a JSON object with exactly three top-level keys, in this order:
data — the business payload on success; null on failure.error — null on success; on failure an object { "code", "message", "details" } with allthree keys present.
meta — pagination and metadata. List endpoints MUST populate it; single-resource and errorresponses may set it to an empty object {} or omit values, but the key stays.
No extra top-level keys (no bare arrays, no success: true, no status, no top-level items). Both data and error are always present together; one is always null.
meta for list endpointsAny endpoint returning a collection MUST include meta with exactly these keys:
page — the 1-based current page.limit — items per page; the default is 20 when the client does not specify.total — the total number of matching records across all pages./users, /orders, /products (never /user, never /getUsers)./order-items, /user-profiles (never orderItems,order_items, or orderitems).
/users/{id}/orders./orders?status=active&sort=-created_at&limit=20&page=1.
/api/v1/..../api/v2/...); the version lives in theURL path, not a header or query string. The old version keeps serving until retired.
| Code | Meaning | |------|---------| | 200 | success returning data | | 201 | resource created | | 400 | malformed request / bad or missing parameters / schema validation failure | | 401 | not authenticated (missing or invalid token) | | 403 | authenticated but not permitted | | 404 | resource does not exist | | 422 | business-logic failure (insufficient balance, duplicate signup, declined card) | | 500 | internal server error |
422 is the one most often guessed wrong: it covers any request that is well-formed and authorized but violates a domain rule. It is not 400 (that is for malformed input) and not 409.
Authorization: Bearer <jwt-token> unless it is explicitlypublic.
@public annotation in code/spec.Each shows the base model's natural default (BEFORE) and the conforming form (AFTER).
BEFORE
json[ { "id": 1 }, { "id": 2 } ]
AFTER
json{ "data": [ { "id": 1 }, { "id": 2 } ], "error": null, "meta": { "page": 1, "limit": 20, "total": 57 } }
BEFORE
json{ "id": 7, "name": "Widget", "price": 9.99 }
AFTER
json{ "data": { "id": 7, "name": "Widget", "price": 9.99 }, "error": null, "meta": {} }
BEFORE
json{ "message": "Invalid email" }
AFTER
json{ "data": null, "error": { "code": "INVALID_EMAIL", "message": "Email address is malformed", "details": {} }, "meta": {} }
BEFORE: GET /api/v1/orderItems AFTER: GET /api/v1/order-items
BEFORE: GET /api/v1/posts/12/comments/88/reactions (three levels) AFTER: GET /api/v1/comments/88/reactions (re-root at the nearest owner; or GET /api/v1/reactions?comment_id=88)
BEFORE: GET /api/v1/orders/active AFTER: GET /api/v1/orders?status=active&limit=20&page=1
BEFORE: GET /users AFTER: GET /api/v1/users
BEFORE: GET /api/v1/users with a changed shape, or GET /api/users?version=2 AFTER: GET /api/v2/users (v1 stays live)
BEFORE: withdraw-over-balance returns 400 Bad Request AFTER: returns 422 Unprocessable Entity with the error envelope
BEFORE: a successful POST /api/v1/orders returns 200 OK AFTER: returns 201 Created
BEFORE: X-Api-Key: abc123 or ?token=abc123 AFTER: Authorization: Bearer <jwt-token>
meta. It may be {}, but the key ispresent so every response has the same three-key shape.
data: [] with meta.total: 0 — not data: null. null means failure.ancestor as a query parameter (/reactions?comment_id=88), or stop nesting at the two-level owner. Never emit a third path segment.
400; awell-formed request that breaks a domain rule (duplicate email, insufficient funds, declined card) is 422.
naming) still applies; mark them @public.
403, missing-token is 401 — do not collapse both to 401./api/v1/. DON'T put the version in a header or query string.{data, error, meta}. DON'T return a bare array or add success/status.422 for domain-rule failures. DON'T use 400 or 409 for them.201 for creates. DON'T use 200 for a successful create.order-items). DON'T use camelCase or snake_case in the path.Authorization: Bearer <token>. DON'T use X-Api-Key or a ?token= query.{data, error, meta}.meta (or its page/limit/total keys) on collection endpoints.data: null for an empty list instead of data: [].400 for a duplicate-signup / insufficient-balance failure that should be 422.200 instead of 201 after creating a resource./orderItems, /order_items) instead of kebab-case./posts/{id}/comments/{id}/reactions)./orders/active) instead of ?status=active./api/v1 prefix.401 and 403 into one code./api/v1/.{data, error, meta}.data set, error: null. Failure: data: null, error: {code, message, details}.meta has page, limit (default 20), total.Authorization: Bearer <jwt-token> required unless @public.| Case | Status | Duration (ms) | Turns | Tokens | Tool calls | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Without | With | Δ | Without | With | Δ | Without | With | Δ | Without | With | Δ | ||
case-19 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-13 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-15 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-22 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-03 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-05 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-09 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-11 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-17 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-20 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-01 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-16 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-21 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-14 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-04 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-12 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-06 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-07 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-08 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-02 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-10 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-18 | pass→pass | — | — | — | — | — | — | — | — | — | — | — | — |
DecimalAI ran this skill against gemini-3.5-flash twice over the same eval suite — once with the skill loaded and once without — and compared the two runs case by case. 22 cases were attempted. The headline lift of +45 percentage points is the difference between those two pass rates over the 22 comparable cases.
The per-case answers from this run were removed by the retention sweep, so the case table below shows the verdicts without the text either arm produced. The counts above were recorded at the time and are unaffected. Answers are now kept for 180 days.
| Model | Method | Date | Lift |
|---|---|---|---|
| gemini-3.5-flash | verified | 6/27/2026 | +63% |
Other measured skills in the registry, with their headline benchmark lift.