{
  "openapi": "3.1.0",
  "info": {
    "title": "Run Cebu Events API",
    "version": "1.0.0",
    "description": "Discover public running events and, with a scoped API key, manage calendar entries. Public reads require no credentials. Write credentials are issued by Run Cebu and carry the smallest applicable role. Errors use RFC 9457 problem details and machine-readable responses publish RateLimit fields. The current stable contract is version 2026-08-27; clients may send Run-Cebu-API-Version: 2026-08-27 to pin behavior.",
    "license": {
      "name": "MIT",
      "identifier": "MIT"
    }
  },
  "externalDocs": {
    "description": "OAuth 2.0 Protected Resource Metadata with supported agent scopes.",
    "url": "https://runcebu.com/.well-known/oauth-protected-resource"
  },
  "servers": [
    {
      "url": "https://runcebu.com",
      "description": "Production API, stable contract version 2026-08-27"
    }
  ],
  "x-api-versioning": {
    "currentVersion": "2026-08-27",
    "defaultVersion": "2026-08-27",
    "versionHeader": "Run-Cebu-API-Version",
    "deprecationPolicy": "Run Cebu keeps documented public API behavior stable for agents. Breaking changes are introduced under a new Run-Cebu-API-Version value and old versions receive Deprecation and Sunset headers before removal."
  },
  "paths": {
    "/api/events": {
      "get": {
        "operationId": "listEvents",
        "summary": "List running events",
        "description": "Returns public event records ordered by date and ID.",
        "security": [],
        "parameters": [
          { "$ref": "#/components/parameters/ApiVersion" },
          { "$ref": "#/components/parameters/Limit" },
          { "$ref": "#/components/parameters/Offset" },
          { "$ref": "#/components/parameters/DateFrom" },
          { "$ref": "#/components/parameters/DateTo" },
          { "$ref": "#/components/parameters/Distance" },
          { "$ref": "#/components/parameters/Category" }
        ],
        "responses": {
          "200": {
            "description": "A page of events.",
            "headers": {
              "RateLimit-Policy": { "$ref": "#/components/headers/RateLimitPolicy" },
              "RateLimit": { "$ref": "#/components/headers/RateLimit" },
              "RateLimit-Limit": { "$ref": "#/components/headers/RateLimitLimit" },
              "RateLimit-Remaining": { "$ref": "#/components/headers/RateLimitRemaining" },
              "RateLimit-Reset": { "$ref": "#/components/headers/RateLimitReset" }
            },
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/EventList" }
              }
            }
          },
          "400": { "$ref": "#/components/responses/BadRequest" },
          "502": { "$ref": "#/components/responses/UpstreamError" },
          "default": { "$ref": "#/components/responses/UnexpectedError" }
        }
      },
      "post": {
        "operationId": "createEvent",
        "summary": "Create an event",
        "description": "Creates a public event record. Requires a scoped API key with events:write.",
        "security": [{ "AgentApiKey": [] }],
        "x-required-permission": "events:write",
        "parameters": [
          { "$ref": "#/components/parameters/ApiVersion" }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": { "$ref": "#/components/schemas/EventInput" }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Event created.",
            "headers": {
              "RateLimit-Policy": { "$ref": "#/components/headers/RateLimitPolicy" },
              "RateLimit": { "$ref": "#/components/headers/RateLimit" }
            },
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Event" } } }
          },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "403": { "$ref": "#/components/responses/Forbidden" },
          "422": { "$ref": "#/components/responses/ValidationError" },
          "502": { "$ref": "#/components/responses/UpstreamError" },
          "default": { "$ref": "#/components/responses/UnexpectedError" }
        }
      }
    },
    "/api/events/{eventId}": {
      "parameters": [{ "$ref": "#/components/parameters/EventId" }],
      "get": {
        "operationId": "getEvent",
        "summary": "Get one running event",
        "description": "Returns one public event record by UUID.",
        "security": [],
        "parameters": [
          { "$ref": "#/components/parameters/ApiVersion" }
        ],
        "responses": {
          "200": {
            "description": "Event found.",
            "headers": {
              "RateLimit-Policy": { "$ref": "#/components/headers/RateLimitPolicy" },
              "RateLimit": { "$ref": "#/components/headers/RateLimit" }
            },
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Event" } } }
          },
          "400": { "$ref": "#/components/responses/BadRequest" },
          "404": { "$ref": "#/components/responses/NotFound" },
          "502": { "$ref": "#/components/responses/UpstreamError" },
          "default": { "$ref": "#/components/responses/UnexpectedError" }
        }
      },
      "patch": {
        "operationId": "updateEvent",
        "summary": "Update an event",
        "description": "Updates supported public event fields. Requires a scoped API key with events:write.",
        "security": [{ "AgentApiKey": [] }],
        "x-required-permission": "events:write",
        "parameters": [
          { "$ref": "#/components/parameters/ApiVersion" }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": { "$ref": "#/components/schemas/EventPatch" }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Event updated.",
            "headers": {
              "RateLimit-Policy": { "$ref": "#/components/headers/RateLimitPolicy" },
              "RateLimit": { "$ref": "#/components/headers/RateLimit" }
            },
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Event" } } }
          },
          "400": { "$ref": "#/components/responses/BadRequest" },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "403": { "$ref": "#/components/responses/Forbidden" },
          "404": { "$ref": "#/components/responses/NotFound" },
          "422": { "$ref": "#/components/responses/ValidationError" },
          "502": { "$ref": "#/components/responses/UpstreamError" },
          "default": { "$ref": "#/components/responses/UnexpectedError" }
        }
      },
      "delete": {
        "operationId": "deleteEvent",
        "summary": "Delete an event",
        "description": "Deletes an event record. Requires a scoped API key with events:delete.",
        "security": [{ "AgentApiKey": [] }],
        "x-required-permission": "events:delete",
        "parameters": [
          { "$ref": "#/components/parameters/ApiVersion" }
        ],
        "responses": {
          "200": {
            "description": "Deleted event.",
            "headers": {
              "RateLimit-Policy": { "$ref": "#/components/headers/RateLimitPolicy" },
              "RateLimit": { "$ref": "#/components/headers/RateLimit" }
            },
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Event" } } }
          },
          "400": { "$ref": "#/components/responses/BadRequest" },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "403": { "$ref": "#/components/responses/Forbidden" },
          "404": { "$ref": "#/components/responses/NotFound" },
          "502": { "$ref": "#/components/responses/UpstreamError" },
          "default": { "$ref": "#/components/responses/UnexpectedError" }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "AgentApiKey": {
        "type": "apiKey",
        "in": "header",
        "name": "X-API-Key",
        "description": "A Run Cebu agent API key. Keys are stored as SHA-256 digests and assigned one or more roles.",
        "x-scopes-supported": ["events:read", "events:write", "events:delete"],
        "x-roles": {
          "events.reader": ["events:read"],
          "events.editor": ["events:read", "events:write"],
          "events.admin": ["events:read", "events:write", "events:delete"]
        }
      }
    },
    "parameters": {
      "ApiVersion": {
        "name": "Run-Cebu-API-Version",
        "in": "header",
        "required": false,
        "description": "Optional stable API contract version. Omit to use the current stable version. Agents can send 2026-08-27 to pin documented behavior.",
        "schema": { "type": "string", "enum": ["2026-08-27"], "default": "2026-08-27" }
      },
      "EventId": { "name": "eventId", "in": "path", "required": true, "schema": { "type": "string", "format": "uuid" } },
      "Limit": { "name": "limit", "in": "query", "schema": { "type": "integer", "minimum": 1, "maximum": 100, "default": 25 } },
      "Offset": { "name": "offset", "in": "query", "schema": { "type": "integer", "minimum": 0, "default": 0 } },
      "DateFrom": { "name": "date_from", "in": "query", "schema": { "type": "string", "format": "date" } },
      "DateTo": { "name": "date_to", "in": "query", "schema": { "type": "string", "format": "date" } },
      "Distance": { "name": "distance", "in": "query", "schema": { "type": "string" }, "example": "10K" },
      "Category": { "name": "category", "in": "query", "schema": { "type": "string" }, "example": "Trail" }
    },
    "headers": {
      "RateLimitPolicy": {
        "description": "IETF RateLimit policy. The public policy allows 120 requests per 60-second window.",
        "schema": { "type": "string", "example": "\"public\";q=120;w=60" }
      },
      "RateLimit": {
        "description": "Current IETF RateLimit state for the public policy.",
        "schema": { "type": "string", "example": "\"public\";r=119;t=60" }
      },
      "RateLimitLimit": {
        "description": "Compatibility field for clients that still read numeric rate limit headers.",
        "schema": { "type": "integer", "example": 120 }
      },
      "RateLimitRemaining": {
        "description": "Compatibility field showing approximate remaining requests in the current window.",
        "schema": { "type": "integer", "example": 119 }
      },
      "RateLimitReset": {
        "description": "Compatibility field showing seconds until the current window resets.",
        "schema": { "type": "integer", "example": 60 }
      },
      "Deprecation": {
        "description": "When present, indicates the API version or operation is deprecated.",
        "schema": { "type": "string", "example": "true" }
      },
      "Sunset": {
        "description": "When present, gives the planned retirement date for a deprecated API version or operation.",
        "schema": { "type": "string", "format": "date-time", "example": "2027-08-27T00:00:00Z" }
      }
    },
    "schemas": {
      "Event": {
        "type": "object",
        "required": ["id", "name", "location", "date", "distances", "categories", "featured"],
        "properties": {
          "id": { "type": "string", "format": "uuid" },
          "name": { "type": "string" },
          "link": { "type": ["string", "null"], "format": "uri" },
          "location": { "type": "string" },
          "distances": { "type": "array", "items": { "type": "string" } },
          "categories": { "type": "array", "items": { "type": "string" } },
          "date": { "type": "string", "format": "date" },
          "featured": { "type": "boolean" },
          "image_url": { "type": ["string", "null"], "format": "uri" },
          "facebook_url": { "type": ["string", "null"], "format": "uri" },
          "coming_soon": { "type": "boolean" },
          "created_at": { "type": "string", "format": "date-time" },
          "updated_at": { "type": "string", "format": "date-time" }
        }
      },
      "EventInput": {
        "type": "object",
        "required": ["name", "location", "date", "distances"],
        "properties": {
          "name": { "type": "string", "minLength": 1 },
          "link": { "type": ["string", "null"], "format": "uri" },
          "location": { "type": "string", "minLength": 1 },
          "distances": { "type": "array", "items": { "type": "string" } },
          "categories": { "type": "array", "items": { "type": "string" }, "default": [] },
          "date": { "type": "string", "format": "date" },
          "featured": { "type": "boolean", "default": false },
          "image_url": { "type": ["string", "null"], "format": "uri" },
          "facebook_url": { "type": ["string", "null"], "format": "uri" },
          "coming_soon": { "type": "boolean", "default": false }
        },
        "additionalProperties": false
      },
      "EventPatch": {
        "type": "object",
        "minProperties": 1,
        "properties": {
          "name": { "type": "string", "minLength": 1 },
          "link": { "type": ["string", "null"], "format": "uri" },
          "location": { "type": "string", "minLength": 1 },
          "distances": { "type": "array", "items": { "type": "string" } },
          "categories": { "type": "array", "items": { "type": "string" } },
          "date": { "type": "string", "format": "date" },
          "featured": { "type": "boolean" },
          "image_url": { "type": ["string", "null"], "format": "uri" },
          "facebook_url": { "type": ["string", "null"], "format": "uri" },
          "coming_soon": { "type": "boolean" }
        },
        "additionalProperties": false
      },
      "EventList": {
        "type": "object",
        "required": ["data", "pagination"],
        "properties": {
          "data": { "type": "array", "items": { "$ref": "#/components/schemas/Event" } },
          "pagination": {
            "type": "object",
            "required": ["limit", "offset", "total"],
            "properties": {
              "limit": { "type": "integer" },
              "offset": { "type": "integer" },
              "total": { "type": ["integer", "null"] }
            }
          }
        }
      },
      "ProblemDetails": {
        "type": "object",
        "required": ["type", "title", "status", "detail", "code", "error"],
        "properties": {
          "type": { "type": "string", "format": "uri" },
          "title": { "type": "string" },
          "status": { "type": "integer" },
          "detail": { "type": "string" },
          "instance": { "type": "string" },
          "code": { "type": "string" },
          "error": {
            "type": "object",
            "required": ["code", "message"],
            "properties": {
              "code": { "type": "string" },
              "message": { "type": "string" }
            }
          }
        }
      }
    },
    "responses": {
      "BadRequest": {
        "description": "Invalid request.",
        "headers": {
          "RateLimit-Policy": { "$ref": "#/components/headers/RateLimitPolicy" },
          "RateLimit": { "$ref": "#/components/headers/RateLimit" }
        },
        "content": { "application/problem+json": { "schema": { "$ref": "#/components/schemas/ProblemDetails" } } }
      },
      "Unauthorized": {
        "description": "Missing or invalid API key.",
        "headers": {
          "RateLimit-Policy": { "$ref": "#/components/headers/RateLimitPolicy" },
          "RateLimit": { "$ref": "#/components/headers/RateLimit" }
        },
        "content": { "application/problem+json": { "schema": { "$ref": "#/components/schemas/ProblemDetails" } } }
      },
      "Forbidden": {
        "description": "API key lacks the required role.",
        "headers": {
          "RateLimit-Policy": { "$ref": "#/components/headers/RateLimitPolicy" },
          "RateLimit": { "$ref": "#/components/headers/RateLimit" }
        },
        "content": { "application/problem+json": { "schema": { "$ref": "#/components/schemas/ProblemDetails" } } }
      },
      "NotFound": {
        "description": "Event not found.",
        "headers": {
          "RateLimit-Policy": { "$ref": "#/components/headers/RateLimitPolicy" },
          "RateLimit": { "$ref": "#/components/headers/RateLimit" }
        },
        "content": { "application/problem+json": { "schema": { "$ref": "#/components/schemas/ProblemDetails" } } }
      },
      "ValidationError": {
        "description": "Invalid event data.",
        "headers": {
          "RateLimit-Policy": { "$ref": "#/components/headers/RateLimitPolicy" },
          "RateLimit": { "$ref": "#/components/headers/RateLimit" }
        },
        "content": { "application/problem+json": { "schema": { "$ref": "#/components/schemas/ProblemDetails" } } }
      },
      "UpstreamError": {
        "description": "Events service unavailable.",
        "headers": {
          "RateLimit-Policy": { "$ref": "#/components/headers/RateLimitPolicy" },
          "RateLimit": { "$ref": "#/components/headers/RateLimit" }
        },
        "content": { "application/problem+json": { "schema": { "$ref": "#/components/schemas/ProblemDetails" } } }
      },
      "UnexpectedError": {
        "description": "Unexpected machine-readable problem response.",
        "headers": {
          "RateLimit-Policy": { "$ref": "#/components/headers/RateLimitPolicy" },
          "RateLimit": { "$ref": "#/components/headers/RateLimit" },
          "Deprecation": { "$ref": "#/components/headers/Deprecation" },
          "Sunset": { "$ref": "#/components/headers/Sunset" }
        },
        "content": { "application/problem+json": { "schema": { "$ref": "#/components/schemas/ProblemDetails" } } }
      }
    }
  }
}
