{
  "openapi": "3.1.0",
  "info": {
    "title": "MotoRevista CR Public API",
    "version": "1.0.0",
    "description": "Read-only public API for MotoRevista CR — Costa Rica's leading motorcycle and ATV media. Provides access to posts, categories, and full-text search. No authentication required. Rate limit: 60 requests/minute per IP.",
    "contact": {
      "name": "MotoRevista CR",
      "email": "info@motorevistacr.com",
      "url": "https://www.motorevistacr.com/developers"
    },
    "license": {
      "name": "CC BY-NC 4.0",
      "url": "https://creativecommons.org/licenses/by-nc/4.0/"
    },
    "x-logo": {
      "url": "https://www.motorevistacr.com/wp-content/uploads/2023/04/cropped-logomotorevistacr2.png.webp",
      "altText": "MotoRevista CR"
    }
  },
  "servers": [
    {
      "url": "https://www.motorevistacr.com",
      "description": "Production"
    }
  ],
  "tags": [
    {
      "name": "Posts",
      "description": "Articles, reviews, tech specs (fichas técnicas), and news"
    },
    {
      "name": "Categories",
      "description": "Content taxonomy categories"
    },
    {
      "name": "Search",
      "description": "Full-text search across titles and keywords"
    }
  ],
  "paths": {
    "/api/posts": {
      "get": {
        "operationId": "listPosts",
        "summary": "List recent posts",
        "description": "Returns a paginated list of posts. Optionally filter by category or tag slug.",
        "tags": ["Posts"],
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Maximum number of posts to return (1–50, default 10)",
            "schema": { "type": "integer", "minimum": 1, "maximum": 50, "default": 10 }
          },
          {
            "name": "page",
            "in": "query",
            "required": false,
            "description": "Page number, 1-indexed (default 1)",
            "schema": { "type": "integer", "minimum": 1, "default": 1 }
          },
          {
            "name": "category",
            "in": "query",
            "required": false,
            "description": "Filter by category slug (e.g. 'motos-nuevas'). Use GET /api/categories to list valid slugs.",
            "schema": { "type": "string" }
          },
          {
            "name": "tag",
            "in": "query",
            "required": false,
            "description": "Filter by tag slug (e.g. 'honda')",
            "schema": { "type": "string" }
          }
        ],
        "responses": {
          "200": {
            "description": "Successful response",
            "headers": {
              "RateLimit-Limit": { "$ref": "#/components/headers/RateLimit-Limit" },
              "RateLimit-Remaining": { "$ref": "#/components/headers/RateLimit-Remaining" },
              "RateLimit-Reset": { "$ref": "#/components/headers/RateLimit-Reset" }
            },
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/PostList" },
                "example": {
                  "data": [
                    {
                      "id": "post_1",
                      "slug": "honda-cb300r-ficha-tecnica",
                      "title": "Honda CB300R: Ficha Técnica y Precio en Costa Rica",
                      "excerpt": "Conoce todas las especificaciones de la Honda CB300R disponible en Costa Rica.",
                      "publishedAt": "2025-03-15T10:00:00.000Z",
                      "modifiedAt": "2025-03-16T08:00:00.000Z",
                      "categoryIds": ["cat_5"],
                      "url": "https://www.motorevistacr.com/honda-cb300r-ficha-tecnica/"
                    }
                  ],
                  "meta": {
                    "page": 1,
                    "limit": 10,
                    "total": 450,
                    "totalPages": 45,
                    "hasNextPage": true,
                    "hasPrevPage": false
                  }
                }
              }
            }
          },
          "400": { "$ref": "#/components/responses/BadRequest" },
          "404": { "$ref": "#/components/responses/NotFound" },
          "429": { "$ref": "#/components/responses/TooManyRequests" },
          "500": { "$ref": "#/components/responses/InternalError" }
        }
      }
    },
    "/api/posts/{slug}": {
      "get": {
        "operationId": "getPostBySlug",
        "summary": "Get a single post by slug",
        "description": "Returns full post data for a given URL slug, including content, author, featured image, SEO metadata, and vehicle spec data for fichas técnicas.",
        "tags": ["Posts"],
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "description": "URL slug of the post (e.g. 'honda-cb300r-ficha-tecnica')",
            "schema": { "type": "string", "minLength": 1 }
          }
        ],
        "responses": {
          "200": {
            "description": "Successful response",
            "headers": {
              "RateLimit-Limit": { "$ref": "#/components/headers/RateLimit-Limit" },
              "RateLimit-Remaining": { "$ref": "#/components/headers/RateLimit-Remaining" },
              "RateLimit-Reset": { "$ref": "#/components/headers/RateLimit-Reset" }
            },
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/PostDetail" }
              }
            }
          },
          "400": { "$ref": "#/components/responses/BadRequest" },
          "404": { "$ref": "#/components/responses/NotFound" },
          "429": { "$ref": "#/components/responses/TooManyRequests" },
          "500": { "$ref": "#/components/responses/InternalError" }
        }
      }
    },
    "/api/categories": {
      "get": {
        "operationId": "listCategories",
        "summary": "List all categories",
        "description": "Returns all content taxonomy categories. The `slug` field can be used as the `category` filter in GET /api/posts.",
        "tags": ["Categories"],
        "responses": {
          "200": {
            "description": "Successful response",
            "headers": {
              "RateLimit-Limit": { "$ref": "#/components/headers/RateLimit-Limit" },
              "RateLimit-Remaining": { "$ref": "#/components/headers/RateLimit-Remaining" },
              "RateLimit-Reset": { "$ref": "#/components/headers/RateLimit-Reset" }
            },
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/CategoryList" }
              }
            }
          },
          "429": { "$ref": "#/components/responses/TooManyRequests" },
          "500": { "$ref": "#/components/responses/InternalError" }
        }
      }
    },
    "/api/search": {
      "get": {
        "operationId": "searchPosts",
        "summary": "Full-text search",
        "description": "Search posts by keyword. Matches against post titles and keyword metadata.",
        "tags": ["Search"],
        "parameters": [
          {
            "name": "q",
            "in": "query",
            "required": true,
            "description": "Search query string (2–200 characters)",
            "schema": { "type": "string", "minLength": 2, "maxLength": 200 }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Maximum results to return (1–50, default 10)",
            "schema": { "type": "integer", "minimum": 1, "maximum": 50, "default": 10 }
          }
        ],
        "responses": {
          "200": {
            "description": "Successful response",
            "headers": {
              "RateLimit-Limit": { "$ref": "#/components/headers/RateLimit-Limit" },
              "RateLimit-Remaining": { "$ref": "#/components/headers/RateLimit-Remaining" },
              "RateLimit-Reset": { "$ref": "#/components/headers/RateLimit-Reset" }
            },
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/SearchResults" }
              }
            }
          },
          "400": { "$ref": "#/components/responses/BadRequest" },
          "429": { "$ref": "#/components/responses/TooManyRequests" },
          "500": { "$ref": "#/components/responses/InternalError" }
        }
      }
    }
  },
  "components": {
    "headers": {
      "RateLimit-Limit": {
        "description": "Request limit per window",
        "schema": { "type": "integer", "example": 60 }
      },
      "RateLimit-Remaining": {
        "description": "Remaining requests in current window",
        "schema": { "type": "integer", "example": 59 }
      },
      "RateLimit-Reset": {
        "description": "Unix timestamp when the window resets",
        "schema": { "type": "integer", "example": 1751500000 }
      },
      "Retry-After": {
        "description": "Seconds to wait before retrying (on 429)",
        "schema": { "type": "integer", "example": 60 }
      }
    },
    "schemas": {
      "PostSummary": {
        "type": "object",
        "required": ["id", "slug", "title", "publishedAt", "url"],
        "properties": {
          "id": { "type": "string", "description": "Unique post identifier" },
          "slug": { "type": "string", "description": "URL-friendly post slug" },
          "title": { "type": "string", "description": "Post title" },
          "excerpt": { "type": "string", "nullable": true, "description": "Short excerpt or summary" },
          "publishedAt": { "type": "string", "format": "date-time" },
          "modifiedAt": { "type": "string", "format": "date-time" },
          "categoryIds": {
            "type": "array",
            "items": { "type": "string" },
            "description": "Array of category IDs"
          },
          "url": { "type": "string", "format": "uri", "description": "Canonical URL" }
        }
      },
      "PostDetail": {
        "type": "object",
        "properties": {
          "data": {
            "allOf": [{ "$ref": "#/components/schemas/PostSummary" }],
            "properties": {
              "content": { "type": "string", "description": "Full HTML content" },
              "tags": {
                "type": "array",
                "items": { "type": "string" }
              },
              "author": {
                "type": "object",
                "nullable": true,
                "properties": {
                  "name": { "type": "string" },
                  "slug": { "type": "string" },
                  "url": { "type": "string", "format": "uri" }
                }
              },
              "featuredImage": {
                "type": "object",
                "nullable": true,
                "properties": {
                  "url": { "type": "string", "format": "uri" },
                  "alt": { "type": "string" },
                  "width": { "type": "integer", "nullable": true },
                  "height": { "type": "integer", "nullable": true }
                }
              },
              "seoTitle": { "type": "string", "nullable": true },
              "metaDescription": { "type": "string", "nullable": true },
              "canonicalUrl": { "type": "string", "format": "uri" },
              "spec": {
                "type": "object",
                "nullable": true,
                "description": "Vehicle specification data (for fichas técnicas only)",
                "properties": {
                  "make": { "type": "string" },
                  "model": { "type": "string" },
                  "year": { "type": "integer" },
                  "displacementCc": { "type": "number", "nullable": true },
                  "horsePower": { "type": "number", "nullable": true },
                  "torqueNm": { "type": "number", "nullable": true },
                  "topSpeedKmH": { "type": "number", "nullable": true },
                  "priceUsd": { "type": "number", "nullable": true },
                  "priceCrc": { "type": "number", "nullable": true },
                  "transmission": { "type": "string", "nullable": true },
                  "rating": { "type": "number", "nullable": true }
                }
              }
            }
          }
        }
      },
      "PostList": {
        "type": "object",
        "properties": {
          "data": {
            "type": "array",
            "items": { "$ref": "#/components/schemas/PostSummary" }
          },
          "meta": {
            "type": "object",
            "properties": {
              "page": { "type": "integer" },
              "limit": { "type": "integer" },
              "total": { "type": "integer" },
              "totalPages": { "type": "integer" },
              "hasNextPage": { "type": "boolean" },
              "hasPrevPage": { "type": "boolean" }
            }
          }
        }
      },
      "Category": {
        "type": "object",
        "required": ["id", "slug", "name", "url"],
        "properties": {
          "id": { "type": "string" },
          "slug": { "type": "string" },
          "name": { "type": "string" },
          "description": { "type": "string", "nullable": true },
          "url": { "type": "string", "format": "uri" }
        }
      },
      "CategoryList": {
        "type": "object",
        "properties": {
          "data": {
            "type": "array",
            "items": { "$ref": "#/components/schemas/Category" }
          },
          "meta": {
            "type": "object",
            "properties": {
              "total": { "type": "integer" }
            }
          }
        }
      },
      "SearchResults": {
        "type": "object",
        "properties": {
          "data": {
            "type": "array",
            "items": { "$ref": "#/components/schemas/PostSummary" }
          },
          "meta": {
            "type": "object",
            "properties": {
              "query": { "type": "string" },
              "limit": { "type": "integer" },
              "total": { "type": "integer" },
              "returned": { "type": "integer" }
            }
          }
        }
      },
      "Error": {
        "type": "object",
        "required": ["error"],
        "properties": {
          "error": {
            "type": "object",
            "required": ["code", "message", "hint"],
            "properties": {
              "code": { "type": "string", "description": "Machine-readable error code", "example": "POST_NOT_FOUND" },
              "message": { "type": "string", "description": "Human-readable description" },
              "hint": { "type": "string", "description": "Suggested resolution" },
              "documentation": { "type": "string", "format": "uri", "description": "Link to developer docs" }
            }
          }
        }
      }
    },
    "responses": {
      "BadRequest": {
        "description": "Invalid request parameters",
        "content": {
          "application/json": {
            "schema": { "$ref": "#/components/schemas/Error" },
            "example": {
              "error": {
                "code": "INVALID_PARAM",
                "message": "Parameter 'limit' must be an integer between 1 and 50.",
                "hint": "Adjust the 'limit' query parameter to a value between 1 and 50.",
                "documentation": "https://www.motorevistacr.com/developers"
              }
            }
          }
        }
      },
      "NotFound": {
        "description": "Resource not found",
        "content": {
          "application/json": {
            "schema": { "$ref": "#/components/schemas/Error" }
          }
        }
      },
      "TooManyRequests": {
        "description": "Rate limit exceeded. Wait for the Retry-After period.",
        "headers": {
          "Retry-After": { "$ref": "#/components/headers/Retry-After" },
          "RateLimit-Limit": { "$ref": "#/components/headers/RateLimit-Limit" },
          "RateLimit-Remaining": { "$ref": "#/components/headers/RateLimit-Remaining" },
          "RateLimit-Reset": { "$ref": "#/components/headers/RateLimit-Reset" }
        },
        "content": {
          "application/json": {
            "schema": { "$ref": "#/components/schemas/Error" },
            "example": {
              "error": {
                "code": "RATE_LIMITED",
                "message": "You have exceeded the rate limit of 60 requests per minute.",
                "hint": "Wait 60 seconds before retrying, or check the Retry-After header.",
                "documentation": "https://www.motorevistacr.com/developers"
              }
            }
          }
        }
      },
      "InternalError": {
        "description": "Internal server error",
        "content": {
          "application/json": {
            "schema": { "$ref": "#/components/schemas/Error" }
          }
        }
      }
    }
  }
}
