Install
$ agentstack add skill-kwhorne-elyra-skills-api-design ✓ scanned · ✓ verified — works with Claude Code, Cursor, and more.
Security review
✓ PassedNo issues found. Passed automated security review. · v0.1.0 How review works →
- ✓ Prompt-injection patterns
- ✓ Secret / credential exfiltration
- ✓ Dangerous shell & filesystem operations
- ✓ Untrusted network calls
- ✓ Known-malicious package signatures
What it can access
- ✓ Network access No
- ✓ Filesystem access No
- ✓ Shell / process execution No
- ✓ Environment & secrets No
- ✓ Dynamic code execution No
From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.
About
API Design
Principles for HTTP/REST APIs that are predictable, debuggable, and pleasant to consume. Opinionated where the field has converged; flexible where reasonable people disagree.
When to use
- "Design an endpoint for X"
- "How should this API look?"
- "Review this API"
- "Add an endpoint to …"
Procedure
- Anchor to existing patterns. Read 2–3 existing endpoints in the same codebase. New endpoints should look like neighbors.
- Model the resource, not the action. Most actions become CRUD on a resource. If they don't (e.g. "publish"), see "Actions on resources" below.
- Sketch the contract before writing code: path, method, request body, response body, status codes, errors.
- Check it against the consistency checklist below.
- Document it — even one paragraph in the PR is better than nothing.
Resource naming
- Use plural nouns for collections:
/users,/orders,/articles - Use IDs, not slugs, for identity:
/users/42(slugs are for SEO, not for APIs) - Nest to express ownership, max 2 levels deep:
- ✅
/users/42/orders - ❌
/users/42/orders/7/items/3/shipments - Use kebab-case in paths:
/account-settings, not/accountSettingsor/account_settings - Lowercase everything in paths
- No verbs in paths — the HTTP method is the verb
Methods
| Method | Purpose | Idempotent | Safe | |---|---|---|---| | GET | Retrieve | yes | yes | | POST | Create, or non-idempotent action | no | no | | PUT | Replace (full update) | yes | no | | PATCH | Partial update | no\* | no | | DELETE | Remove | yes | no |
\ PATCH can* be idempotent if you design it that way; don't rely on it.
Status codes
Pick from this short list. Resist the urge to be clever.
| Code | Meaning | Use for | |---|---|---| | 200 OK | Success with body | GET, PATCH, PUT returning resource | | 201 Created | Resource created | POST that creates a resource (return it + Location header) | | 202 Accepted | Accepted for async processing | Long-running jobs | | 204 No Content | Success, no body | DELETE, PUT without return | | 400 Bad Request | Malformed request | Bad JSON, missing required fields | | 401 Unauthorized | Not authenticated | Missing/invalid credentials | | 403 Forbidden | Authenticated but not allowed | Permission denied | | 404 Not Found | Resource doesn't exist | Unknown ID | | 409 Conflict | State conflict | Duplicate, version mismatch | | 422 Unprocessable Entity | Validation failed | Well-formed but semantically invalid | | 429 Too Many Requests | Rate-limited | Include Retry-After | | 500 Internal Server Error | We broke it | Unexpected server fault | | 503 Service Unavailable | Temporarily down | Maintenance, dependency outage |
Don't invent custom codes. Don't return 200 with {"error": "..."}.
Errors
Use a single, documented error shape across the entire API:
{
"error": {
"code": "validation_failed",
"message": "Request failed validation.",
"details": [
{ "field": "email", "code": "invalid_format" },
{ "field": "age", "code": "must_be_positive" }
],
"request_id": "req_01HX…"
}
}
- Stable, machine-readable
code(snake_case, never localized) - Human-readable
message(English; localize on the client) detailsfor per-field validationrequest_idfor support / log correlation
Consider RFC 9457 Problem Details if the consumer already uses it.
Pagination
Pick one and stick with it across the API.
- Cursor-based (preferred for large/changing data):
- Request:
?limit=50&cursor=eyJpZCI6... - Response:
{ "data": [...], "next_cursor": "...", "has_more": true } - Page-based (fine for small/static data):
- Request:
?page=2&per_page=50 - Response:
{ "data": [...], "page": 2, "per_page": 50, "total": 1234 }
Always cap limit / per_page server-side. Document the max.
Filtering, sorting, sparse fields
- Filter:
?status=active&created_after=2025-01-01 - Sort:
?sort=-created_at,name(leading-= descending) - Sparse fields:
?fields=id,email,created_at
Versioning
Pick one strategy and commit:
- URL:
/v1/users— most visible, easiest to route - Header:
Accept: application/vnd.example.v1+json— cleaner URLs, harder to debug - Avoid query-string versioning
Only bump the major version for breaking changes. Additive changes (new fields, new endpoints) don't require a new version.
Actions on resources
When CRUD doesn't fit (publish, archive, retry, refund), prefer:
- State transitions via PATCH:
PATCH /articles/42 {"status": "published"}— best if you have multiple states - Sub-resources for explicit actions:
POST /articles/42/publish— pragmatic when the action has side effects or its own payload
Don't go full RPC: POST /publishArticle?id=42. You're using HTTP; use it.
Consistency checklist
- [ ] Resource name is a plural noun
- [ ] Path uses kebab-case and lowercase
- [ ] HTTP method matches semantics (idempotency!)
- [ ] Status codes from the short list
- [ ] Error response matches the API-wide error shape
- [ ]
POSTthat creates returns201+Locationheader - [ ] List endpoints paginate and cap
limit - [ ] Timestamps are ISO 8601 with timezone (
2026-05-22T14:30:00Z) - [ ] IDs are opaque strings (not auto-increment ints leaked) when possible
- [ ] No sensitive data in URLs (use headers / body)
- [ ] Versioning strategy followed
- [ ] Authentication / authorization checks documented per endpoint
Anti-patterns
- ❌
GET /getUser?id=42— verb in path, query param for identity - ❌
POST /users/delete/42— DELETE exists for a reason - ❌ Returning
200with{"success": false} - ❌ Inconsistent casing:
userIdin one response,user_idin another - ❌ Auto-increment IDs in URLs leaking row counts to the public
- ❌ Pagination with no upper bound on
limit - ❌ Mixing snake_case and camelCase in the same payload
- ❌ "Soft 200" — returning HTTP success but logical failure
Source & license
This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.
- Author: kwhorne
- Source: kwhorne/elyra-skills
- License: MIT
- Homepage: https://elyracode.com
Install and usage instructions live in the source repository linked above.
Reviews
No reviews yet — be the first.
Write a review
Versions
- v0.1.0 Imported from the upstream source.