本文へ移動
cccskills
無料GitHub で公開日本語紹介

api-design

REST APIのURL、応答形式、エラー処理を一貫したルールで設計・見直しします。ページ分割や認証、利用回数の制限、バージョン管理まで整理するスキルです。

原文原文の説明を見る

Patrones de diseño REST API incluyendo nomenclatura de recursos, códigos de estado, paginación, filtrado, respuestas de error, versionado y rate limiting para APIs de producción.

インストール方法を見る

こんなときに便利

  • 新しいAPIの窓口を設計したいとき
  • 既存APIの仕様を見直したいとき
  • ページ分割や絞り込みを追加したいとき
  • APIのエラー応答を統一したいとき
  • 公開APIのバージョン管理を考えたいとき

日本語での紹介

できること

REST APIの設計ルールを整理し、新しいAPIの作成や既存仕様の見直しを支援します。URLの命名、HTTPメソッド、処理結果を示すステータスコード、成功時とエラー時の応答形式を扱います。一覧を分割して取得するページネーション、絞り込み、並べ替え、認証と権限確認、リクエスト回数の制限、バージョン管理も対象です。

こんなときに便利

公開APIや連携先向けAPIの仕様を決めるとき、既存のAPIで命名やエラー形式がばらついているときに向いています。ページ番号方式と、取得位置を示すカーソル方式の違いを比較し、用途に合う一覧取得の設計を考える際にも役立ちます。

使い方の例

  • 「ユーザー管理APIのURLと応答形式を設計して」
  • 「既存APIのエラー応答とHTTPステータスを見直して」
  • 「公開APIのページネーションとバージョン移行方針を整理して」

注意点

掲載された実装例はNext.jsとZod、Django REST Framework、Goのnet/httpを使っています。例を採用する場合は、それぞれの環境に合わせた実装が必要です。APIの応答にはスタックトレースやSQLエラーなどの内部情報を含めない方針です。

この紹介文は、公開されている SKILL.md をもとに AI(Claude Haiku)が作成しました。正確な仕様は下の原文を確認してください。

含まれるファイル(1)

  • SKILL.md13.7 KB

SKILL.md(原文)

インストールする前に、エージェントに与えられる指示の中身を確認できます。

Patrones de Diseño de API

Convenciones y buenas prácticas para diseñar APIs REST consistentes y amigables para desarrolladores.

Cuándo Activar

  • Diseñar nuevos endpoints de API
  • Revisar contratos de API existentes
  • Agregar paginación, filtrado u ordenamiento
  • Implementar manejo de errores para APIs
  • Planificar la estrategia de versionado de API
  • Construir APIs públicas o para partners

Diseño de Recursos

Estructura de URL

# Los recursos son sustantivos, plural, minúsculas, kebab-case
GET    /api/v1/users
GET    /api/v1/users/:id
POST   /api/v1/users
PUT    /api/v1/users/:id
PATCH  /api/v1/users/:id
DELETE /api/v1/users/:id

# Sub-recursos para relaciones
GET    /api/v1/users/:id/orders
POST   /api/v1/users/:id/orders

# Acciones que no mapean a CRUD (usar verbos con moderación)
POST   /api/v1/orders/:id/cancel
POST   /api/v1/auth/login
POST   /api/v1/auth/refresh

Reglas de Nomenclatura

# BIEN
/api/v1/team-members          # kebab-case para recursos de varias palabras
/api/v1/orders?status=active  # query params para filtrado
/api/v1/users/123/orders      # recursos anidados para pertenencia

# MAL
/api/v1/getUsers              # verbo en la URL
/api/v1/user                  # singular (usar plural)
/api/v1/team_members          # snake_case en URLs
/api/v1/users/123/getOrders   # verbo en recurso anidado

Métodos HTTP y Códigos de Estado

Semántica de Métodos

MétodoIdempotenteSeguroUsar Para
GETSíSíRecuperar recursos
POSTNoNoCrear recursos, disparar acciones
PUTSíNoReemplazo completo de un recurso
PATCHNo*NoActualización parcial de un recurso
DELETESíNoEliminar un recurso

*PATCH puede hacerse idempotente con la implementación adecuada

Referencia de Códigos de Estado

# Éxito
200 OK                    — GET, PUT, PATCH (con cuerpo de respuesta)
201 Created               — POST (incluir header Location)
204 No Content            — DELETE, PUT (sin cuerpo de respuesta)

# Errores de Cliente
400 Bad Request           — Fallo de validación, JSON malformado
401 Unauthorized          — Autenticación ausente o inválida
403 Forbidden             — Autenticado pero no autorizado
404 Not Found             — El recurso no existe
409 Conflict              — Entrada duplicada, conflicto de estado
422 Unprocessable Entity  — Semánticamente inválido (JSON válido, datos incorrectos)
429 Too Many Requests     — Límite de rate excedido

