{
  "openapi": "3.1.0",
  "info": {
    "title": "NEXO Latinoamérica API",
    "version": "1.0.0",
    "description": "Public REST API for NEXO Latinoamérica — a youth leadership program serving vulnerable young people aged 15–25 across Mexico, Colombia, Peru, Chile, Ecuador, and the Dominican Republic.\n\n## Response format\nAll endpoints return JSON. Error responses follow a structured envelope: `{\"error\":{\"code\",\"message\",\"hint\",\"documentation\"}}`.\n\n## Versioning\nThe stable version is **v1**. It is addressed by URL path prefix: `https://nexolatam.org/api/v1/<endpoint>`. The unversioned base `https://nexolatam.org/api/<endpoint>` is a permanent alias that always tracks the latest stable version. Every API response includes an `X-API-Version` header. Machine-readable version metadata is available at `/api/version`.\n\n## Deprecation policy\nWhen a version is deprecated we guarantee a minimum notice period of **180 days** before it is retired. Deprecated responses will carry the `Deprecation` and `Sunset` HTTP headers (RFC 8594) with the retirement date. There are no deprecated versions at this time.\n\n## Rate limiting\nRequests are limited to **60 per minute per IP address**. Every response includes the standard `RateLimit-Limit`, `RateLimit-Remaining`, `RateLimit-Reset` and `RateLimit-Policy` headers. When the limit is exceeded the API returns HTTP `429` with a `Retry-After` header and the standard error envelope (code `RATE_LIMIT_EXCEEDED`). Note: limits are enforced per serverless instance.\n\n## CLI\nAn official command-line client is available: `@nexolatam/cli` (Node.js). See https://nexolatam.org/docs for installation and usage.\n\nFull documentation: https://nexolatam.org/docs",
    "contact": {
      "name": "NEXO Latinoamérica",
      "email": "liderazgo@nexolatam.org",
      "url": "https://nexolatam.org/contact"
    },
    "license": {
      "name": "Proprietary",
      "url": "https://nexolatam.org/privacy"
    },
    "x-logo": {
      "url": "https://nexolatam.org/images/nexo-logo-principal.png",
      "altText": "NEXO Latinoamérica Logo"
    }
  },
  "servers": [
    {
      "url": "https://nexolatam.org/api/v1",
      "description": "Production — stable v1 (recommended, pinned version)"
    },
    {
      "url": "https://nexolatam.org/api",
      "description": "Production — unversioned alias (always tracks the latest stable version)"
    }
  ],
  "tags": [
    {
      "name": "Blog",
      "description": "Published blog posts — articles about youth leadership, career development, and social impact."
    },
    {
      "name": "Newsletter",
      "description": "Newsletter subscription management."
    },
    {
      "name": "Content",
      "description": "Machine-readable page content for AI agents and scrapers (Markdown format)."
    },
    {
      "name": "Health",
      "description": "Service health and readiness probes."
    },
    {
      "name": "Meta",
      "description": "API metadata — version, deprecation policy, and the OpenAPI specification itself."
    }
  ],
  "paths": {
    "/blog": {
      "get": {
        "operationId": "listBlogPosts",
        "summary": "List published blog posts",
        "description": "Returns a paginated list of published blog posts with optional filters by category, tag, and full-text search. Each post includes title, slug, excerpt, category, tags, author, view count, and publish date.",
        "tags": ["Blog"],
        "parameters": [
          {
            "name": "page",
            "in": "query",
            "description": "Page number (1-based).",
            "required": false,
            "schema": { "type": "integer", "minimum": 1, "default": 1, "example": 1 }
          },
          {
            "name": "limit",
            "in": "query",
            "description": "Number of posts per page.",
            "required": false,
            "schema": { "type": "integer", "minimum": 1, "maximum": 100, "default": 10, "example": 10 }
          },
          {
            "name": "category",
            "in": "query",
            "description": "Filter by category slug (e.g. `liderazgo`, `empleabilidad`).",
            "required": false,
            "schema": { "type": "string", "example": "liderazgo" }
          },
          {
            "name": "tag",
            "in": "query",
            "description": "Filter by tag name.",
            "required": false,
            "schema": { "type": "string", "example": "juventud" }
          },
          {
            "name": "search",
            "in": "query",
            "description": "Full-text search across title and excerpt fields.",
            "required": false,
            "schema": { "type": "string", "example": "liderazgo juvenil" }
          }
        ],
        "responses": {
          "200": {
            "description": "Paginated list of posts and available categories.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/BlogListResponse" },
                "example": {
                  "posts": [
                    {
                      "id": "clx1234",
                      "title": "Cómo NEXO transforma jóvenes en líderes",
                      "slug": "nexo-transforma-jovenes",
                      "excerpt": "Descubre el modelo de tres fases que usamos para desarrollar líderes.",
                      "featuredImage": "/images/nexo-hero.jpg",
                      "category": { "id": "cat1", "name": "Liderazgo", "slug": "liderazgo", "color": "#1E3A8A" },
                      "tags": ["liderazgo", "juventud"],
                      "authorName": "Alam Lule",
                      "views": 142,
                      "publishedAt": "2026-03-15T10:00:00.000Z"
                    }
                  ],
                  "categories": [],
                  "pagination": { "page": 1, "limit": 10, "total": 1, "totalPages": 1 }
                }
              }
            }
          },
          "500": {
            "description": "Internal server error.",
            "content": {
              "application/json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } }
            }
          }
        }
      }
    },
    "/blog/{slug}": {
      "get": {
        "operationId": "getBlogPostBySlug",
        "summary": "Get a blog post by slug",
        "description": "Returns the full content of a single published blog post identified by its URL slug, along with up to 3 related posts. Each successful read increments the post's view counter.",
        "tags": ["Blog"],
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "description": "URL-safe slug of the blog post.",
            "required": true,
            "schema": { "type": "string", "example": "nexo-transforma-jovenes" }
          }
        ],
        "responses": {
          "200": {
            "description": "Blog post found.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/BlogPostResponse" }
              }
            }
          },
          "404": {
            "description": "Post not found or not published.",
            "content": {
              "application/json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } }
            }
          },
          "500": {
            "description": "Internal server error.",
            "content": {
              "application/json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } }
            }
          }
        }
      }
    },
    "/subscribe": {
      "post": {
        "operationId": "subscribeToNewsletter",
        "summary": "Subscribe to the NEXO newsletter",
        "description": "Adds a new subscriber to the NEXO newsletter. If the email already exists and is active, returns success without creating a duplicate. If previously unsubscribed, reactivates the subscription.",
        "tags": ["Newsletter"],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": { "$ref": "#/components/schemas/SubscribeRequest" },
              "example": {
                "firstName": "María",
                "lastName": "García",
                "email": "maria@ong-ejemplo.org",
                "country": "México",
                "source": "newsletter_home"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Subscription created or already active.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/SubscribeResponse" },
                "example": { "success": true, "message": "¡Gracias por suscribirte! Pronto recibirás nuestros recursos." }
              }
            }
          },
          "400": {
            "description": "Validation error — missing required fields or invalid email format.",
            "content": {
              "application/json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } }
            }
          },
          "500": {
            "description": "Internal server error.",
            "content": {
              "application/json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } }
            }
          }
        }
      }
    },
    "/markdown/{slug}": {
      "get": {
        "operationId": "getPageMarkdown",
        "summary": "Get page content as Markdown",
        "description": "Returns the content of a known site page in Markdown format (Content-Type: text/markdown). Designed for AI agents and scrapers that prefer plain text over HTML. Unknown paths return HTTP 404 with a Markdown site-map body so agents can recover. Sets `Vary: Accept, Accept-Encoding` for correct CDN caching.",
        "tags": ["Content"],
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "description": "Page slug. Supported values: `home`, `para-jovenes`, `para-organizaciones`, `sobre-alam`, `about`, `contact`, `privacy`, `blog`, `llms`.",
            "required": true,
            "schema": {
              "type": "string",
              "enum": ["home", "para-jovenes", "para-organizaciones", "sobre-alam", "about", "contact", "privacy", "blog", "llms"],
              "example": "about"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Page content in Markdown.",
            "content": {
              "text/markdown": {
                "schema": { "type": "string" }
              }
            }
          },
          "404": {
            "description": "Unknown page slug. Body is Markdown with a site-map to help agents navigate.",
            "content": {
              "text/markdown": {
                "schema": { "type": "string" }
              }
            }
          }
        }
      }
    },
    "/health": {
      "get": {
        "operationId": "getHealthFull",
        "summary": "Full health check",
        "description": "Returns detailed health status including image asset availability, environment info, and response time. Returns HTTP 200 when healthy, 503 when degraded or unhealthy.",
        "tags": ["Health"],
        "responses": {
          "200": {
            "description": "Service is healthy.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/HealthResponse" }
              }
            }
          },
          "503": {
            "description": "Service is degraded or unhealthy.",
            "content": {
              "application/json": { "schema": { "$ref": "#/components/schemas/HealthResponse" } }
            }
          }
        }
      }
    },
    "/health/simple": {
      "get": {
        "operationId": "getHealthSimple",
        "summary": "Simple health check",
        "description": "Lightweight probe that returns HTTP 200 with `{\"status\":\"ok\"}` as long as the process is running. Use this for uptime monitors and load-balancer readiness checks.",
        "tags": ["Health"],
        "responses": {
          "200": {
            "description": "Service is running.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": ["status", "timestamp"],
                  "properties": {
                    "status": { "type": "string", "enum": ["ok"], "example": "ok" },
                    "timestamp": { "type": "string", "format": "date-time", "example": "2026-09-10T00:00:00.000Z" }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/version": {
      "get": {
        "operationId": "getApiVersion",
        "summary": "Get API version and deprecation policy",
        "description": "Returns machine-readable metadata about the current API version, the versioning strategy (URL path prefix `/api/v1/`), and the deprecation / sunset policy. Agents can call this to discover which version they are talking to and what stability guarantees apply.",
        "tags": ["Meta"],
        "responses": {
          "200": {
            "description": "Version and policy metadata.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "current": { "type": "string", "example": "v1" },
                    "latest": { "type": "string", "example": "v1" },
                    "status": { "type": "string", "example": "stable" },
                    "versioning": { "type": "object" },
                    "deprecationPolicy": { "type": "object" }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/openapi": {
      "get": {
        "operationId": "getOpenApiSpec",
        "summary": "Get the OpenAPI specification",
        "description": "Returns this OpenAPI 3.1 specification as JSON. Also available as the static file at https://nexolatam.org/openapi.json. Sends permissive CORS headers so browser-based agents can fetch it.",
        "tags": ["Meta"],
        "responses": {
          "200": {
            "description": "The OpenAPI specification document.",
            "content": {
              "application/json": {
                "schema": { "type": "object" }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "schemas": {
      "ErrorResponse": {
        "type": "object",
        "description": "Structured error envelope returned by all API endpoints on failure.",
        "required": ["error"],
        "properties": {
          "error": {
            "type": "object",
            "required": ["code", "message"],
            "properties": {
              "code": {
                "type": "string",
                "description": "Machine-readable error identifier.",
                "example": "NOT_FOUND",
                "enum": [
                  "NOT_FOUND",
                  "BAD_REQUEST",
                  "INTERNAL_SERVER_ERROR",
                  "METHOD_NOT_ALLOWED",
                  "VALIDATION_ERROR",
                  "RATE_LIMIT_EXCEEDED"
                ]
              },
              "message": {
                "type": "string",
                "description": "Human-readable description of the error (in Spanish).",
                "example": "Post no encontrado"
              },
              "hint": {
                "type": ["string", "null"],
                "description": "Actionable suggestion for resolving the error.",
                "example": "Verifica el slug y consulta /api/blog para ver todos los recursos disponibles."
              },
              "documentation": {
                "type": "string",
                "format": "uri",
                "description": "Link to the API documentation.",
                "example": "https://nexolatam.org/docs"
              }
            }
          }
        }
      },
      "Category": {
        "type": "object",
        "required": ["id", "name", "slug"],
        "properties": {
          "id": { "type": "string", "example": "cat1" },
          "name": { "type": "string", "example": "Liderazgo" },
          "slug": { "type": "string", "example": "liderazgo" },
          "color": { "type": "string", "example": "#1E3A8A" }
        }
      },
      "PostSummary": {
        "type": "object",
        "required": ["id", "title", "slug"],
        "properties": {
          "id": { "type": "string", "example": "clx1234" },
          "title": { "type": "string", "example": "Cómo NEXO transforma jóvenes en líderes" },
          "slug": { "type": "string", "example": "nexo-transforma-jovenes" },
          "excerpt": { "type": ["string", "null"], "example": "Descubre el modelo de tres fases." },
          "featuredImage": { "type": ["string", "null"], "example": "/images/nexo-hero.jpg" },
          "category": { "$ref": "#/components/schemas/Category" },
          "tags": { "type": "array", "items": { "type": "string" }, "example": ["liderazgo", "juventud"] },
          "authorName": { "type": ["string", "null"], "example": "Alam Lule" },
          "views": { "type": "integer", "example": 142 },
          "publishedAt": { "type": ["string", "null"], "format": "date-time", "example": "2026-03-15T10:00:00.000Z" }
        }
      },
      "Pagination": {
        "type": "object",
        "required": ["page", "limit", "total", "totalPages"],
        "properties": {
          "page": { "type": "integer", "example": 1 },
          "limit": { "type": "integer", "example": 10 },
          "total": { "type": "integer", "example": 42 },
          "totalPages": { "type": "integer", "example": 5 }
        }
      },
      "BlogListResponse": {
        "type": "object",
        "required": ["posts", "categories", "pagination"],
        "properties": {
          "posts": { "type": "array", "items": { "$ref": "#/components/schemas/PostSummary" } },
          "categories": {
            "type": "array",
            "items": {
              "allOf": [
                { "$ref": "#/components/schemas/Category" },
                {
                  "type": "object",
                  "properties": {
                    "_count": {
                      "type": "object",
                      "properties": {
                        "posts": { "type": "integer", "example": 5 }
                      }
                    }
                  }
                }
              ]
            }
          },
          "pagination": { "$ref": "#/components/schemas/Pagination" }
        }
      },
      "BlogPostResponse": {
        "type": "object",
        "required": ["post", "relatedPosts"],
        "properties": {
          "post": {
            "allOf": [
              { "$ref": "#/components/schemas/PostSummary" },
              {
                "type": "object",
                "properties": {
                  "content": { "type": "string", "description": "Full HTML or Markdown post content." },
                  "status": { "type": "string", "enum": ["published"] },
                  "category": { "$ref": "#/components/schemas/Category" }
                }
              }
            ]
          },
          "relatedPosts": {
            "type": "array",
            "maxItems": 3,
            "items": { "$ref": "#/components/schemas/PostSummary" }
          }
        }
      },
      "SubscribeRequest": {
        "type": "object",
        "required": ["firstName", "email"],
        "properties": {
          "firstName": { "type": "string", "minLength": 1, "example": "María" },
          "lastName": { "type": "string", "example": "García" },
          "email": { "type": "string", "format": "email", "example": "maria@ong-ejemplo.org" },
          "phone": { "type": "string", "example": "+52 55 1234 5678" },
          "country": { "type": "string", "example": "México" },
          "source": {
            "type": "string",
            "description": "Acquisition source identifier.",
            "default": "newsletter_home",
            "example": "newsletter_home"
          }
        }
      },
      "SubscribeResponse": {
        "type": "object",
        "required": ["success", "message"],
        "properties": {
          "success": { "type": "boolean", "example": true },
          "message": { "type": "string", "example": "¡Gracias por suscribirte! Pronto recibirás nuestros recursos." }
        }
      },
      "HealthResponse": {
        "type": "object",
        "required": ["status", "timestamp"],
        "properties": {
          "status": { "type": "string", "enum": ["healthy", "degraded", "unhealthy"], "example": "healthy" },
          "timestamp": { "type": "string", "format": "date-time" },
          "uptime": { "type": "number", "description": "Process uptime in seconds.", "example": 3600 },
          "version": { "type": "string", "example": "1.0.0" },
          "responseTime": { "type": "string", "example": "12ms" },
          "checks": {
            "type": "object",
            "properties": {
              "images": {
                "type": "object",
                "properties": {
                  "status": { "type": "string", "enum": ["pass", "fail"] },
                  "details": { "type": "string" },
                  "missing": { "type": "array", "items": { "type": "string" } }
                }
              },
              "environment": {
                "type": "object",
                "properties": {
                  "status": { "type": "string", "enum": ["pass", "fail"] },
                  "nodeVersion": { "type": "string", "example": "v22.14.0" },
                  "nextVersion": { "type": "string", "example": "14.2.28" }
                }
              }
            }
          }
        }
      }
    }
  }
}
