Install
$ agentstack add skill-paraskakis-skills-design-api ✓ 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 Used
- ✓ 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
Design API
Design an API end-to-end: requirements → stories → OpenAPI spec → lint → iterate. This is Emmanuel Paraskakis's method for designing APIs with LLMs.
All four foundation files (requirements, domain, standards, OpenAPI best practices) are always required. Stories are optional — if provided, they represent the human's design decisions and the skill skips straight to spec generation. If not, the skill generates stories first and pauses for review.
When to Use
Use when the user says /design-api or asks to design an API from requirements or stories.
Optional Dependencies
The core skill (stories + spec generation) works with no extra tools. For the full experience:
- RateMyOpenAPI API key — enables automated linting and scoring. Free signup at api.ratemyopenapi.com/docs. Set as
RMOA_API_KEYenv var. - Node.js — enables local Swagger UI preview via
npx http-server. - curl + python3 — used for the RMOA API call. Almost always pre-installed.
Without these, the skill still generates a complete OpenAPI spec — it just skips linting and local preview.
Inputs
The user must provide (or point to) these files. Files can be named anything — identify each file by its # Title heading or content, not by filename.
Always required:
- Requirements — ICP, needs, pain points, use cases, benefits. The "why" and "what." Look for a heading like
# Requirements,# Research, or similar. - Domain — Data objects, properties, enums, object relations. Source of truth for schemas. Look for a heading like
# Domain,# Domain Model, or similar. - API Standards — Style guide, naming, paths, security, error handling. Source of truth for conventions. Look for a heading like
# API Standards,# Style Guide, or similar. - OpenAPI Best Practices — Formatting rules, DRY principles, SDK compatibility, component structure. Source of truth for OpenAPI document quality. Look for a heading like
# OpenAPI Best Practices,# OAS Best Practices, or similar.
Optional (determines entry point):
- Stories — API user stories with methods, resources, parameters. The human's design decisions — resource nesting, endpoint grouping, parameter choices. Look for a heading like
# Stories,# User Stories, or similar. If provided → skip to Phase 2 (spec). If not → Phase 1 generates them for review.
If any required file is missing, ask the user to provide it before proceeding. If the user doesn't have their own files yet, offer to use the bundled examples in references/examples/ (a conference scheduling API) so they can try the skill end-to-end.
Bundled Examples
A complete set of example inputs is included in references/examples/:
conference-research.md— requirementsconference-domain.md— domain modelAPI-standards.md— API standardsOpenAPI-best-practices.md— OpenAPI best practices
Phase 1: Stories (skip if stories are provided)
Step 1: Read Inputs
Read requirements, domain, and standards completely. Understand:
- Who the API consumers are (from requirements)
- What entities and relationships exist (from domain)
- What conventions to follow (from standards)
Step 2: Identify Themes
Group use cases from the requirements into logical themes. Each theme maps to a cluster of related API operations. Do not pause here — proceed immediately to Step 3.
Exception: if the user explicitly asks for step-by-step review, present themes first and wait.
Step 3: Generate Stories (Default = Simple)
By default, generate simple stories — just enough to review the API shape and validate the design direction.
Simple Story Format (default)
For each story, output ONLY:
#### Story [Theme].[Number]: [Name]
**As a** [actor from requirements],
**I want** to [action],
**So that** [business value].
* **Method:** `GET` / `POST` / `PUT` / `PATCH` / `DELETE`
* **Resource:** `/path/{id}/subresource`
* **Parameters:**
* `paramName` (type, required/optional): Brief description.
Rules:
- Method and resource MUST follow the API standards (naming, nesting, pluralization)
- Parameters include path params, query params, and request body fields
- Mark each parameter as required or optional
- Use domain object names and property names exactly as defined
- Do NOT include response schemas, error codes, headers, or implementation details
Detailed Story Format (on request)
If the user asks for detailed stories (e.g., --detailed), add:
- Success response summary (status code + what's returned)
- Error responses (status codes + when they occur)
- Headers (rate limiting, pagination cursors)
- Notes on pagination approach
Step 4: Review Checkpoint
Present all stories in a single pass, then ask:
"Here are the stories. Any changes before I generate the spec, or good to go?"
- If the user has changes → incorporate them, re-present, and ask again.
- If the user says go → save the final stories to file immediately (in its own response, before any spec generation thinking), then proceed to Phase 2.
- If the user passes
--no-pause→ skip the checkpoint and proceed directly.
Important: Always save the final, reviewed stories to a file before generating the spec. The saved stories are the source of truth for Phase 2 — the spec must reflect any changes the user made during review.
Save stories FIRST, spec SECOND — in separate responses. When the user approves the stories, write them to file immediately in a standalone tool call. Do not batch the stories save with the spec generation — the spec requires significant reasoning time, and bundling them delays the stories save unnecessarily. Saving stories first shows progress to the user and gives them a checkpoint in case spec generation fails.
Save stories to a file the user specifies, or ask where they'd like them saved.
Write directly — do not check for or read existing files first. If the Write tool fails due to a name conflict, append a number and try again.
Phase 2: Spec
Step 5: Read All Inputs
Read stories, domain, standards, and OpenAPI best practices completely. Build a mental model of:
- Every endpoint from the stories (method + resource + parameters)
- Every data object and enum from the domain
- Every convention from the standards
- Every formatting rule from the best practices
Step 6: Generate the OpenAPI Document
Output a single valid JSON file. Default to OpenAPI 3.1.0 unless the user specifies a different version.
Structure
Follow this order in the document:
openapi— "3.1.0" (or the user-specified version)info— Title, description, version, contact, license (per best practices)servers— At least one server with descriptionsecurity— Global security scheme if standards require authtags— One tag per theme from the storiespaths— All endpoints from the storiescomponents— schemas, parameters, responses, headers, securitySchemes
Schema Generation Rules
- Every domain object becomes a component schema. Use the exact property names from the domain file.
- Every enum becomes a component schema. Don't inline enums.
- Collection responses get their own schema. e.g.,
InstrumentCollectionwithdataarray + pagination fields. - Error responses use RFC 9457 (Problem Details) unless standards specify otherwise.
- No inline schemas. Extract everything to
components/schemasand$refit. - Reusable parameters go in
components/parameters. Pagination params (cursor,limit), common filters — define once, reference everywhere. - Reusable response objects go in
components/responses. Error responses (400, 401, 404, 429, 500) — define once per unique shape. - Reusable headers go in
components/headers. Rate limit headers etc.
Content Rules
- Every operation, parameter, schema, property, response, and header MUST have a non-empty
description. - Every schema and parameter MUST have an
examplevalue. Use realistic examples from the domain file. - Every operation MUST have a
summaryandoperationId. - Error examples must match the error type (a 429 example should describe rate limiting, not "bad request").
- Use
$refaggressively. The document should be DRY.
Naming Rules
- operationIds: camelCase verbs matching the story action (e.g.,
searchInstruments,getInstrumentByFigi) - Tags: Match theme names from stories
- Schemas: PascalCase matching domain object names
- Parameters: camelCase matching domain property names
What NOT to Add
- Do not add endpoints that aren't in the stories
- Do not add properties that aren't in the domain
- Do not add security schemes that aren't in the standards
- If stories are simple (no error codes listed), still add standard error responses (400 for collection endpoints with filters, 404 for single-resource endpoints, 401 if auth is required, 429 rate limiting, 500 server error)
Step 7: Validate JSON
Before writing the file:
- Ensure the output is valid JSON (no trailing commas, no duplicate keys)
- Ensure every
$refpoints to a component that exists - Ensure every component is referenced at least once (no orphans)
- Ensure no
nullablekeyword is used (use"type": ["string", "null"]instead per 3.1.0)
Step 8: Save the Spec
Save as JSON to a location the user specifies, or ask where they'd like it saved.
Write directly — do not check for or read existing files first. If the Write tool fails due to a name conflict, append a number and try again.
Step 9: Lint with RateMyOpenAPI (optional)
This step requires an API key from RateMyOpenAPI. Sign up for a free account at api.ratemyopenapi.com/docs — your API key appears in the Authentication section once logged in. Set it as the environment variable RMOA_API_KEY (or any name you prefer — just update the curl command below to match).
If RMOA_API_KEY is not set, try sourcing the user's shell profile first (source ~/.zshrc, source ~/.bashrc, or equivalent) — the key may be defined there but not yet loaded in the current session. If still not set, tell the user: "No RMOA API key found. Skipping automated linting. You can get a free key at ratemyopenapi.com and set RMOA_API_KEY to enable it." Then skip to Step 10.
If RMOA_API_KEY is set, upload the spec and get scores + full issue list.
Write the response to a file first (don't pipe curl into python3 -c). RMOA responses can contain literal control characters inside JSON strings, which Python's strict JSON parser rejects when reading from stdin. Saving to a file and parsing with strict=False avoids both that and shell byte-mangling.
curl -s -X POST \
-H "Authorization: Bearer $RMOA_API_KEY" \
-F "apiFile=@[/absolute/path/to/spec.json]" \
-o /tmp/rmoa-response.json \
-w "HTTP:%{http_code} SIZE:%{size_download}\n" \
"https://api.ratemyopenapi.com/sync-report"
Check that the curl call returned HTTP:200 and a non-zero size before parsing. If not, RMOA itself is the problem — retry or skip linting.
Then parse the saved response:
python3 -c "
import json
data = json.loads(open('/tmp/rmoa-response.json').read(), strict=False)
r = data['results']['simpleReport']
issues = data['results']['fullReport']['issues']
print(f'Score: {r[\"score\"]} | Docs: {r[\"docsScore\"]} | Completeness: {r[\"completenessScore\"]} | SDK: {r[\"sdkGenerationScore\"]} | Security: {r[\"securityScore\"]}')
print(f'Report: https://ratemyopenapi.com/report/{r.get(\"reportId\", \"\")}')
print(f'Total issues: {len(issues)}')
for i, issue in enumerate(issues, 1):
path = ' > '.join(str(p) for p in issue.get('path', []))
print(f'{i}. [{\"ERROR\" if issue[\"severity\"]==0 else \"WARN\"}] {issue[\"code\"]}')
print(f' {issue[\"message\"]}')
print(f' Path: {path}')
print()
"
Use the issue list to drive all fixes below.
Interpreting Results
Scores four categories (0-100 each):
- Docs — Descriptions, summaries, examples
- Completeness — Required fields, response codes, schemas
- SDK Generation — operationIds, no inline schemas, proper refs
- Security — Auth schemes, 401/403 responses
Target: 100/100 on all categories.
Handling Issues
- Auto-fix anything that's clearly a formatting or completeness issue (missing description, missing example, missing error response).
- Ask the user about issues that involve design decisions. Common examples:
- No authentication / missing 401: If the API intentionally has no auth (e.g., a training exercise), tell the user RMOA flagged it and ask if they want to ignore it or add auth.
- Missing error codes: If RMOA expects 401/403 but the stories don't include auth, ask.
- Naming conventions: If RMOA flags a naming pattern that conflicts with the standards file, the standards file wins. Tell the user.
- Re-run after fixes. Keep iterating until either:
- Score is 100/100, or
- Remaining issues are intentional design choices the user has approved ignoring
Common Gotchas
- No-auth APIs score ~65 on Security. RMOA flags 8+ warnings about missing 401/403. If the API intentionally has no auth, these are expected — confirm with the user and move on.
nullablekeyword. OpenAPI 3.1.0 droppednullable. Use"type": ["string", "null"]instead. RMOA will flag this.- Missing descriptions/examples. The most common deductions. Every operation, parameter, schema property, and response needs both. Don't skip headers either.
- Orphaned components. RMOA flags schemas defined in
componentsbut never$ref'd. Remove them or wire them up. - Inline schemas. Any schema defined directly in a path (not via
$ref) hurts the SDK Generation score. Extract tocomponents/schemas.
Step 10: Report
Show the user:
## OpenAPI Spec Complete
**File:** [path]
**Score:** [Overall] / 100
- Docs: [score]
- Completeness: [score]
- SDK Generation: [score]
- Security: [score]
**Report:** [RMOA report URL]
**Issues ignored (by design):**
- [list any intentional deviations the user approved]
Then ask: "Want to preview this in Swagger UI?"
Requires Node.js (for npx http-server) and internet access (Swagger UI loads from unpkg.com CDN). If Node is not installed, tell the user: "Swagger UI preview requires Node.js. You can paste the spec into https://editor.swagger.io to preview it there instead." Then skip the preview.
If yes and Node is available, create a swagger-preview.html file in the same directory as the spec:
[API Name] — Swagger UI
fetch('./[spec-filename].json')
.then(r => r.json())
.then(spec => {
SwaggerUIBundle({
spec: spec,
dom_id: '#swagger-ui',
deepLinking: true,
presets: [SwaggerUIBundle.presets.apis],
layout: "BaseLayout"
});
});
Then serve it locally and open in the browser:
cd [spec-directory] && npx -y http-server . -p 8090 -c-1 &
sleep 2 && open http://localhost:8090/swagger-preview.html
Use port 8090 by default. If it's in use, try 8091, 8092, etc.
After the user has finished reviewing, ask: "Done previewing? I'll clean up the server and preview file."
If yes:
- Kill the http-server process:
lsof -ti:8090 | xargs kill 2>/dev/null - Delete the preview HTML file: remove
swagger-preview.htmlfrom the spec directory - Confirm: "Server stopped and preview file removed."
Key Principles
- Stories are for human review, not engineering specs. Keep them scannable. A PM should be able to look at the method + resource + parameters and say "yes, that's the right API" or "no, we're missing X."
- Follow the standards religiously. If the standards say camelCase, every parameter is camelCase. If they say pl
…
Source & license
This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.
- Author: paraskakis
- Source: paraskakis/skills
- License: MIT
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.