# Errores de Servidor
500 Internal Server Error — Fallo inesperado (nunca exponer detalles)
502 Bad Gateway           — Falló el servicio upstream
503 Service Unavailable   — Sobrecarga temporal, incluir Retry-After

Errores Comunes

# MAL: 200 para todo
{ "status": 200, "success": false, "error": "Not found" }

# BIEN: Usar códigos de estado HTTP semánticamente
HTTP/1.1 404 Not Found
{ "error": { "code": "not_found", "message": "User not found" } }

# MAL: 500 para errores de validación
# BIEN: 400 o 422 con detalles por campo

# MAL: 200 para recursos creados
# BIEN: 201 con header Location
HTTP/1.1 201 Created
Location: /api/v1/users/abc-123

Formato de Respuesta

Respuesta Exitosa

{
  "data": {
    "id": "abc-123",
    "email": "alice@example.com",
    "name": "Alice",
    "created_at": "2025-01-15T10:30:00Z"
  }
}

Respuesta de Colección (con Paginación)

{
  "data": [
    { "id": "abc-123", "name": "Alice" },
    { "id": "def-456", "name": "Bob" }
  ],
  "meta": {
    "total": 142,
    "page": 1,
    "per_page": 20,
    "total_pages": 8
  },
  "links": {
    "self": "/api/v1/users?page=1&per_page=20",
    "next": "/api/v1/users?page=2&per_page=20",
    "last": "/api/v1/users?page=8&per_page=20"
  }
}

Respuesta de Error

{
  "error": {
    "code": "validation_error",
    "message": "Request validation failed",
    "details": [
      {
        "field": "email",
        "message": "Must be a valid email address",
        "code": "invalid_format"
      },
      {
        "field": "age",
        "message": "Must be between 0 and 150",
        "code": "out_of_range"
      }
    ]
  }
}

Variantes de Envelope de Respuesta

// Opción A: Envelope con wrapper data (recomendado para APIs públicas)
interface ApiResponse<T> {
  data: T;
  meta?: PaginationMeta;
  links?: PaginationLinks;
}

interface ApiError {
  error: {
    code: string;
    message: string;
    details?: FieldError[];
  };
}

// Opción B: Respuesta plana (más simple, común para APIs internas)
// Éxito: retornar el recurso directamente
// Error: retornar objeto de error
// Distinguir por código de estado HTTP

Paginación

Basada en Offset (Simple)

GET /api/v1/users?page=2&per_page=20

# Implementación
SELECT * FROM users
ORDER BY created_at DESC
LIMIT 20 OFFSET 20;

Pros: Fácil de implementar, soporta "saltar a página N" Contras: Lento en offsets grandes (OFFSET 100000), inconsistente con inserciones concurrentes

Basada en Cursor (Escalable)

GET /api/v1/users?cursor=eyJpZCI6MTIzfQ&limit=20

# Implementación
SELECT * FROM users
WHERE id > :cursor_id
ORDER BY id ASC
LIMIT 21;  -- obtener uno extra para determinar has_next
{
  "data": [...],
  "meta": {
    "has_next": true,
    "next_cursor": "eyJpZCI6MTQzfQ"
  }
}

Pros: Rendimiento consistente independientemente de la posición, estable con inserciones concurrentes Contras: No se puede saltar a una página arbitraria, el cursor es opaco

Cuándo Usar Cuál

Caso de UsoTipo de Paginación
Dashboards administrativos, datasets pequeños (<10K)Offset
Scroll infinito, feeds, datasets grandesCursor
APIs públicasCursor (por defecto) con offset (opcional)
Resultados de búsquedaOffset (los usuarios esperan números de página)

Filtrado, Ordenamiento y Búsqueda

Filtrado

# Igualdad simple
GET /api/v1/orders?status=active&customer_id=abc-123

# Operadores de comparación (usar notación de corchetes)
GET /api/v1/products?price[gte]=10&price[lte]=100
GET /api/v1/orders?created_at[after]=2025-01-01

# Múltiples valores (separados por coma)
GET /api/v1/products?category=electronics,clothing

# Campos anidados (notación de punto)
GET /api/v1/orders?customer.country=US

Ordenamiento

# Campo único (prefijo - para descendente)
GET /api/v1/products?sort=-created_at

# Múltiples campos (separados por coma)
GET /api/v1/products?sort=-featured,price,-created_at

Búsqueda de Texto Completo

# Parámetro de consulta de búsqueda
GET /api/v1/products?q=wireless+headphones

# Búsqueda específica de campo
GET /api/v1/users?email=alice

Conjuntos de Campos Reducidos (Sparse Fieldsets)

