{
  "openapi": "3.1.0",
  "info": {
    "title": "Andrew Campi portfolio API",
    "version": "1.0.0",
    "description": "Read-only JSON for Andrew Campi's public portfolio. The only scope is portfolio:read and it is already granted. Do not send an Authorization header. No token is issued. RateLimit-Limit, RateLimit-Remaining, and RateLimit-Reset are sent on every response. A 429 also sends Retry-After. Errors use the ErrorEnvelope schema. The current version is the /api/v1 path. It is not deprecated and does not send Deprecation or Sunset headers. The versioning and deprecation policy is https://andrewcampi.com/api-versioning.html."
  },
  "servers": [
    {
      "url": "https://andrewcampi.com"
    }
  ],
  "security": [
    {
      "portfolioRead": []
    }
  ],
  "paths": {
    "/api/v1": {
      "get": {
        "operationId": "getApiDirectory",
        "summary": "List the portfolio API",
        "description": "Returns the public scope, the endpoint list, and links to OpenAPI, the developer guide, and MCP.",
        "security": [
          {
            "portfolioRead": []
          }
        ],
        "responses": {
          "200": {
            "description": "Success.",
            "headers": {
              "RateLimit-Limit": {
                "description": "Maximum requests per minute for this client IP.",
                "schema": {
                  "type": "integer"
                }
              },
              "RateLimit-Remaining": {
                "description": "Requests remaining in the current one-minute window.",
                "schema": {
                  "type": "integer"
                }
              },
              "RateLimit-Reset": {
                "description": "Seconds until the oldest request in the window expires.",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "name",
                    "authentication",
                    "scope",
                    "tokenRequired",
                    "endpoints"
                  ],
                  "properties": {
                    "name": {
                      "type": "string"
                    },
                    "authentication": {
                      "type": "string"
                    },
                    "scope": {
                      "type": "string"
                    },
                    "tokenRequired": {
                      "type": "boolean"
                    },
                    "openapi": {
                      "type": "string"
                    },
                    "docs": {
                      "type": "string"
                    },
                    "mcp": {
                      "type": "string"
                    },
                    "versioningPolicy": {
                      "type": "string",
                      "description": "URL of the versioning and deprecation policy."
                    },
                    "endpoints": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "required": [
                          "method",
                          "path",
                          "operationId"
                        ],
                        "properties": {
                          "method": {
                            "type": "string"
                          },
                          "path": {
                            "type": "string"
                          },
                          "operationId": {
                            "type": "string"
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "429": {
            "description": "The caller exceeded 120 requests in the current minute.",
            "headers": {
              "RateLimit-Limit": {
                "description": "Maximum requests per minute for this client IP.",
                "schema": {
                  "type": "integer"
                }
              },
              "RateLimit-Remaining": {
                "description": "Requests remaining in the current one-minute window.",
                "schema": {
                  "type": "integer"
                }
              },
              "RateLimit-Reset": {
                "description": "Seconds until the oldest request in the window expires.",
                "schema": {
                  "type": "integer"
                }
              },
              "Retry-After": {
                "description": "Seconds to wait before retrying the same request.",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "error"
                  ],
                  "properties": {
                    "error": {
                      "type": "object",
                      "required": [
                        "code",
                        "message",
                        "hint"
                      ],
                      "properties": {
                        "code": {
                          "type": "string",
                          "description": "Stable error code such as not_found or rate_limited."
                        },
                        "message": {
                          "type": "string"
                        },
                        "hint": {
                          "type": "string",
                          "description": "What the caller should do next."
                        }
                      }
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/profile": {
      "get": {
        "operationId": "getProfile",
        "summary": "Get Andrew Campi's public profile",
        "description": "Returns name, current role, location, education, and work history. Comft highlights are an empty list.",
        "security": [
          {
            "portfolioRead": []
          }
        ],
        "responses": {
          "200": {
            "description": "Success.",
            "headers": {
              "RateLimit-Limit": {
                "description": "Maximum requests per minute for this client IP.",
                "schema": {
                  "type": "integer"
                }
              },
              "RateLimit-Remaining": {
                "description": "Requests remaining in the current one-minute window.",
                "schema": {
                  "type": "integer"
                }
              },
              "RateLimit-Reset": {
                "description": "Seconds until the oldest request in the window expires.",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "name",
                    "jobTitle",
                    "company",
                    "url",
                    "summary",
                    "location",
                    "experience"
                  ],
                  "properties": {
                    "name": {
                      "type": "string"
                    },
                    "jobTitle": {
                      "type": "string"
                    },
                    "company": {
                      "type": "string"
                    },
                    "url": {
                      "type": "string"
                    },
                    "summary": {
                      "type": "string"
                    },
                    "location": {
                      "type": "object",
                      "required": [
                        "locality",
                        "region",
                        "country"
                      ],
                      "properties": {
                        "locality": {
                          "type": "string"
                        },
                        "region": {
                          "type": "string"
                        },
                        "country": {
                          "type": "string"
                        }
                      }
                    },
                    "github": {
                      "type": "string"
                    },
                    "linkedin": {
                      "type": "string"
                    },
                    "sameAs": {
                      "type": "array",
                      "items": {
                        "type": "string"
                      }
                    },
                    "education": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "required": [
                          "school"
                        ],
                        "properties": {
                          "school": {
                            "type": "string"
                          },
                          "credential": {
                            "type": "string"
                          },
                          "year": {
                            "type": "string"
                          }
                        }
                      }
                    },
                    "experience": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "required": [
                          "title",
                          "company",
                          "start",
                          "highlights"
                        ],
                        "properties": {
                          "title": {
                            "type": "string"
                          },
                          "company": {
                            "type": "string"
                          },
                          "start": {
                            "type": "string",
                            "description": "YYYY-MM."
                          },
                          "end": {
                            "type": [
                              "string",
                              "null"
                            ],
                            "description": "YYYY-MM, or null when this is the current role."
                          },
                          "highlights": {
                            "type": "array",
                            "items": {
                              "type": "string"
                            }
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "429": {
            "description": "The caller exceeded 120 requests in the current minute.",
            "headers": {
              "RateLimit-Limit": {
                "description": "Maximum requests per minute for this client IP.",
                "schema": {
                  "type": "integer"
                }
              },
              "RateLimit-Remaining": {
                "description": "Requests remaining in the current one-minute window.",
                "schema": {
                  "type": "integer"
                }
              },
              "RateLimit-Reset": {
                "description": "Seconds until the oldest request in the window expires.",
                "schema": {
                  "type": "integer"
                }
              },
              "Retry-After": {
                "description": "Seconds to wait before retrying the same request.",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "error"
                  ],
                  "properties": {
                    "error": {
                      "type": "object",
                      "required": [
                        "code",
                        "message",
                        "hint"
                      ],
                      "properties": {
                        "code": {
                          "type": "string",
                          "description": "Stable error code such as not_found or rate_limited."
                        },
                        "message": {
                          "type": "string"
                        },
                        "hint": {
                          "type": "string",
                          "description": "What the caller should do next."
                        }
                      }
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/experience": {
      "get": {
        "operationId": "getExperience",
        "summary": "Get work history",
        "description": "Returns the experience array. end is null for the current role.",
        "security": [
          {
            "portfolioRead": []
          }
        ],
        "responses": {
          "200": {
            "description": "Success.",
            "headers": {
              "RateLimit-Limit": {
                "description": "Maximum requests per minute for this client IP.",
                "schema": {
                  "type": "integer"
                }
              },
              "RateLimit-Remaining": {
                "description": "Requests remaining in the current one-minute window.",
                "schema": {
                  "type": "integer"
                }
              },
              "RateLimit-Reset": {
                "description": "Seconds until the oldest request in the window expires.",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "experience"
                  ],
                  "properties": {
                    "experience": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "required": [
                          "title",
                          "company",
                          "start",
                          "highlights"
                        ],
                        "properties": {
                          "title": {
                            "type": "string"
                          },
                          "company": {
                            "type": "string"
                          },
                          "start": {
                            "type": "string",
                            "description": "YYYY-MM."
                          },
                          "end": {
                            "type": [
                              "string",
                              "null"
                            ],
                            "description": "YYYY-MM, or null when this is the current role."
                          },
                          "highlights": {
                            "type": "array",
                            "items": {
                              "type": "string"
                            }
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "429": {
            "description": "The caller exceeded 120 requests in the current minute.",
            "headers": {
              "RateLimit-Limit": {
                "description": "Maximum requests per minute for this client IP.",
                "schema": {
                  "type": "integer"
                }
              },
              "RateLimit-Remaining": {
                "description": "Requests remaining in the current one-minute window.",
                "schema": {
                  "type": "integer"
                }
              },
              "RateLimit-Reset": {
                "description": "Seconds until the oldest request in the window expires.",
                "schema": {
                  "type": "integer"
                }
              },
              "Retry-After": {
                "description": "Seconds to wait before retrying the same request.",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "error"
                  ],
                  "properties": {
                    "error": {
                      "type": "object",
                      "required": [
                        "code",
                        "message",
                        "hint"
                      ],
                      "properties": {
                        "code": {
                          "type": "string",
                          "description": "Stable error code such as not_found or rate_limited."
                        },
                        "message": {
                          "type": "string"
                        },
                        "hint": {
                          "type": "string",
                          "description": "What the caller should do next."
                        }
                      }
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/projects": {
      "get": {
        "operationId": "listProjects",
        "summary": "List portfolio projects",
        "description": "Returns every project page with a summary, HTML URL, markdown URL, and GitHub URL when one exists.",
        "security": [
          {
            "portfolioRead": []
          }
        ],
        "responses": {
          "200": {
            "description": "Success.",
            "headers": {
              "RateLimit-Limit": {
                "description": "Maximum requests per minute for this client IP.",
                "schema": {
                  "type": "integer"
                }
              },
              "RateLimit-Remaining": {
                "description": "Requests remaining in the current one-minute window.",
                "schema": {
                  "type": "integer"
                }
              },
              "RateLimit-Reset": {
                "description": "Seconds until the oldest request in the window expires.",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "projects"
                  ],
                  "properties": {
                    "projects": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "required": [
                          "slug",
                          "title",
                          "summary",
                          "url",
                          "markdownUrl"
                        ],
                        "properties": {
                          "slug": {
                            "type": "string"
                          },
                          "title": {
                            "type": "string"
                          },
                          "summary": {
                            "type": "string"
                          },
                          "url": {
                            "type": "string"
                          },
                          "markdownUrl": {
                            "type": "string"
                          },
                          "github": {
                            "type": [
                              "string",
                              "null"
                            ]
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "429": {
            "description": "The caller exceeded 120 requests in the current minute.",
            "headers": {
              "RateLimit-Limit": {
                "description": "Maximum requests per minute for this client IP.",
                "schema": {
                  "type": "integer"
                }
              },
              "RateLimit-Remaining": {
                "description": "Requests remaining in the current one-minute window.",
                "schema": {
                  "type": "integer"
                }
              },
              "RateLimit-Reset": {
                "description": "Seconds until the oldest request in the window expires.",
                "schema": {
                  "type": "integer"
                }
              },
              "Retry-After": {
                "description": "Seconds to wait before retrying the same request.",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "error"
                  ],
                  "properties": {
                    "error": {
                      "type": "object",
                      "required": [
                        "code",
                        "message",
                        "hint"
                      ],
                      "properties": {
                        "code": {
                          "type": "string",
                          "description": "Stable error code such as not_found or rate_limited."
                        },
                        "message": {
                          "type": "string"
                        },
                        "hint": {
                          "type": "string",
                          "description": "What the caller should do next."
                        }
                      }
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/projects/{slug}": {
      "get": {
        "operationId": "getProject",
        "summary": "Get one portfolio project",
        "description": "Returns one project by slug. Unknown slugs return a JSON not_found error.",
        "security": [
          {
            "portfolioRead": []
          }
        ],
        "responses": {
          "200": {
            "description": "Success.",
            "headers": {
              "RateLimit-Limit": {
                "description": "Maximum requests per minute for this client IP.",
                "schema": {
                  "type": "integer"
                }
              },
              "RateLimit-Remaining": {
                "description": "Requests remaining in the current one-minute window.",
                "schema": {
                  "type": "integer"
                }
              },
              "RateLimit-Reset": {
                "description": "Seconds until the oldest request in the window expires.",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "slug",
                    "title",
                    "summary",
                    "url",
                    "markdownUrl"
                  ],
                  "properties": {
                    "slug": {
                      "type": "string"
                    },
                    "title": {
                      "type": "string"
                    },
                    "summary": {
                      "type": "string"
                    },
                    "url": {
                      "type": "string"
                    },
                    "markdownUrl": {
                      "type": "string"
                    },
                    "github": {
                      "type": [
                        "string",
                        "null"
                      ]
                    }
                  }
                }
              }
            }
          },
          "429": {
            "description": "The caller exceeded 120 requests in the current minute.",
            "headers": {
              "RateLimit-Limit": {
                "description": "Maximum requests per minute for this client IP.",
                "schema": {
                  "type": "integer"
                }
              },
              "RateLimit-Remaining": {
                "description": "Requests remaining in the current one-minute window.",
                "schema": {
                  "type": "integer"
                }
              },
              "RateLimit-Reset": {
                "description": "Seconds until the oldest request in the window expires.",
                "schema": {
                  "type": "integer"
                }
              },
              "Retry-After": {
                "description": "Seconds to wait before retrying the same request.",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "error"
                  ],
                  "properties": {
                    "error": {
                      "type": "object",
                      "required": [
                        "code",
                        "message",
                        "hint"
                      ],
                      "properties": {
                        "code": {
                          "type": "string",
                          "description": "Stable error code such as not_found or rate_limited."
                        },
                        "message": {
                          "type": "string"
                        },
                        "hint": {
                          "type": "string",
                          "description": "What the caller should do next."
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "A machine-readable error.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "error"
                  ],
                  "properties": {
                    "error": {
                      "type": "object",
                      "required": [
                        "code",
                        "message",
                        "hint"
                      ],
                      "properties": {
                        "code": {
                          "type": "string",
                          "description": "Stable error code such as not_found or rate_limited."
                        },
                        "message": {
                          "type": "string"
                        },
                        "hint": {
                          "type": "string",
                          "description": "What the caller should do next."
                        }
                      }
                    }
                  }
                }
              }
            }
          }
        },
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "description": "Project slug, such as cecropia or system1-server.",
            "schema": {
              "type": "string"
            }
          }
        ]
      }
    }
  },
  "components": {
    "securitySchemes": {
      "portfolioRead": {
        "type": "oauth2",
        "description": "The portfolio:read scope is public. This scheme exists so clients can see the scope name. There is no authorization server and no access token. Call the API with no Authorization header. The authorizationUrl below is the human developer guide, not a token endpoint.",
        "flows": {
          "implicit": {
            "authorizationUrl": "https://andrewcampi.com/developers.html",
            "scopes": {
              "portfolio:read": "Read the public portfolio profile, experience, and project pages."
            }
          }
        }
      }
    },
    "schemas": {
      "ErrorEnvelope": {
        "type": "object",
        "required": [
          "error"
        ],
        "properties": {
          "error": {
            "type": "object",
            "required": [
              "code",
              "message",
              "hint"
            ],
            "properties": {
              "code": {
                "type": "string",
                "description": "Stable error code such as not_found or rate_limited."
              },
              "message": {
                "type": "string"
              },
              "hint": {
                "type": "string",
                "description": "What the caller should do next."
              }
            }
          }
        }
      },
      "Experience": {
        "type": "object",
        "required": [
          "title",
          "company",
          "start",
          "highlights"
        ],
        "properties": {
          "title": {
            "type": "string"
          },
          "company": {
            "type": "string"
          },
          "start": {
            "type": "string",
            "description": "YYYY-MM."
          },
          "end": {
            "type": [
              "string",
              "null"
            ],
            "description": "YYYY-MM, or null when this is the current role."
          },
          "highlights": {
            "type": "array",
            "items": {
              "type": "string"
            }
          }
        }
      },
      "Education": {
        "type": "object",
        "required": [
          "school"
        ],
        "properties": {
          "school": {
            "type": "string"
          },
          "credential": {
            "type": "string"
          },
          "year": {
            "type": "string"
          }
        }
      },
      "Location": {
        "type": "object",
        "required": [
          "locality",
          "region",
          "country"
        ],
        "properties": {
          "locality": {
            "type": "string"
          },
          "region": {
            "type": "string"
          },
          "country": {
            "type": "string"
          }
        }
      },
      "Profile": {
        "type": "object",
        "required": [
          "name",
          "jobTitle",
          "company",
          "url",
          "summary",
          "location",
          "experience"
        ],
        "properties": {
          "name": {
            "type": "string"
          },
          "jobTitle": {
            "type": "string"
          },
          "company": {
            "type": "string"
          },
          "url": {
            "type": "string"
          },
          "summary": {
            "type": "string"
          },
          "location": {
            "$ref": "#/components/schemas/Location"
          },
          "github": {
            "type": "string"
          },
          "linkedin": {
            "type": "string"
          },
          "sameAs": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "education": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Education"
            }
          },
          "experience": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Experience"
            }
          }
        }
      },
      "ExperienceList": {
        "type": "object",
        "required": [
          "experience"
        ],
        "properties": {
          "experience": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Experience"
            }
          }
        }
      },
      "Project": {
        "type": "object",
        "required": [
          "slug",
          "title",
          "summary",
          "url",
          "markdownUrl"
        ],
        "properties": {
          "slug": {
            "type": "string"
          },
          "title": {
            "type": "string"
          },
          "summary": {
            "type": "string"
          },
          "url": {
            "type": "string"
          },
          "markdownUrl": {
            "type": "string"
          },
          "github": {
            "type": [
              "string",
              "null"
            ]
          }
        }
      },
      "ProjectList": {
        "type": "object",
        "required": [
          "projects"
        ],
        "properties": {
          "projects": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Project"
            }
          }
        }
      },
      "ApiDirectory": {
        "type": "object",
        "required": [
          "name",
          "authentication",
          "scope",
          "tokenRequired",
          "endpoints"
        ],
        "properties": {
          "name": {
            "type": "string"
          },
          "authentication": {
            "type": "string"
          },
          "scope": {
            "type": "string"
          },
          "tokenRequired": {
            "type": "boolean"
          },
          "openapi": {
            "type": "string"
          },
          "docs": {
            "type": "string"
          },
          "mcp": {
            "type": "string"
          },
          "versioningPolicy": {
            "type": "string",
            "description": "URL of the versioning and deprecation policy."
          },
          "endpoints": {
            "type": "array",
            "items": {
              "type": "object",
              "required": [
                "method",
                "path",
                "operationId"
              ],
              "properties": {
                "method": {
                  "type": "string"
                },
                "path": {
                  "type": "string"
                },
                "operationId": {
                  "type": "string"
                }
              }
            }
          }
        }
      }
    }
  }
}
