{
  "openapi": "3.1.0",
  "info": {
    "title": "gabrimatic.info API",
    "version": "1.0.0",
    "description": "Read-only JSON API for Soroush Yousefpour's portfolio. Anonymous reads are welcome; OAuth 2.1 is optional and documented at /auth.md. Real-time protocol endpoints (MCP at /api/mcp, A2A at /api/a2a) speak JSON-RPC 2.0 and are described by their own discovery cards under /.well-known/, not by this document.",
    "contact": {
      "name": "Soroush Yousefpour",
      "url": "https://linkedin.com/in/gabrimatic"
    }
  },
  "externalDocs": {
    "description": "Agent Discovery directory",
    "url": "https://www.gabrimatic.info/agents.html"
  },
  "servers": [{ "url": "https://www.gabrimatic.info" }],
  "paths": {
    "/api": {
      "get": {
        "operationId": "getApiIndex",
        "summary": "Index of every live API endpoint",
        "responses": {
          "200": {
            "description": "Map of live endpoints plus documentation, quickstart, and catalog pointers.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": ["service", "endpoints"],
                  "properties": {
                    "service": { "type": "string" },
                    "endpoints": { "type": "object" },
                    "documentation": { "type": "string", "format": "uri" },
                    "quickstart": { "type": "string", "format": "uri" },
                    "catalog": { "type": "string", "format": "uri" }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/api/profile": {
      "get": {
        "operationId": "getProfile",
        "summary": "Structured profile (schema.org/Person shaped)",
        "responses": {
          "200": {
            "description": "Name, alternate names, location, current roles, expertise, languages, links, contact preferences, portfolio runtime metadata, and agent endpoints.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": ["name", "jobTitle", "location", "currentRoles", "links"],
                  "properties": {
                    "name": { "type": "string" },
                    "alternateNames": { "type": "array", "items": { "type": "string" } },
                    "jobTitle": { "type": "string" },
                    "location": { "type": "object" },
                    "summary": { "type": "string" },
                    "currentRoles": { "type": "array", "items": { "type": "object" } },
                    "expertise": { "type": "array", "items": { "type": "string" } },
                    "languages": { "type": "array", "items": { "type": "string" } },
                    "links": { "type": "object" },
                    "preferredContact": { "type": "object" },
                    "contactPoint": { "type": "array", "items": { "type": "object" } },
                    "agentEndpoints": { "type": "object" },
                    "portfolioSite": { "type": "object" }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/api/journey": {
      "get": {
        "operationId": "getJourney",
        "summary": "Ordered professional journey (schema.org/ItemList shaped)",
        "responses": {
          "200": {
            "description": "Nine roles. Affinidi is pinned at position 1; the rest are reverse-chronological.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": ["items"],
                  "properties": {
                    "items": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "required": ["position", "employer", "title", "period"],
                        "properties": {
                          "position": { "type": "integer" },
                          "employer": { "type": "string" },
                          "title": { "type": "string" },
                          "url": { "type": "string", "format": "uri" },
                          "period": { "type": "string" },
                          "employmentType": { "type": "string" },
                          "location": { "type": "string" },
                          "summary": { "type": "string" }
                        }
                      }
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/api/repos": {
      "get": {
        "operationId": "getRepos",
        "summary": "Live GitHub repositories with server-side categorization",
        "description": "Proxy of api.github.com/users/gabrimatic/repos, edge-cached 5 minutes with 5 minutes stale-while-revalidate. The payload's own cache object states the exact staleness policy; check it before flagging fetchedAt as stale.",
        "parameters": [
          {
            "name": "per_page",
            "in": "query",
            "schema": { "type": "integer", "minimum": 1, "maximum": 100, "default": 100 }
          },
          {
            "name": "sort",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": ["updated", "created", "pushed", "full_name"],
              "default": "updated"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Categorized repository list.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": ["status", "count", "repos"],
                  "properties": {
                    "source": { "type": "string", "format": "uri" },
                    "fetchedAt": { "type": "string", "format": "date-time" },
                    "cache": { "type": "object" },
                    "status": { "type": "integer" },
                    "count": { "type": "integer" },
                    "categories": { "type": "array", "items": { "type": "object" } },
                    "categoryCounts": { "type": "object" },
                    "visibleCategoryCounts": { "type": "object" },
                    "repos": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "required": ["name", "category", "hidden"],
                        "properties": {
                          "name": { "type": "string" },
                          "description": { "type": ["string", "null"] },
                          "html_url": { "type": "string", "format": "uri" },
                          "language": { "type": ["string", "null"] },
                          "stargazers_count": { "type": "integer" },
                          "category": { "type": "string", "enum": ["ai", "flutter", "native", "other"] },
                          "categoryLabel": { "type": "string" },
                          "priority": { "type": ["integer", "null"], "minimum": 0, "maximum": 5 },
                          "hidden": { "type": "boolean" }
                        }
                      }
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/api/contributions": {
      "get": {
        "operationId": "getContributions",
        "summary": "Merged external open-source contributions, grouped by project",
        "description": "Proxy of GitHub Search for merged pull requests authored by gabrimatic outside owned repositories, edge-cached 15 minutes with 15 minutes stale-while-revalidate. The payload's own cache object states the exact staleness policy.",
        "responses": {
          "200": {
            "description": "One entry per external project with merged PRs.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": ["status", "count", "projects"],
                  "properties": {
                    "source": { "type": "string", "format": "uri" },
                    "fetchedAt": { "type": "string", "format": "date-time" },
                    "cache": { "type": "object" },
                    "status": { "type": "integer" },
                    "count": { "type": "integer" },
                    "totalMergedPrCount": { "type": "integer" },
                    "projects": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "required": ["name", "fullName", "logoUrl", "mergedPrCount", "url"],
                        "properties": {
                          "name": { "type": "string" },
                          "fullName": { "type": "string" },
                          "logoUrl": { "type": "string" },
                          "mergedPrCount": { "type": "integer" },
                          "url": { "type": "string", "format": "uri" }
                        }
                      }
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/api/oauth/register": {
      "post": {
        "operationId": "registerOAuthClient",
        "summary": "Dynamic client registration (RFC 7591)",
        "description": "Anonymous reads never need OAuth; register only if your agent expects bearer-token discipline. Redirect URIs must be http(s) loopback URLs or same-site HTTPS URLs. The full recipe is at /auth.md.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "client_name": { "type": "string" },
                  "redirect_uris": { "type": "array", "items": { "type": "string", "format": "uri" } },
                  "grant_types": { "type": "array", "items": { "type": "string" } },
                  "scope": { "type": "string" }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Registered client with client_id and client_secret.",
            "content": { "application/json": { "schema": { "type": "object" } } }
          },
          "400": { "description": "Invalid client metadata or redirect URI." }
        }
      }
    },
    "/api/oauth/token": {
      "post": {
        "operationId": "issueOAuthToken",
        "summary": "Token endpoint (RFC 6749, OAuth 2.1)",
        "description": "Grants: authorization_code (PKCE S256 required), client_credentials, refresh_token. Access tokens are EdDSA-signed JWTs, 1 hour TTL, scopes mcp:read and mcp:tools. The authorization endpoint (GET /api/oauth/authorize) is a browser redirect flow described by /.well-known/oauth-authorization-server.",
        "requestBody": {
          "required": true,
          "content": {
            "application/x-www-form-urlencoded": { "schema": { "type": "object" } },
            "application/json": { "schema": { "type": "object" } }
          }
        },
        "responses": {
          "200": {
            "description": "access_token (Bearer), expires_in 3600, scope; refresh_token on the authorization_code grant.",
            "content": { "application/json": { "schema": { "type": "object" } } }
          },
          "400": { "description": "Invalid grant." },
          "401": { "description": "Client authentication failed." }
        }
      }
    }
  }
}