# Retornar solo los campos especificados (reduce el payload)
GET /api/v1/users?fields=id,name,email
GET /api/v1/orders?fields=id,total,status&include=customer.name

Autenticación y Autorización

Autenticación Basada en Token

# Bearer token en el header Authorization
GET /api/v1/users
Authorization: Bearer eyJhbGciOiJIUzI1NiIs...

# API key (para servidor a servidor)
GET /api/v1/data
X-API-Key: sk_live_abc123

Patrones de Autorización

// A nivel de recurso: verificar propiedad
app.get("/api/v1/orders/:id", async (req, res) => {
  const order = await Order.findById(req.params.id);
  if (!order) return res.status(404).json({ error: { code: "not_found" } });
  if (order.userId !== req.user.id) return res.status(403).json({ error: { code: "forbidden" } });
  return res.json({ data: order });
});

// Basada en roles: verificar permisos
app.delete("/api/v1/users/:id", requireRole("admin"), async (req, res) => {
  await User.delete(req.params.id);
  return res.status(204).send();
});

Rate Limiting

Headers

HTTP/1.1 200 OK
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 95
X-RateLimit-Reset: 1640000000

# Cuando se excede
HTTP/1.1 429 Too Many Requests
Retry-After: 60
{
  "error": {
    "code": "rate_limit_exceeded",
    "message": "Rate limit exceeded. Try again in 60 seconds."
  }
}

Niveles de Rate Limit

NivelLímiteVentanaCaso de Uso
Anónimo30/minPor IPEndpoints públicos
Autenticado100/minPor usuarioAcceso API estándar
Premium1000/minPor API keyPlanes de API de pago
Interno10000/minPor servicioServicio a servicio

Versionado

Versionado en Ruta de URL (Recomendado)

/api/v1/users
/api/v2/users

Pros: Explícito, fácil de enrutar, cacheable Contras: La URL cambia entre versiones

Versionado por Header

GET /api/users
Accept: application/vnd.myapp.v2+json

Pros: URLs limpias Contras: Más difícil de probar, fácil de olvidar

Estrategia de Versionado

1. Empezar con /api/v1/ — no versionar hasta que sea necesario
2. Mantener como máximo 2 versiones activas (actual + anterior)
3. Línea de tiempo de deprecación:
   - Anunciar la deprecación (6 meses de aviso para APIs públicas)
   - Agregar header Sunset: Sunset: Sat, 01 Jan 2026 00:00:00 GMT
   - Retornar 410 Gone después de la fecha de sunset
4. Los cambios no disruptivos no necesitan una nueva versión:
   - Agregar nuevos campos a las respuestas
   - Agregar nuevos parámetros de consulta opcionales
   - Agregar nuevos endpoints
5. Los cambios disruptivos requieren una nueva versión:
   - Eliminar o renombrar campos
   - Cambiar tipos de campo
   - Cambiar la estructura de URL
   - Cambiar el método de autenticación

Patrones de Implementación

TypeScript (Next.js API Route)

import { z } from "zod";
import { NextRequest, NextResponse } from "next/server";

const createUserSchema = z.object({
  email: z.string().email(),
  name: z.string().min(1).max(100),
});

export async function POST(req: NextRequest) {
  const body = await req.json();
  const parsed = createUserSchema.safeParse(body);

  if (!parsed.success) {
    return NextResponse.json({
      error: {
        code: "validation_error",
        message: "Request validation failed",
        details: parsed.error.issues.map(i => ({
          field: i.path.join("."),
          message: i.message,
          code: i.code,
        })),
      },
    }, { status: 422 });
  }

  const user = await createUser(parsed.data);

  return NextResponse.json(
    { data: user },
    {
      status: 201,
      headers: { Location: `/api/v1/users/${user.id}` },
    },
  );
}

Python (Django REST Framework)

from rest_framework import serializers, viewsets, status
from rest_framework.response import Response

class CreateUserSerializer(serializers.Serializer):
    email = serializers.EmailField()
    name = serializers.CharField(max_length=100)

class UserSerializer(serializers.ModelSerializer):
    class Meta:
        model = User
        fields = ["id", "email", "name", "created_at"]

class UserViewSet(viewsets.ModelViewSet):
    serializer_class = UserSerializer
    permission_classes = [IsAuthenticated]

    def get_serializer_class(self):
        if self.action == "create":
            return CreateUserSerializer
        return UserSerializer

    def create(self, request):
        serializer = CreateUserSerializer(data=request.data)
        serializer.is_valid(raise_exception=True)
        user = UserService.create(**serializer.validated_data)
        return Response(
            {"data": UserSerializer(user).data},
            status=status.HTTP_201_CREATED,
            headers={"Location": f"/api/v1/users/{user.id}"},
        )

