api-design
Use this skill when designing APIs, choosing between REST/GraphQL/gRPC, writing OpenAPI specs, implementing pagination, versioning endpoints, or structuring request/response schemas. Triggers on API design, endpoint naming, HTTP methods, status codes, rate limiting, authentication schemes, HATEOAS, query parameters, and any task requiring API architecture decisions.
engineering apirestgraphqlgrpcopenapidesignWhat is api-design?
Use this skill when designing APIs, choosing between REST/GraphQL/gRPC, writing OpenAPI specs, implementing pagination, versioning endpoints, or structuring request/response schemas. Triggers on API design, endpoint naming, HTTP methods, status codes, rate limiting, authentication schemes, HATEOAS, query parameters, and any task requiring API architecture decisions.
api-design
api-design is a production-ready AI agent skill for claude-code, gemini-cli, openai-codex. Designing APIs, choosing between REST/GraphQL/gRPC, writing OpenAPI specs, implementing pagination, versioning endpoints, or structuring request/response schemas.
Quick Facts
| Field | Value |
|---|---|
| Category | engineering |
| Version | 0.1.0 |
| Platforms | claude-code, gemini-cli, openai-codex |
| License | MIT |
How to Install
- Make sure you have Node.js installed on your machine.
- Run the following command in your terminal:
npx skills add AbsolutelySkilled/AbsolutelySkilled --skill api-design- The api-design skill is now available in your AI coding agent (Claude Code, Gemini CLI, OpenAI Codex, etc.).
Overview
API design is the practice of defining the contract between a service and its consumers in a way that is consistent, predictable, and resilient to change. A well-designed API reduces integration friction, makes versioning safe, and communicates intent through naming and structure rather than documentation alone. This skill covers the three dominant paradigms - REST, GraphQL, and gRPC - along with OpenAPI specs, pagination strategies, versioning, error formats, and authentication patterns.
Tags
api rest graphql grpc openapi design
Platforms
- claude-code
- gemini-cli
- openai-codex
Related Skills
Pair api-design with these complementary skills:
Frequently Asked Questions
What is api-design?
Use this skill when designing APIs, choosing between REST/GraphQL/gRPC, writing OpenAPI specs, implementing pagination, versioning endpoints, or structuring request/response schemas. Triggers on API design, endpoint naming, HTTP methods, status codes, rate limiting, authentication schemes, HATEOAS, query parameters, and any task requiring API architecture decisions.
How do I install api-design?
Run npx skills add AbsolutelySkilled/AbsolutelySkilled --skill api-design in your terminal. The skill will be immediately available in your AI coding agent.
What AI agents support api-design?
This skill works with claude-code, gemini-cli, openai-codex. Install it once and use it across any supported AI coding agent.
Maintainers
Generated from AbsolutelySkilled
SKILL.md
API Design
API design is the practice of defining the contract between a service and its consumers in a way that is consistent, predictable, and resilient to change. A well-designed API reduces integration friction, makes versioning safe, and communicates intent through naming and structure rather than documentation alone. This skill covers the three dominant paradigms - REST, GraphQL, and gRPC - along with OpenAPI specs, pagination strategies, versioning, error formats, and authentication patterns.
When to use this skill
Trigger this skill when the user:
- Asks how to name, structure, or version API endpoints
- Needs to choose between REST, GraphQL, or gRPC for a new service
- Wants to write or review an OpenAPI / Swagger specification
- Asks about HTTP status codes and when to use each
- Needs to implement pagination (offset, cursor, keyset)
- Asks about authentication schemes (API key, OAuth2, JWT)
- Wants a consistent error response format across their API
- Needs to design request/response schemas or query parameters
Do NOT trigger this skill for:
- Internal function/method interfaces inside a single service - use clean-code or clean-architecture skills
- Database schema design unless it is driven by API contract requirements
Key principles
Consistency over cleverness - Every endpoint, field name, error shape, and status code should follow the same pattern throughout the API. Consumers should be able to predict behavior for an endpoint they have never used before.
Resource-oriented design - Model your API around nouns (resources), not verbs (actions).
POST /ordersis better thanPOST /createOrder. The HTTP method carries the verb.Proper HTTP semantics - Use the right method (
GETis safe + idempotent,PUT/DELETEare idempotent,POSTis neither). Use correct status codes:201for creation,204for empty success,400for client errors,404for not found,409for conflicts,429for rate limiting.Version from day one - Include a version in your URL or header before publishing.
v1in the path costs nothing; removing a breaking change from a production API costs everything.Design for the consumer - Shape responses around what the client needs, not around what the database returns. Clients should not have to join, filter, or transform data after receiving a response.
Core concepts
REST resources
REST treats everything as a resource identified by a URL. Resources are
manipulated through a uniform interface: GET, POST, PUT, PATCH, DELETE.
Collections live at /resources and individual items at /resources/{id}.
Sub-resources express ownership: /users/{id}/orders.
GraphQL schema
GraphQL exposes a single endpoint and lets clients declare exactly which fields they need. The schema is the contract - it defines types, queries, mutations, and subscriptions. Best for: UIs that need flexible data fetching, aggregating multiple back-end services, or reducing over/under-fetching.
gRPC + Protobuf
gRPC uses Protocol Buffers as its IDL and HTTP/2 as transport. It generates strongly-typed client/server stubs. Best for: internal service-to-service communication where performance, type safety, and streaming matter more than browser compatibility.
When to use which
| Need | REST | GraphQL | gRPC |
|---|---|---|---|
| Public/partner API | Best | Good | Avoid |
| Browser clients | Best | Best | Poor |
| Internal microservices | Good | Overkill | Best |
| Real-time / streaming | Polling/SSE | Subscriptions | Best |
| Flexible field selection | Sparse fieldsets | Best | N/A |
| Type-safe contracts | OpenAPI | Schema | Proto |
Common tasks
1. Design RESTful resource endpoints
Use lowercase, hyphen-separated plural nouns. Never use verbs in the path.
# Collections
GET /v1/articles - list
POST /v1/articles - create
# Single resource
GET /v1/articles/{id} - read
PUT /v1/articles/{id} - full replace
PATCH /v1/articles/{id} - partial update
DELETE /v1/articles/{id} - delete
# Sub-resources
GET /v1/users/{id}/orders - list orders for a user
# Actions that don't map to CRUD (use verb noun under resource)
POST /v1/orders/{id}/cancel
POST /v1/users/{id}/password-reset2. Write an OpenAPI 3.1 spec
Always use $ref to pull components out of paths for reuse. See
references/openapi-patterns.md for the full component library (security
schemes, reusable responses, discriminators, webhooks).
openapi: 3.1.0
info:
title: Articles API
version: 1.0.0
servers:
- url: https://api.example.com/v1
paths:
/articles:
get:
operationId: listArticles
summary: List articles
tags: [Articles]
parameters:
- { name: cursor, in: query, schema: { type: string } }
- { name: limit, in: query, schema: { type: integer, default: 20, maximum: 100 } }
responses:
'200':
description: Paginated list of articles
content:
application/json:
schema:
$ref: '#/components/schemas/ArticleListResponse'
'400': { $ref: '#/components/responses/BadRequest' }
post:
operationId: createArticle
summary: Create an article
tags: [Articles]
security:
- bearerAuth: []
requestBody:
required: true
content:
application/json:
schema:
type: object
required: [title]
properties:
title: { type: string, maxLength: 255 }
body: { type: string }
responses:
'201':
description: Article created
content:
application/json:
schema: { $ref: '#/components/schemas/Article' }
'422': { $ref: '#/components/responses/UnprocessableEntity' }
components:
schemas:
Article:
type: object
required: [id, title, status, createdAt]
properties:
id: { type: string, format: uuid }
title: { type: string, maxLength: 255 }
status: { type: string, enum: [draft, published, archived] }
createdAt: { type: string, format: date-time }
ArticleListResponse:
type: object
required: [data, pagination]
properties:
data:
type: array
items: { $ref: '#/components/schemas/Article' }
pagination:
type: object
properties:
nextCursor: { type: [string, "null"] }
hasMore: { type: boolean }
responses:
BadRequest:
description: Invalid request
content:
application/problem+json:
schema: { $ref: '#/components/schemas/ProblemDetails' }
UnprocessableEntity:
description: Validation failed
content:
application/problem+json:
schema: { $ref: '#/components/schemas/ProblemDetails' }
securitySchemes:
bearerAuth:
type: http
scheme: bearer
bearerFormat: JWT3. Implement cursor-based pagination
Cursor pagination is stable under concurrent writes; offset pagination is not.
interface PaginationParams {
cursor?: string;
limit?: number;
}
interface PaginatedResult<T> {
data: T[];
pagination: {
nextCursor: string | null;
hasMore: boolean;
};
}
async function listArticles(
params: PaginationParams
): Promise<PaginatedResult<Article>> {
const limit = Math.min(params.limit ?? 20, 100);
// Decode opaque cursor back to an internal value
const afterId = params.cursor
? Buffer.from(params.cursor, 'base64url').toString('utf8')
: null;
const rows = await db.article.findMany({
where: afterId ? { id: { gt: afterId } } : undefined,
orderBy: { id: 'asc' },
take: limit + 1, // fetch one extra to detect hasMore
});
const hasMore = rows.length > limit;
const data = hasMore ? rows.slice(0, limit) : rows;
const lastId = data.at(-1)?.id ?? null;
return {
data,
pagination: {
nextCursor: hasMore && lastId
? Buffer.from(lastId).toString('base64url')
: null,
hasMore,
},
};
}4. Implement API versioning
Recommendation: URL path versioning for public APIs (/v1/, /v2/), header
versioning for internal/partner APIs. Avoid query param versioning - it leaks into
caches and logs.
import { Router } from 'express';
// Option A: URL path (public APIs) - each version is a separate router
const v1 = Router(); v1.get('/articles', v1ArticlesHandler);
const v2 = Router(); v2.get('/articles', v2ArticlesHandler);
app.use('/v1', v1);
app.use('/v2', v2);
// Option B: Header versioning (internal/partner APIs)
// Request header: Api-Version: 2
function versionMiddleware(req: Request, res: Response, next: NextFunction) {
req.apiVersion = parseInt((req.headers['api-version'] as string) ?? '1', 10);
next();
}
// Option C: Content negotiation
// Accept: application/vnd.example.v2+json5. Design error response format (RFC 7807)
Always return machine-readable errors. Use application/problem+json content type.
interface ProblemDetails {
type: string; // URI identifying the error class
title: string; // Human-readable summary (stable per type)
status: number; // HTTP status code
detail?: string; // Human-readable explanation for this occurrence
instance?: string; // URI of the specific request (e.g. trace ID)
[key: string]: unknown; // Extension fields allowed
}
function problemResponse(
res: Response,
status: number,
type: string,
title: string,
detail?: string,
extensions?: Record<string, unknown>
) {
res.status(status).type('application/problem+json').json({
type: `https://api.example.com/errors/${type}`,
title,
status,
detail,
instance: `/requests/${res.locals.requestId}`,
...extensions,
} satisfies ProblemDetails);
}
// Usage
problemResponse(res, 422, 'validation-error', 'Request validation failed',
'The field "title" must not exceed 255 characters.',
{ fields: [{ field: 'title', message: 'Too long' }] }
);6. Design authentication
Three patterns, in order of complexity:
| Scheme | Header | Use when |
|---|---|---|
| API Key | X-API-Key: <key> |
Server-to-server, simple integrations |
| JWT Bearer | Authorization: Bearer <jwt> |
Stateless user sessions |
| OAuth2 | Authorization: Bearer <access_token> |
Delegated access with scopes |
import jwt from 'jsonwebtoken';
// JWT middleware - validates token, rejects with 401 on failure
function authMiddleware(req: Request, res: Response, next: NextFunction) {
const header = req.headers.authorization ?? '';
if (!header.startsWith('Bearer ')) {
return problemResponse(res, 401, 'unauthorized', 'Missing bearer token');
}
try {
req.user = jwt.verify(header.slice(7), process.env.JWT_SECRET!) as JwtPayload;
next();
} catch {
problemResponse(res, 401, 'invalid-token', 'Token is invalid or expired');
}
}
// Scope guard - rejects with 403 if required scope is absent
function requireScope(scope: string) {
return (req: Request, res: Response, next: NextFunction) => {
if (!req.user?.scopes?.includes(scope)) {
return problemResponse(res, 403, 'forbidden', `Scope "${scope}" required`);
}
next();
};
}
app.delete('/v1/articles/:id', authMiddleware, requireScope('articles:write'), handler);7. Choose REST vs GraphQL vs gRPC
| Factor | REST | GraphQL | gRPC |
|---|---|---|---|
| Browser support | Native | Native | Needs grpc-web |
| Learning curve | Low | Medium | Medium-High |
| Caching | HTTP cache works | Needs persisted queries | App-layer only |
| Type safety | Via OpenAPI | Schema-first | Proto-first |
| Over-fetching | Common | Eliminated | N/A |
| Streaming | SSE / chunked | Subscriptions | Bidirectional |
| Tooling maturity | Excellent | Good | Good |
| Best for | Public APIs | UI-driven APIs | Internal RPC |
Decision rule: Start with REST. Move to GraphQL when UI teams are blocked by over/under-fetching. Move to gRPC for high-throughput internal services where latency and type safety are critical.
Error handling reference
| Scenario | Status Code |
|---|---|
| Successful creation | 201 Created |
| Successful with no body | 204 No Content |
| Bad request / malformed JSON | 400 Bad Request |
| Missing or invalid auth token | 401 Unauthorized |
| Valid token, insufficient permission | 403 Forbidden |
| Resource not found | 404 Not Found |
| HTTP method not allowed | 405 Method Not Allowed |
| Conflict (duplicate, stale update) | 409 Conflict |
| Validation errors on input | 422 Unprocessable Entity |
| Rate limit exceeded | 429 Too Many Requests |
| Unexpected server error | 500 Internal Server Error |
| Upstream dependency unavailable | 503 Service Unavailable |
Gotchas
Offset pagination breaks under concurrent writes - Offset-based pagination (
?page=2&limit=20) produces incorrect results when rows are inserted or deleted between pages. Use cursor-based pagination (keyset/seek) for any dataset that changes while clients are paginating through it.Breaking changes in "minor" updates - Removing a field, changing a field's type, or narrowing an enum are breaking changes even if you don't bump the version. Consumers fail at runtime with no warning. Use the expand-contract pattern: add the new field, deprecate the old one, remove it only after all consumers have migrated.
422vs400confusion -400 Bad Requestis for malformed requests (unparseable JSON, wrong content type).422 Unprocessable Entityis for syntactically valid requests that fail business validation (email already taken, negative quantity). Returning400for validation errors prevents consumers from distinguishing parse errors from validation failures.URL versioning leaks into caches and logs - Query parameter versioning (
?version=2) gets cached incorrectly by HTTP caches that ignore query strings, and pollutes analytics logs. URL path versioning (/v2/) is cleanest for public APIs; header versioning is better for internal APIs that need per-consumer negotiation.DELETEreturning200with a body vs204- Many clients discard the body on204 No Contentresponses. If you need to return data from a delete operation, use200 OKwith a body. If nothing needs to be returned, use204. Mixing them creates client parsing bugs.
References
- RFC 7807 - Problem Details for HTTP APIs
- OpenAPI 3.1 Specification
- Google API Design Guide
- Microsoft REST API Guidelines
- GraphQL Best Practices
- gRPC Concepts
- Stripe API Reference - exemplary REST API design
references/openapi-patterns.md- reusable OpenAPI 3.1 component patterns
References
openapi-patterns.md
OpenAPI 3.1 Patterns Reference
Reusable patterns for writing production-quality OpenAPI 3.1 specifications. Load this reference when writing or reviewing any OpenAPI spec.
Reusable Components
The components section is the heart of a maintainable spec. Define everything
once, reference it everywhere with $ref.
Component types
components:
schemas: # Data shapes (request bodies, response objects, enums)
responses: # Reusable HTTP response definitions
parameters: # Reusable path/query/header parameters
requestBodies: # Reusable request body definitions
headers: # Reusable response headers
securitySchemes: # Auth method definitions
links: # Hypermedia links between operations
callbacks: # Webhook/callback definitions
pathItems: # Reusable full path items (OpenAPI 3.1 only)
examples: # Named example values for parameters and schemasSchema Patterns
Nullable fields (OpenAPI 3.1 style)
OpenAPI 3.1 aligns with JSON Schema. Use type as an array instead of the
deprecated nullable: true from 3.0.
# OpenAPI 3.1 - correct
deletedAt:
type: [string, "null"]
format: date-time
# OpenAPI 3.0 - deprecated, avoid in new specs
deletedAt:
type: string
format: date-time
nullable: trueRead-only and write-only fields
components:
schemas:
User:
type: object
properties:
id:
type: string
format: uuid
readOnly: true # present in responses, ignored in requests
password:
type: string
writeOnly: true # accepted in requests, never returned
email:
type: string
format: emailEnums with descriptions
components:
schemas:
OrderStatus:
type: string
enum: [pending, processing, shipped, delivered, cancelled]
description: |
- pending: order placed, awaiting payment
- processing: payment confirmed, preparing shipment
- shipped: handed to carrier
- delivered: confirmed delivery
- cancelled: order voidedShared pagination envelope
components:
schemas:
CursorPage:
type: object
required: [data, pagination]
properties:
pagination:
type: object
required: [hasMore]
properties:
nextCursor:
type: [string, "null"]
description: Opaque base64url cursor. Pass as ?cursor= to get the next page.
hasMore:
type: boolean
# Inline per-resource via allOf
ArticlePage:
allOf:
- $ref: '#/components/schemas/CursorPage'
- type: object
properties:
data:
type: array
items:
$ref: '#/components/schemas/Article'Discriminators
Use discriminators when a field value determines the concrete schema type. Useful for polymorphic request bodies (e.g. different payment methods).
components:
schemas:
PaymentMethod:
type: object
required: [type]
discriminator:
propertyName: type
mapping:
card: '#/components/schemas/CardPayment'
bank: '#/components/schemas/BankPayment'
wallet: '#/components/schemas/WalletPayment'
properties:
type:
type: string
CardPayment:
allOf:
- $ref: '#/components/schemas/PaymentMethod'
- type: object
required: [cardToken, expiryMonth, expiryYear]
properties:
cardToken:
type: string
expiryMonth:
type: integer
minimum: 1
maximum: 12
expiryYear:
type: integer
BankPayment:
allOf:
- $ref: '#/components/schemas/PaymentMethod'
- type: object
required: [accountNumber, routingNumber]
properties:
accountNumber:
type: string
routingNumber:
type: string
WalletPayment:
allOf:
- $ref: '#/components/schemas/PaymentMethod'
- type: object
required: [walletId]
properties:
walletId:
type: string
format: uuidSecurity Schemes
Define all auth methods in components/securitySchemes, then apply globally
or per-operation.
components:
securitySchemes:
# API Key - simplest, server-to-server
apiKeyHeader:
type: apiKey
in: header
name: X-API-Key
# HTTP Bearer (JWT)
bearerAuth:
type: http
scheme: bearer
bearerFormat: JWT
# OAuth2 with scopes
oauth2:
type: oauth2
flows:
authorizationCode:
authorizationUrl: https://auth.example.com/oauth/authorize
tokenUrl: https://auth.example.com/oauth/token
scopes:
articles:read: Read articles
articles:write: Create and update articles
admin: Full administrative access
clientCredentials:
tokenUrl: https://auth.example.com/oauth/token
scopes:
service:read: Read-only service account access
service:write: Write service account access
# OpenID Connect
openIdConnect:
type: openIdConnect
openIdConnectUrl: https://auth.example.com/.well-known/openid-configurationApplying security globally vs per-operation
# Global default - all operations require bearer auth
security:
- bearerAuth: []
paths:
/v1/articles:
get:
# Override: this endpoint is public (no auth)
security: []
post:
# Override: requires a specific OAuth2 scope
security:
- oauth2: [articles:write]Webhooks
OpenAPI 3.1 adds first-class webhook support via the webhooks field.
webhooks:
orderShipped:
post:
summary: Triggered when an order ships
requestBody:
required: true
content:
application/json:
schema:
type: object
required: [event, data]
properties:
event:
type: string
enum: [order.shipped]
data:
$ref: '#/components/schemas/Order'
timestamp:
type: string
format: date-time
responses:
'200':
description: Webhook received successfully
'410':
description: Webhook endpoint no longer active - stop sendingReusable Parameters
components:
parameters:
ResourceId:
name: id
in: path
required: true
schema:
type: string
format: uuid
PageCursor:
name: cursor
in: query
description: Opaque pagination cursor from a previous response
schema:
type: string
PageLimit:
name: limit
in: query
description: Number of items per page
schema:
type: integer
minimum: 1
maximum: 100
default: 20
FilterCreatedAfter:
name: createdAfter
in: query
description: Return only items created after this ISO 8601 timestamp
schema:
type: string
format: date-time
SortOrder:
name: order
in: query
schema:
type: string
enum: [asc, desc]
default: descReusable Responses
components:
responses:
NoContent:
description: Operation succeeded with no response body
BadRequest:
description: Invalid request parameters or malformed body
content:
application/problem+json:
schema:
$ref: '#/components/schemas/ProblemDetails'
example:
type: https://api.example.com/errors/bad-request
title: Bad Request
status: 400
detail: The field "limit" must be between 1 and 100.
Unauthorized:
description: Missing or invalid authentication credentials
headers:
WWW-Authenticate:
schema:
type: string
example: Bearer realm="api.example.com"
content:
application/problem+json:
schema:
$ref: '#/components/schemas/ProblemDetails'
Forbidden:
description: Authenticated but not authorized for this operation
content:
application/problem+json:
schema:
$ref: '#/components/schemas/ProblemDetails'
NotFound:
description: Resource does not exist
content:
application/problem+json:
schema:
$ref: '#/components/schemas/ProblemDetails'
Conflict:
description: Resource already exists or update would cause a conflict
content:
application/problem+json:
schema:
$ref: '#/components/schemas/ProblemDetails'
UnprocessableEntity:
description: Request is well-formed but fails business validation
content:
application/problem+json:
schema:
allOf:
- $ref: '#/components/schemas/ProblemDetails'
- type: object
properties:
errors:
type: array
items:
type: object
required: [field, message]
properties:
field:
type: string
message:
type: string
TooManyRequests:
description: Rate limit exceeded
headers:
Retry-After:
description: Seconds to wait before retrying
schema:
type: integer
X-RateLimit-Limit:
schema:
type: integer
X-RateLimit-Remaining:
schema:
type: integer
X-RateLimit-Reset:
description: Unix timestamp when the rate limit resets
schema:
type: integer
content:
application/problem+json:
schema:
$ref: '#/components/schemas/ProblemDetails'
InternalServerError:
description: Unexpected server error
content:
application/problem+json:
schema:
$ref: '#/components/schemas/ProblemDetails'RFC 7807 Problem Details Schema
Include this in every API spec that returns errors.
components:
schemas:
ProblemDetails:
type: object
required: [type, title, status]
properties:
type:
type: string
format: uri
description: >
A URI reference that identifies the problem type.
Dereferencing this URI should provide human-readable documentation.
example: https://api.example.com/errors/validation-error
title:
type: string
description: >
A short, human-readable summary of the problem type.
Stable across occurrences of the same problem type.
example: Validation Error
status:
type: integer
description: HTTP status code
example: 422
detail:
type: string
description: >
Human-readable explanation specific to this occurrence of the problem.
example: The field "email" must be a valid email address.
instance:
type: string
format: uri
description: >
A URI reference identifying the specific occurrence (e.g. trace ID path).
example: /requests/01HXYZ1234ABCD
additionalProperties: true # extension fields allowed per RFC 7807Links (Hypermedia)
Links describe relationships between operations, enabling HATEOAS-style discoverability without embedding URLs in every response.
paths:
/v1/orders:
post:
operationId: createOrder
responses:
'201':
description: Order created
content:
application/json:
schema:
$ref: '#/components/schemas/Order'
links:
GetOrder:
operationId: getOrder
parameters:
id: $response.body#/id
description: Use the returned order ID to fetch full details
CancelOrder:
operationId: cancelOrder
parameters:
id: $response.body#/idSpec Organization Tips
- Keep specs under 500 lines per file; use
$refto external files for large APIs - Use
operationIdon every operation - tooling (SDKs, mocks) derives names from it - Tag operations by resource (
tags: [Orders]) for grouped documentation - Add
x-extension fields for tooling metadata without polluting the spec - Validate specs with
redocly lintorspectral lintin CI - Generate server stubs and client SDKs from the spec with
openapi-generator
Frequently Asked Questions
What is api-design?
Use this skill when designing APIs, choosing between REST/GraphQL/gRPC, writing OpenAPI specs, implementing pagination, versioning endpoints, or structuring request/response schemas. Triggers on API design, endpoint naming, HTTP methods, status codes, rate limiting, authentication schemes, HATEOAS, query parameters, and any task requiring API architecture decisions.
How do I install api-design?
Run npx skills add AbsolutelySkilled/AbsolutelySkilled --skill api-design in your terminal. The skill will be immediately available in your AI coding agent.
What AI agents support api-design?
api-design works with claude-code, gemini-cli, openai-codex. Install it once and use it across any supported AI coding agent.