{
  "openapi": "3.1.0",
  "info": {
    "title": "ucalyptus.me Agent API",
    "version": "1.0.0",
    "summary": "Stateless OAuth 2.0 client_credentials server and one gated profile endpoint for ucalyptus.me.",
    "description": "A real, minimal agent-facing API for Sayantan Das's personal site. Any agent can register an anonymous client, exchange it for a short-lived ES256 Bearer token, and read an extended CV/availability profile. No user accounts, no database — the client secret is HMAC-derived, not stored. See /auth.md for the human-readable flow.",
    "contact": {
      "name": "Sayantan Das",
      "url": "https://ucalyptus.me",
      "email": "hello@ucalyptus.me"
    },
    "license": {
      "name": "MIT",
      "url": "https://ucalyptus.me/LICENSE"
    }
  },
  "servers": [
    { "url": "https://ucalyptus.me", "description": "Apex host" },
    { "url": "https://www.ucalyptus.me", "description": "www host" }
  ],
  "externalDocs": {
    "description": "auth.md — human-readable registration + token flow",
    "url": "https://ucalyptus.me/auth.md"
  },
  "tags": [
    { "name": "oauth", "description": "Anonymous client registration and token issuance (RFC 6749 §4.4)." },
    { "name": "profile", "description": "Bearer-gated extended profile data." }
  ],
  "paths": {
    "/oauth/register": {
      "post": {
        "tags": ["oauth"],
        "operationId": "registerClient",
        "summary": "Register an anonymous client",
        "description": "Returns a client_id and an HMAC-derived client_secret. Stateless: the secret is recomputed at token time, never persisted. Losing the client_id means losing the secret — just register again.",
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "client_name": {
                    "type": "string",
                    "description": "Optional human-readable label for the client, echoed back only."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Client registered.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/RegisterResponse" }
              }
            }
          }
        }
      }
    },
    "/oauth/token": {
      "post": {
        "tags": ["oauth"],
        "operationId": "issueToken",
        "summary": "Exchange client credentials for a Bearer token",
        "description": "grant_type must be client_credentials. Returns a 15-minute ES256 JWT scoped to read:extended. Public verification key is at /.well-known/jwks.json.",
        "requestBody": {
          "required": true,
          "content": {
            "application/x-www-form-urlencoded": {
              "schema": { "$ref": "#/components/schemas/TokenRequest" }
            },
            "application/json": {
              "schema": { "$ref": "#/components/schemas/TokenRequest" }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Access token issued.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/TokenResponse" }
              }
            }
          },
          "400": {
            "description": "unsupported_grant_type or invalid_request.",
            "content": {
              "application/json": { "schema": { "$ref": "#/components/schemas/OAuthError" } }
            }
          },
          "401": {
            "description": "invalid_client — client_id/client_secret did not match.",
            "content": {
              "application/json": { "schema": { "$ref": "#/components/schemas/OAuthError" } }
            }
          }
        }
      }
    },
    "/api/agent/full-profile": {
      "get": {
        "tags": ["profile"],
        "operationId": "getFullProfile",
        "summary": "Extended CV and availability profile (gated)",
        "description": "Fuller CV/availability data than the public /api/site.json — service/leadership history plus what the author is currently open to hearing about from an agent. Requires a Bearer token with the read:extended scope.",
        "security": [{ "agent_oauth": ["read:extended"] }],
        "responses": {
          "200": {
            "description": "Extended profile JSON.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/FullProfile" }
              }
            }
          },
          "401": {
            "description": "Missing/invalid/expired token. WWW-Authenticate points at the Protected Resource Metadata.",
            "content": {
              "application/json": { "schema": { "$ref": "#/components/schemas/OAuthError" } }
            }
          },
          "403": {
            "description": "insufficient_scope — token lacks read:extended.",
            "content": {
              "application/json": { "schema": { "$ref": "#/components/schemas/OAuthError" } }
            }
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "agent_oauth": {
        "type": "oauth2",
        "description": "Stateless client_credentials flow. Register at /oauth/register, token at /oauth/token.",
        "flows": {
          "clientCredentials": {
            "tokenUrl": "https://ucalyptus.me/oauth/token",
            "scopes": {
              "read:extended": "Read the extended CV/availability profile."
            }
          }
        }
      }
    },
    "schemas": {
      "RegisterResponse": {
        "type": "object",
        "properties": {
          "client_id": { "type": "string", "format": "uuid" },
          "client_secret": { "type": "string" },
          "client_name": { "type": ["string", "null"] },
          "token_endpoint": { "type": "string", "format": "uri" },
          "grant_types_supported": { "type": "array", "items": { "type": "string" } },
          "scope": { "type": "string" },
          "note": { "type": "string" }
        }
      },
      "TokenRequest": {
        "type": "object",
        "required": ["grant_type", "client_id", "client_secret"],
        "properties": {
          "grant_type": { "type": "string", "enum": ["client_credentials"] },
          "client_id": { "type": "string" },
          "client_secret": { "type": "string" }
        }
      },
      "TokenResponse": {
        "type": "object",
        "properties": {
          "access_token": { "type": "string", "description": "ES256 JWT." },
          "token_type": { "type": "string", "enum": ["Bearer"] },
          "expires_in": { "type": "integer", "example": 900 },
          "scope": { "type": "string", "example": "read:extended" }
        }
      },
      "FullProfile": {
        "type": "object",
        "description": "Extended profile payload plus a _meta block describing the token it was served under.",
        "properties": {
          "_meta": {
            "type": "object",
            "properties": {
              "issued_to": { "type": "string" },
              "scope": { "type": "string" },
              "expires_at": { "type": "string", "format": "date-time" }
            }
          }
        },
        "additionalProperties": true
      },
      "OAuthError": {
        "type": "object",
        "properties": {
          "error": { "type": "string" },
          "error_description": { "type": "string" }
        }
      }
    }
  }
}