Go (net/http)

func (h *UserHandler) CreateUser(w http.ResponseWriter, r *http.Request) {
    var req CreateUserRequest
    if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
        writeError(w, http.StatusBadRequest, "invalid_json", "Invalid request body")
        return
    }

    if err := req.Validate(); err != nil {
        writeError(w, http.StatusUnprocessableEntity, "validation_error", err.Error())
        return
    }

    user, err := h.service.Create(r.Context(), req)
    if err != nil {
        switch {
        case errors.Is(err, domain.ErrEmailTaken):
            writeError(w, http.StatusConflict, "email_taken", "Email already registered")
        default:
            writeError(w, http.StatusInternalServerError, "internal_error", "Internal error")
        }
        return
    }

    w.Header().Set("Location", fmt.Sprintf("/api/v1/users/%s", user.ID))
    writeJSON(w, http.StatusCreated, map[string]any{"data": user})
}

Lista de Verificación de Diseño de API

Antes de publicar un nuevo endpoint:

  • La URL del recurso sigue las convenciones de nomenclatura (plural, kebab-case, sin verbos)
  • Se usa el método HTTP correcto (GET para lecturas, POST para creaciones, etc.)
  • Se retornan códigos de estado apropiados (no 200 para todo)
  • La entrada se valida con esquema (Zod, Pydantic, Bean Validation)
  • Las respuestas de error siguen el formato estándar con códigos y mensajes
  • Se implementa paginación para endpoints de lista (cursor u offset)
  • Autenticación requerida (o marcado explícitamente como público)
  • Autorización verificada (el usuario solo puede acceder a sus propios recursos)
  • Rate limiting configurado
  • La respuesta no filtra detalles internos (stack traces, errores SQL)
  • Nomenclatura consistente con los endpoints existentes (camelCase vs snake_case)
  • Documentado (especificación OpenAPI/Swagger actualizada)

レビュー

まだレビューはありません。使ってみた感想をお寄せください。

同じリポジトリのスキル

概要と使いどころ

accessibility

無料日本語概要

Web・iOS・Androidの画面を、読み上げやキーボード操作に対応させ、ラベル、配色、操作対象の大きさなどをWCAG 2.2に沿って設計・点検するスキル。

  • アイコンボタンの説明を付けたいとき
  • キーボード操作とモーダルの点検
  • コントラストや操作対象の大きさの確認
affaan-m/ECC27.8万2026年10月12日 更新

agent-architecture-audit

無料日本語概要

AIエージェントの不調を、指示・記憶・ツール実行・画面表示など12の層から調べるスキル。コードやログを根拠に原因を整理し、重要度順の指摘と修正案をまとめます。

  • アプリ内だけで起きる不調を調べたいとき
  • 過去の会話が混ざる原因を調査
  • ツールの未実行や実行の誤報を確認
affaan-m/ECC27.8万2026年10月12日 更新

agent-eval

無料日本語概要

実際の開発課題で複数のコーディングエージェントを比較するスキル。成功率、取得可能なAPI費用、所要時間、繰り返し実行の安定性を測り、選定や更新後の評価に使えます。

  • 実際の開発課題でエージェントを比較
  • 新しいツールやモデルの導入前評価
  • エージェント更新後の性能確認
affaan-m/ECC27.8万2026年10月12日 更新

agent-harness-construction

無料日本語概要

AIエージェントが使うツールの種類や入出力、エラーからの復帰手順を設計・見直します。文脈の情報量も整理し、作業完了率や再試行回数で改善を評価します。

  • エージェントのツールや入力形式の設計
  • ツールの結果と次の行動を明確にしたいとき
  • 安全な再試行と停止条件を定めたいとき
affaan-m/ECC27.8万2026年10月12日 更新

agent-introspection-debugging

無料日本語概要

AIエージェントの失敗や同じ操作の繰り返しを記録し、原因の切り分け、小さな復旧操作、結果の報告まで進める手順を示して、根拠のある再試行につなげるスキル。

  • 同じツール操作を繰り返す原因の調査
  • 会話の肥大化による品質低下の調査
  • ファイルパスや環境の食い違いの確認
affaan-m/ECC27.8万2026年10月12日 更新

agent-introspection-debugging

無料日本語概要

AIエージェントが失敗や同じ操作を繰り返す原因を、エラーと実行状況から整理します。小さな復旧操作を試し、結果と根拠を引き継げる報告にまとめるスキルです。

  • エージェントの連続失敗を診断したいとき
  • 同じツール操作のループ調査
  • 情報の蓄積による出力劣化の点検
affaan-m/ECC27.8万2026年10月5日 更新

affaan-m のスキルをすべて見る

このスキルの問題を報告する