{
  "openapi": "3.1.0",
  "info": {
    "title": "Simone Ruggiero public API",
    "version": "1.0.0",
    "summary": "Read-only data of simoneruggiero.com",
    "description": "Public read-only API with the same data shown on simoneruggiero.com: profile, iOS/macOS apps, web projects, blog posts and AI thoughts. No authentication. Rate limit: 60 requests per minute per IP, reported with the IETF RateLimit and RateLimit-Policy headers. Errors use application/problem+json (RFC 9457).",
    "contact": {
      "name": "Simone Ruggiero",
      "email": "simone.ruggiero97@gmail.com",
      "url": "https://simoneruggiero.com/contact"
    }
  },
  "externalDocs": {
    "description": "API and MCP docs",
    "url": "https://simoneruggiero.com/docs"
  },
  "servers": [
    {
      "url": "https://simoneruggiero.com"
    }
  ],
  "security": [],
  "tags": [
    {
      "name": "profile"
    },
    {
      "name": "apps"
    },
    {
      "name": "content"
    }
  ],
  "paths": {
    "/api/v1/info": {
      "get": {
        "operationId": "getInfo",
        "summary": "Get profile",
        "description": "Profile of Simone Ruggiero: role, location, email, social links, skills and main site links.",
        "tags": [
          "profile"
        ],
        "responses": {
          "200": {
            "description": "Profile",
            "headers": {
              "RateLimit": {
                "description": "IETF RateLimit header, e.g. `\"default\";r=59;t=60` (remaining is an estimate).",
                "schema": {
                  "type": "string"
                }
              },
              "RateLimit-Policy": {
                "description": "IETF RateLimit-Policy header: `\"default\";q=60;w=60`.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Info"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded (60 requests per minute per IP)",
            "headers": {
              "Retry-After": {
                "schema": {
                  "type": "integer"
                },
                "description": "Seconds to wait"
              },
              "RateLimit": {
                "description": "IETF RateLimit header, e.g. `\"default\";r=59;t=60` (remaining is an estimate).",
                "schema": {
                  "type": "string"
                }
              },
              "RateLimit-Policy": {
                "description": "IETF RateLimit-Policy header: `\"default\";q=60;w=60`.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/apps": {
      "get": {
        "operationId": "listApps",
        "summary": "List apps",
        "description": "iOS and macOS apps published by Simone Ruggiero, with store and tracked download links.",
        "tags": [
          "apps"
        ],
        "responses": {
          "200": {
            "description": "Apps",
            "headers": {
              "RateLimit": {
                "description": "IETF RateLimit header, e.g. `\"default\";r=59;t=60` (remaining is an estimate).",
                "schema": {
                  "type": "string"
                }
              },
              "RateLimit-Policy": {
                "description": "IETF RateLimit-Policy header: `\"default\";q=60;w=60`.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AppList"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded (60 requests per minute per IP)",
            "headers": {
              "Retry-After": {
                "schema": {
                  "type": "integer"
                },
                "description": "Seconds to wait"
              },
              "RateLimit": {
                "description": "IETF RateLimit header, e.g. `\"default\";r=59;t=60` (remaining is an estimate).",
                "schema": {
                  "type": "string"
                }
              },
              "RateLimit-Policy": {
                "description": "IETF RateLimit-Policy header: `\"default\";q=60;w=60`.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/projects": {
      "get": {
        "operationId": "listProjects",
        "summary": "List web projects",
        "description": "Web products and client websites built by Simone Ruggiero.",
        "tags": [
          "apps"
        ],
        "responses": {
          "200": {
            "description": "Web projects",
            "headers": {
              "RateLimit": {
                "description": "IETF RateLimit header, e.g. `\"default\";r=59;t=60` (remaining is an estimate).",
                "schema": {
                  "type": "string"
                }
              },
              "RateLimit-Policy": {
                "description": "IETF RateLimit-Policy header: `\"default\";q=60;w=60`.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProjectList"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded (60 requests per minute per IP)",
            "headers": {
              "Retry-After": {
                "schema": {
                  "type": "integer"
                },
                "description": "Seconds to wait"
              },
              "RateLimit": {
                "description": "IETF RateLimit header, e.g. `\"default\";r=59;t=60` (remaining is an estimate).",
                "schema": {
                  "type": "string"
                }
              },
              "RateLimit-Policy": {
                "description": "IETF RateLimit-Policy header: `\"default\";q=60;w=60`.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/posts": {
      "get": {
        "operationId": "listPosts",
        "summary": "List posts",
        "description": "Posts of a collection, newest first.",
        "tags": [
          "content"
        ],
        "parameters": [
          {
            "name": "collection",
            "in": "query",
            "required": false,
            "description": "Which collection to list (default blog)",
            "schema": {
              "type": "string",
              "enum": [
                "blog",
                "ai-thoughts"
              ],
              "default": "blog"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Posts",
            "headers": {
              "RateLimit": {
                "description": "IETF RateLimit header, e.g. `\"default\";r=59;t=60` (remaining is an estimate).",
                "schema": {
                  "type": "string"
                }
              },
              "RateLimit-Policy": {
                "description": "IETF RateLimit-Policy header: `\"default\";q=60;w=60`.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PostList"
                }
              }
            }
          },
          "400": {
            "description": "Invalid collection",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded (60 requests per minute per IP)",
            "headers": {
              "Retry-After": {
                "schema": {
                  "type": "integer"
                },
                "description": "Seconds to wait"
              },
              "RateLimit": {
                "description": "IETF RateLimit header, e.g. `\"default\";r=59;t=60` (remaining is an estimate).",
                "schema": {
                  "type": "string"
                }
              },
              "RateLimit-Policy": {
                "description": "IETF RateLimit-Policy header: `\"default\";q=60;w=60`.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/posts/{collection}/{slug}": {
      "get": {
        "operationId": "getPost",
        "summary": "Get post",
        "description": "One post with its full content in Markdown.",
        "tags": [
          "content"
        ],
        "parameters": [
          {
            "name": "collection",
            "in": "path",
            "required": true,
            "description": "Post collection",
            "schema": {
              "type": "string",
              "enum": [
                "blog",
                "ai-thoughts"
              ]
            }
          },
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "description": "Post slug from listPosts",
            "schema": {
              "type": "string",
              "pattern": "^[a-z0-9][a-z0-9-]{0,119}$"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Post",
            "headers": {
              "RateLimit": {
                "description": "IETF RateLimit header, e.g. `\"default\";r=59;t=60` (remaining is an estimate).",
                "schema": {
                  "type": "string"
                }
              },
              "RateLimit-Policy": {
                "description": "IETF RateLimit-Policy header: `\"default\";q=60;w=60`.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Post"
                }
              }
            }
          },
          "400": {
            "description": "Invalid collection",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded (60 requests per minute per IP)",
            "headers": {
              "Retry-After": {
                "schema": {
                  "type": "integer"
                },
                "description": "Seconds to wait"
              },
              "RateLimit": {
                "description": "IETF RateLimit header, e.g. `\"default\";r=59;t=60` (remaining is an estimate).",
                "schema": {
                  "type": "string"
                }
              },
              "RateLimit-Policy": {
                "description": "IETF RateLimit-Policy header: `\"default\";q=60;w=60`.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "schemas": {
      "Problem": {
        "type": "object",
        "description": "RFC 9457 problem details",
        "required": [
          "type",
          "title",
          "status",
          "detail",
          "code",
          "resolution"
        ],
        "properties": {
          "type": {
            "type": "string",
            "format": "uri"
          },
          "title": {
            "type": "string"
          },
          "status": {
            "type": "integer"
          },
          "detail": {
            "type": "string"
          },
          "code": {
            "type": "string",
            "enum": [
              "not_found",
              "method_not_allowed",
              "invalid_parameter",
              "rate_limited"
            ]
          },
          "resolution": {
            "type": "string"
          }
        }
      },
      "Info": {
        "type": "object",
        "required": [
          "name",
          "jobTitle",
          "description",
          "url",
          "email",
          "location",
          "sameAs",
          "skills"
        ],
        "properties": {
          "name": {
            "type": "string"
          },
          "jobTitle": {
            "type": "string"
          },
          "description": {
            "type": "string"
          },
          "url": {
            "type": "string",
            "format": "uri"
          },
          "image": {
            "type": "string",
            "format": "uri"
          },
          "email": {
            "type": "string",
            "format": "email"
          },
          "location": {
            "type": "object",
            "properties": {
              "city": {
                "type": "string"
              },
              "region": {
                "type": "string"
              },
              "country": {
                "type": "string"
              }
            }
          },
          "languages": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "sameAs": {
            "type": "array",
            "items": {
              "type": "string",
              "format": "uri"
            }
          },
          "skills": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "links": {
            "type": "object",
            "additionalProperties": {
              "type": "string",
              "format": "uri"
            }
          }
        }
      },
      "App": {
        "type": "object",
        "required": [
          "slug",
          "title",
          "subtitle",
          "platform",
          "storeUrl",
          "downloadUrl"
        ],
        "properties": {
          "slug": {
            "type": "string"
          },
          "title": {
            "type": "string"
          },
          "subtitle": {
            "type": "string"
          },
          "platform": {
            "type": "string",
            "enum": [
              "ios",
              "mac"
            ]
          },
          "storeUrl": {
            "type": "string",
            "format": "uri"
          },
          "downloadUrl": {
            "type": "string",
            "format": "uri"
          },
          "iconUrl": {
            "type": "string",
            "format": "uri"
          }
        }
      },
      "AppList": {
        "type": "object",
        "required": [
          "apps"
        ],
        "properties": {
          "apps": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/App"
            }
          }
        }
      },
      "Project": {
        "type": "object",
        "required": [
          "title",
          "description",
          "tags",
          "url"
        ],
        "properties": {
          "title": {
            "type": "string"
          },
          "description": {
            "type": "string"
          },
          "tags": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "url": {
            "type": "string",
            "format": "uri"
          }
        }
      },
      "ProjectList": {
        "type": "object",
        "required": [
          "projects"
        ],
        "properties": {
          "projects": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Project"
            }
          }
        }
      },
      "PostSummary": {
        "type": "object",
        "required": [
          "collection",
          "slug",
          "title",
          "date",
          "url"
        ],
        "properties": {
          "collection": {
            "type": "string",
            "enum": [
              "blog",
              "ai-thoughts"
            ]
          },
          "slug": {
            "type": "string"
          },
          "title": {
            "type": "string"
          },
          "date": {
            "type": "string",
            "format": "date"
          },
          "description": {
            "type": "string"
          },
          "url": {
            "type": "string",
            "format": "uri"
          }
        }
      },
      "PostList": {
        "type": "object",
        "required": [
          "collection",
          "posts"
        ],
        "properties": {
          "collection": {
            "type": "string",
            "enum": [
              "blog",
              "ai-thoughts"
            ]
          },
          "posts": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/PostSummary"
            }
          }
        }
      },
      "Post": {
        "allOf": [
          {
            "$ref": "#/components/schemas/PostSummary"
          },
          {
            "type": "object",
            "required": [
              "markdown"
            ],
            "properties": {
              "markdown": {
                "type": "string",
                "description": "Full content in Markdown"
              }
            }
          }
        ]
      }
    }
  }
}