Api design
Portable engineering policies for coding agents — git, testing, logging, and language conventions written once and referenced everywhere
npx -y skills add andr-ca/agentharness --skill api-designAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
2 things to look at
- 25 days oldThe repository was created 25 days ago. New is not bad, but a brand new repository carrying a familiar-sounding name is the shape a typosquat arrives in, and there has been no time for anyone else to find a problem with it.
- 1 stars1 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
Use when designing, reviewing, or evolving a REST or GraphQL API — resource naming, HTTP status codes, versioning strategy, error response shapes, pagination, and authentication patterns.
SKILL.md
6.0 KB, as published. Nobody here has run it
API Design
Design choices for HTTP APIs (REST-style and GraphQL). The goal is an API that is consistent, predictable, and easy to evolve without breaking callers.
Reference: Google API Design Guide, JSON:API, RFC 9457 (Problem Details for HTTP APIs).
Resource naming (REST)
Use nouns, not verbs. Collections are plural.
GET /users → list users
POST /users → create a user
GET /users/{id} → get one user
PATCH /users/{id} → partial update
PUT /users/{id} → full replace
DELETE /users/{id} → delete
GET /users/{id}/posts → posts belonging to a user
POST /users/{id}/posts → create a post for a user
Actions that don't map cleanly to CRUD go on a sub-resource with a verb:
POST /users/{id}/activate
POST /payments/{id}/refund
Naming rules:
kebab-casefor multi-word path segments (/user-profiles, not/userProfilesor/user_profiles)camelCasefor JSON field names (consistent with most JS/TS clients)- IDs in path, filters/pagination in query string
HTTP status codes — use them correctly
| Code | Meaning | Use for |
|---|---|---|
| 200 | OK | Successful GET, PATCH, PUT |
| 201 | Created | Successful POST that creates a resource |
| 204 | No Content | Successful DELETE or action with no response body |
| 400 | Bad Request | Client sent invalid data (validation failure) |
| 401 | Unauthorized | Not authenticated (no or invalid token) |
| 403 | Forbidden | Authenticated but not permitted for this resource |
| 404 | Not Found | Resource doesn't exist (or is hidden for auth reasons) |
| 409 | Conflict | Duplicate creation, optimistic lock failure |
| 422 | Unprocessable Entity | Syntactically valid but semantically invalid input |
| 429 | Too Many Requests | Rate limit exceeded |
| 500 | Internal Server Error | Unexpected server failure (never expose details) |
Common mistakes:
200with{"success": false}in the body — use a 4xx status code404for auth failures — use401/403(don't leak resource existence only when that itself is sensitive)500for business logic failures — use a 4xx
Error response shape
{
"type": "https://api.example.com/errors/validation-failed",
"title": "Validation Failed",
"status": 400,
"detail": "The request body contains invalid fields.",
"instance": "/requests/abc-123",
"errors": [
{ "field": "email", "message": "must be a valid email address" },
{ "field": "age", "message": "must be a positive integer" }
]
}
type— a stable URI identifying the error class (not a URL that must resolve, but it should if possible)title— human-readable, stable label for the error classdetail— human-readable description of this specific error instanceerrors— field-level validation failures (your extension, not in the spec)
Never include: stack traces, internal IDs, raw database errors.
Versioning
Prefer URL versioning for public APIs: /v1/users. It's explicit and easy
to route at the infrastructure level.
/v1/users → current stable version
/v2/users → new version (breaking changes)
Rules:
- Additive changes (new fields, new endpoints) are non-breaking — deploy without a version bump.
- Breaking changes (removed fields, changed types, removed endpoints) require a new version.
- Maintain at least one previous major version for a published deprecation window (typically 6–12 months).
- Never break a version in place. Even a "fix" that changes a field type is a breaking change.
Pagination
Use cursor-based pagination for large or frequently-updated collections. Offset pagination is simpler but breaks under concurrent writes.
// Request
GET /users?cursor=eyJpZCI6MTAwfQ&limit=20
// Response
{
"data": [...],
"pagination": {
"next_cursor": "eyJpZCI6MTIwfQ",
"has_more": true
}
}
limithas a server-enforced maximum (e.g. 100); clients can request smaller pages but not larger.next_cursoris opaque — clients must not parse it.- Return
has_more: falseand omitnext_cursoron the last page.
Authentication
- Bearer tokens in the
Authorizationheader:Authorization: Bearer <token>— never in the URL. - API keys in a custom header (
X-Api-Key) or Bearer — never in query params (they appear in server logs). - Return
401for missing/invalid credentials;403for valid credentials that lack permission for the specific resource.
GraphQL-specific notes
- Use
inputtypes for mutations — don't accept scalar arguments directly on mutations that take more than one field. - Implement cursor-based connection pagination for lists (Relay connection spec).
- Don't expose internal database IDs directly — use opaque, base64-encoded
global IDs (
User:123→VXNlcjoxMjM=). - Rate-limit by query complexity or depth, not just request count.
Review checklist
Before shipping a new API endpoint or changing an existing one:
- Nouns used for resource names; collections plural;
kebab-casepaths - Correct HTTP method and status codes for each operation
- Error responses follow RFC 9457 shape; no stack traces in responses
- Additive changes do not bump the version; breaking changes do
- Pagination implemented for any list endpoint that could return > 100 items
- Authentication tokens sent in headers only (not URL or body)
- Sensitive fields not exposed (passwords, internal IDs, PII beyond what's needed)
- New endpoints documented (OpenAPI / README)