{
  "openapi": "3.1.0",
  "info": {
    "title": "AgentCal API",
    "version": "0.5.0",
    "description": "The AgentCal HTTP API: calendars for AI agents. Agents mint their own identity and credentials, create calendars, write events, share access with humans and other agents, and compose calendars as layers. Plain JSON over HTTPS; no SDK required.\n\nAgent-readable quickstart: https://youragentcal.com/start.md",
    "contact": { "url": "https://youragentcal.com" }
  },
  "servers": [{ "url": "https://youragentcal.com" }],
  "security": [{ "agentToken": [] }],
  "tags": [
    { "name": "Identity", "description": "Minting and credentials. An agent mints once, keeps the token, and can manage extra keys." },
    { "name": "Calendars", "description": "Create calendars and manage their settings (name, slug, timezone, visibility, purpose, lenses)." },
    { "name": "Events", "description": "One-off, recurring, and someday events on a calendar." },
    { "name": "Grants", "description": "Per-calendar access for other agents: owner, writer, or reader." },
    { "name": "Access requests", "description": "Ask for access to someone else's calendar; owners approve or deny." },
    { "name": "Share views", "description": "Filtered, capability-URL projections of a calendar at /v/{slug}." },
    { "name": "Layers", "description": "Attach other calendars as layers to compose a project calendar." }
  ],
  "paths": {
    "/v1/mint": {
      "post": {
        "tags": ["Identity"],
        "operationId": "mint",
        "summary": "Mint an agent identity and first calendar",
        "description": "Public, no auth. Returns the agent token once; store it. Anonymous mints also return a single-use claim_url (7 days) a human can open to link the agent to their account. With an onboard_token (from a signed-in human's Get Started flow) the agent is linked immediately.",
        "security": [],
        "parameters": [{ "$ref": "#/components/parameters/IdempotencyKey" }],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": ["name"],
                "properties": {
                  "name": { "type": "string", "description": "Agent name, non-empty." },
                  "calendar": {
                    "type": "object",
                    "properties": {
                      "name": { "type": "string", "description": "Calendar name; defaults to the agent name." },
                      "timezone": { "type": "string", "description": "IANA timezone; defaults to UTC.", "examples": ["America/Montreal"] }
                    }
                  },
                  "onboard_token": { "type": "string", "description": "ac_onboard_… token from a signed-in human; pre-links them as owner. 30-minute validity." }
                }
              },
              "example": { "name": "Claude", "calendar": { "name": "Launch planning", "timezone": "America/Montreal" } }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Minted. The token appears only in this response (idempotent replays included, exactly once per key).",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "token": { "type": "string", "description": "ac_agent_… bearer token. Shown once." },
                    "agent": { "$ref": "#/components/schemas/Agent" },
                    "credential": { "$ref": "#/components/schemas/Credential" },
                    "calendar": { "$ref": "#/components/schemas/Calendar" },
                    "claim_url": { "type": "string", "description": "Anonymous mints only: single-use link for a human to claim this agent." }
                  }
                }
              }
            }
          },
          "400": { "$ref": "#/components/responses/BadRequest" },
          "401": { "description": "Invalid onboard_token.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }
        }
      }
    },
    "/v1/me": {
      "get": {
        "tags": ["Identity"],
        "operationId": "getMe",
        "summary": "Who am I",
        "description": "The authenticated agent, its credential, and every calendar it can touch (with roles and purpose previews). Requires scope agent:read.",
        "responses": {
          "200": {
            "description": "Identity and grants.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "agent": { "$ref": "#/components/schemas/Agent" },
                    "credential": { "$ref": "#/components/schemas/Credential" },
                    "grants": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "calendar_id": { "type": "string" },
                          "slug": { "type": "string" },
                          "role": { "$ref": "#/components/schemas/Role" },
                          "purpose_preview": { "type": "string" }
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "403": { "$ref": "#/components/responses/Forbidden" }
        }
      }
    },
    "/v1/agent-credentials": {
      "get": {
        "tags": ["Identity"],
        "operationId": "listCredentials",
        "summary": "List credentials",
        "description": "Metadata for every credential of this agent (never the secrets). Requires scope agent:read.",
        "responses": {
          "200": {
            "description": "Credential list.",
            "content": { "application/json": { "schema": { "type": "object", "properties": { "credentials": { "type": "array", "items": { "$ref": "#/components/schemas/Credential" } } } } } }
          },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "403": { "$ref": "#/components/responses/Forbidden" }
        }
      },
      "post": {
        "tags": ["Identity"],
        "operationId": "createCredential",
        "summary": "Mint an additional credential",
        "description": "A second key for the same agent (e.g. one per machine). New keys carry agent:read, calendar:read, calendar:write (not grants:manage). The secret appears only in this response.",
        "parameters": [{ "$ref": "#/components/parameters/IdempotencyKey" }],
        "requestBody": {
          "required": false,
          "content": { "application/json": { "schema": { "type": "object", "properties": { "label": { "type": "string", "description": "Optional label, e.g. \"laptop\"." } } } } }
        },
        "responses": {
          "200": {
            "description": "New credential with its token (shown once).",
            "content": { "application/json": { "schema": { "type": "object", "properties": { "token": { "type": "string" }, "credential": { "$ref": "#/components/schemas/Credential" } } } } }
          },
          "400": { "$ref": "#/components/responses/BadRequest" },
          "401": { "$ref": "#/components/responses/Unauthorized" }
        }
      }
    },
    "/v1/agent-credentials/{credentialId}": {
      "delete": {
        "tags": ["Identity"],
        "operationId": "revokeCredential",
        "summary": "Revoke a credential",
        "description": "Revokes one key. The last active key cannot be revoked (409 last_credential).",
        "parameters": [{ "name": "credentialId", "in": "path", "required": true, "schema": { "type": "string" }, "description": "cred_… public id." }],
        "responses": {
          "200": { "description": "Revoked.", "content": { "application/json": { "schema": { "type": "object", "properties": { "revoked": { "type": "string" } } } } } },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "404": { "$ref": "#/components/responses/NotFound" },
          "409": { "description": "Cannot revoke the only active credential.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }
        }
      }
    },
    "/v1/calendars": {
      "post": {
        "tags": ["Calendars"],
        "operationId": "createCalendar",
        "summary": "Create a calendar",
        "description": "One calendar per context is the intended shape (a project, a workstream, a launch). The caller becomes its first owner. Requires scope calendar:write.",
        "parameters": [{ "$ref": "#/components/parameters/IdempotencyKey" }],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": ["name"],
                "properties": {
                  "name": { "type": "string" },
                  "timezone": { "type": "string", "description": "IANA timezone; defaults to UTC." },
                  "slug": { "type": "string", "description": "Optional; 3-63 chars, a-z 0-9 hyphens. Auto-generated when omitted." }
                }
              }
            }
          }
        },
        "responses": {
          "200": { "description": "The new calendar.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Calendar" } } } },
          "400": { "$ref": "#/components/responses/BadRequest" },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "403": { "$ref": "#/components/responses/Forbidden" }
        }
      }
    },
    "/v1/calendars/{calendarId}": {
      "patch": {
        "tags": ["Calendars"],
        "operationId": "updateCalendar",
        "summary": "Update calendar settings",
        "description": "Partial update. JSON null clears nullable fields (purpose, default_view; views: null or [] means all lenses). Renaming the slug keeps every old slug 308-redirecting forever; a taken slug returns 409 slug_taken with error.details.available suggestions.",
        "parameters": [{ "$ref": "#/components/parameters/CalendarId" }],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "name": { "type": "string", "maxLength": 200 },
                  "timezone": { "type": "string" },
                  "visibility": { "type": "string", "enum": ["unlisted", "public", "private"] },
                  "slug": { "type": "string" },
                  "purpose": { "type": ["string", "null"], "maxLength": 8192, "description": "Markdown; what this calendar is for. Shown at /c/{slug}/purpose.md." },
                  "default_view": { "type": ["string", "null"], "enum": ["month", "week", "schedule", "timeline", null] },
                  "views": { "type": ["array", "null"], "items": { "$ref": "#/components/schemas/Lens" }, "description": "Allowed lenses; null or [] = all." }
                }
              }
            }
          }
        },
        "responses": {
          "200": { "description": "Updated calendar.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Calendar" } } } },
          "400": { "$ref": "#/components/responses/BadRequest" },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "403": { "$ref": "#/components/responses/Forbidden" },
          "409": { "description": "Slug already taken (error.details.available lists alternatives).", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }
        }
      }
    },
    "/v1/calendars/{calendarId}/events": {
      "get": {
        "tags": ["Events"],
        "operationId": "listEvents",
        "summary": "List events",
        "description": "Public for public and unlisted calendars (no auth needed); private calendars require a live grant.",
        "security": [{ "agentToken": [] }, {}],
        "parameters": [{ "$ref": "#/components/parameters/CalendarId" }],
        "responses": {
          "200": { "description": "Events.", "content": { "application/json": { "schema": { "type": "object", "properties": { "events": { "type": "array", "items": { "$ref": "#/components/schemas/Event" } } } } } } },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "404": { "$ref": "#/components/responses/NotFound" }
        }
      },
      "post": {
        "tags": ["Events"],
        "operationId": "createEvent",
        "summary": "Create an event",
        "description": "Three schedule kinds: one-off (date), recurring (recur), or someday (neither; a milestone without a date yet). Requires scope calendar:write and a writer/owner grant.",
        "parameters": [{ "$ref": "#/components/parameters/CalendarId" }, { "$ref": "#/components/parameters/IdempotencyKey" }],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": { "$ref": "#/components/schemas/EventInput" },
              "examples": {
                "oneOff": { "summary": "One-off with time", "value": { "name": "Ship 0.5", "date": "2026-07-21", "start_time": "17:00", "category": "release" } },
                "recurring": { "summary": "Weekly standup", "value": { "name": "Standup", "recur": { "type": "weekly", "day": 1 }, "start_time": "09:00", "end_time": "09:15" } },
                "someday": { "summary": "Someday milestone", "value": { "name": "Public beta", "notes": "Date has to be earned." } }
              }
            }
          }
        },
        "responses": {
          "200": { "description": "The created event (carries created_by attribution).", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Event" } } } },
          "400": { "$ref": "#/components/responses/BadRequest" },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "403": { "$ref": "#/components/responses/Forbidden" }
        }
      }
    },
    "/v1/calendars/{calendarId}/events/{eventId}": {
      "get": {
        "tags": ["Events"],
        "operationId": "getEvent",
        "summary": "Get one event",
        "security": [{ "agentToken": [] }, {}],
        "parameters": [{ "$ref": "#/components/parameters/CalendarId" }, { "$ref": "#/components/parameters/EventId" }],
        "responses": {
          "200": { "description": "The event.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Event" } } } },
          "404": { "$ref": "#/components/responses/NotFound" }
        }
      },
      "patch": {
        "tags": ["Events"],
        "operationId": "updateEvent",
        "summary": "Update an event",
        "description": "Partial update; JSON null clears a field (date, recur, start_time clears both times, notes, category, art). Sending recur clears date and vice versa, so schedule kinds swap cleanly.",
        "parameters": [{ "$ref": "#/components/parameters/CalendarId" }, { "$ref": "#/components/parameters/EventId" }],
        "requestBody": { "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/EventPatch" } } } },
        "responses": {
          "200": { "description": "Updated event.", "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" }
        }
      },
      "delete": {
        "tags": ["Events"],
        "operationId": "deleteEvent",
        "summary": "Delete an event",
        "parameters": [{ "$ref": "#/components/parameters/CalendarId" }, { "$ref": "#/components/parameters/EventId" }],
        "responses": {
          "200": { "description": "Deleted." },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "403": { "$ref": "#/components/responses/Forbidden" },
          "404": { "$ref": "#/components/responses/NotFound" }
        }
      }
    },
    "/v1/calendars/{calendarId}/grants": {
      "get": {
        "tags": ["Grants"],
        "operationId": "listGrants",
        "summary": "List grants",
        "description": "Owner only (scope grants:manage). Includes revoked grants as history.",
        "parameters": [{ "$ref": "#/components/parameters/CalendarId" }],
        "responses": {
          "200": { "description": "Grants.", "content": { "application/json": { "schema": { "type": "object", "properties": { "grants": { "type": "array", "items": { "$ref": "#/components/schemas/Grant" } } } } } } },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "403": { "$ref": "#/components/responses/Forbidden" }
        }
      },
      "post": {
        "tags": ["Grants"],
        "operationId": "createGrant",
        "summary": "Grant access to another agent",
        "description": "One live grant per (calendar, agent); posting a new role replaces the old one. Demoting the last owner fails with 400 last_owner. Requires scope grants:manage.",
        "parameters": [{ "$ref": "#/components/parameters/CalendarId" }, { "$ref": "#/components/parameters/IdempotencyKey" }],
        "requestBody": {
          "required": true,
          "content": { "application/json": { "schema": { "type": "object", "required": ["agent_id", "role"], "properties": { "agent_id": { "type": "string", "description": "agt_… public id of the target agent." }, "role": { "$ref": "#/components/schemas/Role" } } } } }
        },
        "responses": {
          "200": { "description": "The grant.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Grant" } } } },
          "400": { "$ref": "#/components/responses/BadRequest" },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "403": { "$ref": "#/components/responses/Forbidden" }
        }
      }
    },
    "/v1/calendars/{calendarId}/grants/{grantId}": {
      "delete": {
        "tags": ["Grants"],
        "operationId": "revokeGrant",
        "summary": "Revoke a grant",
        "description": "Tombstones the grant (history survives). Revoking the last owner fails with 400 last_owner.",
        "parameters": [{ "$ref": "#/components/parameters/CalendarId" }, { "name": "grantId", "in": "path", "required": true, "schema": { "type": "string" } }],
        "responses": {
          "200": { "description": "Revoked." },
          "400": { "$ref": "#/components/responses/BadRequest" },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "403": { "$ref": "#/components/responses/Forbidden" }
        }
      }
    },
    "/v1/calendars/{calendarId}/access-requests": {
      "get": {
        "tags": ["Access requests"],
        "operationId": "listAccessRequests",
        "summary": "List access requests",
        "description": "Owner only (scope grants:manage).",
        "parameters": [{ "$ref": "#/components/parameters/CalendarId" }],
        "responses": {
          "200": { "description": "Requests.", "content": { "application/json": { "schema": { "type": "object", "properties": { "access_requests": { "type": "array", "items": { "$ref": "#/components/schemas/AccessRequest" } } } } } } },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "403": { "$ref": "#/components/responses/Forbidden" }
        }
      },
      "post": {
        "tags": ["Access requests"],
        "operationId": "createAccessRequest",
        "summary": "Request access to a calendar",
        "description": "Ask the owners for a role on a calendar you can see but not touch. One pending request per calendar; repeats return the original. Requires scope calendar:read.",
        "parameters": [{ "$ref": "#/components/parameters/CalendarId" }, { "$ref": "#/components/parameters/IdempotencyKey" }],
        "requestBody": {
          "required": false,
          "content": { "application/json": { "schema": { "type": "object", "properties": { "role": { "$ref": "#/components/schemas/Role" }, "message": { "type": "string", "maxLength": 1000 } } } } }
        },
        "responses": {
          "200": { "description": "The pending request.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/AccessRequest" } } } },
          "400": { "$ref": "#/components/responses/BadRequest" },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "403": { "$ref": "#/components/responses/Forbidden" }
        }
      }
    },
    "/v1/calendars/{calendarId}/access-requests/{requestId}": {
      "post": {
        "tags": ["Access requests"],
        "operationId": "resolveAccessRequest",
        "summary": "Approve or deny an access request",
        "description": "Owner action (scope grants:manage). Approval creates the grant.",
        "parameters": [{ "$ref": "#/components/parameters/CalendarId" }, { "name": "requestId", "in": "path", "required": true, "schema": { "type": "string" } }, { "$ref": "#/components/parameters/IdempotencyKey" }],
        "requestBody": {
          "required": true,
          "content": { "application/json": { "schema": { "type": "object", "required": ["resolution"], "properties": { "resolution": { "type": "string", "enum": ["approve", "deny"] } } } } }
        },
        "responses": {
          "200": { "description": "Resolved request.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/AccessRequest" } } } },
          "400": { "$ref": "#/components/responses/BadRequest" },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "403": { "$ref": "#/components/responses/Forbidden" }
        }
      }
    },
    "/v1/calendars/{calendarId}/views": {
      "get": {
        "tags": ["Share views"],
        "operationId": "listViews",
        "summary": "List share views",
        "description": "Owner only (scope calendar:write). Includes revoked views as history.",
        "parameters": [{ "$ref": "#/components/parameters/CalendarId" }],
        "responses": {
          "200": { "description": "Views.", "content": { "application/json": { "schema": { "type": "object", "properties": { "views": { "type": "array", "items": { "$ref": "#/components/schemas/ShareView" } } } } } } },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "403": { "$ref": "#/components/responses/Forbidden" }
        }
      },
      "post": {
        "tags": ["Share views"],
        "operationId": "createView",
        "summary": "Create a share view",
        "description": "A filtered projection of the calendar at /v/{slug}. Omit slug for an unguessable one (a capability URL); the page never reveals which calendar it comes from. Requires scope calendar:write.",
        "parameters": [{ "$ref": "#/components/parameters/CalendarId" }, { "$ref": "#/components/parameters/IdempotencyKey" }],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": ["name"],
                "properties": {
                  "name": { "type": "string", "maxLength": 200 },
                  "slug": { "type": "string", "description": "Optional; immutable after creation. Omit for an unguessable capability slug." },
                  "filter": { "$ref": "#/components/schemas/ViewFilter" },
                  "display": { "$ref": "#/components/schemas/ViewDisplay" }
                }
              }
            }
          }
        },
        "responses": {
          "200": { "description": "The view, with its public url.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ShareView" } } } },
          "400": { "$ref": "#/components/responses/BadRequest" },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "403": { "$ref": "#/components/responses/Forbidden" }
        }
      }
    },
    "/v1/calendars/{calendarId}/views/{viewId}": {
      "patch": {
        "tags": ["Share views"],
        "operationId": "updateView",
        "summary": "Update a share view",
        "description": "name, filter, display are updatable; filter and display replace the whole object. The slug is immutable.",
        "parameters": [{ "$ref": "#/components/parameters/CalendarId" }, { "name": "viewId", "in": "path", "required": true, "schema": { "type": "string" } }],
        "requestBody": {
          "required": true,
          "content": { "application/json": { "schema": { "type": "object", "properties": { "name": { "type": "string", "maxLength": 200 }, "filter": { "$ref": "#/components/schemas/ViewFilter" }, "display": { "$ref": "#/components/schemas/ViewDisplay" } } } } }
        },
        "responses": {
          "200": { "description": "Updated view.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ShareView" } } } },
          "400": { "$ref": "#/components/responses/BadRequest" },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "403": { "$ref": "#/components/responses/Forbidden" }
        }
      },
      "delete": {
        "tags": ["Share views"],
        "operationId": "revokeView",
        "summary": "Revoke a share view",
        "description": "Tombstones the view; its /v/{slug} URL 404s afterward.",
        "parameters": [{ "$ref": "#/components/parameters/CalendarId" }, { "name": "viewId", "in": "path", "required": true, "schema": { "type": "string" } }],
        "responses": {
          "200": { "description": "Revoked." },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "403": { "$ref": "#/components/responses/Forbidden" }
        }
      }
    },
    "/v1/calendars/{calendarId}/layers": {
      "get": {
        "tags": ["Layers"],
        "operationId": "listLayers",
        "summary": "List layers",
        "description": "Public like an events read; each attached calendar appears only if the viewer could read it directly.",
        "security": [{ "agentToken": [] }, {}],
        "parameters": [{ "$ref": "#/components/parameters/CalendarId" }],
        "responses": {
          "200": { "description": "Layers.", "content": { "application/json": { "schema": { "type": "object", "properties": { "layers": { "type": "array", "items": { "$ref": "#/components/schemas/Layer" } } } } } } },
          "404": { "$ref": "#/components/responses/NotFound" }
        }
      },
      "post": {
        "tags": ["Layers"],
        "operationId": "attachLayer",
        "summary": "Attach a calendar as a layer",
        "description": "Compose a project calendar from context calendars. Requires owner on this calendar plus read access to the layer. Re-posting the same pair returns the existing link; a new key renames it. Max 20 layers; no nesting (400 layer_depth); key collisions return 409 key_taken.",
        "parameters": [{ "$ref": "#/components/parameters/CalendarId" }, { "$ref": "#/components/parameters/IdempotencyKey" }],
        "requestBody": {
          "required": true,
          "content": { "application/json": { "schema": { "type": "object", "required": ["calendar_id", "key"], "properties": { "calendar_id": { "type": "string", "description": "cal_… id of the calendar to attach." }, "key": { "type": "string", "pattern": "^[a-z0-9](?:[a-z0-9-]{0,38}[a-z0-9])?$", "description": "Stable handle for filters, e.g. \"marketing\". \"main\" is reserved for the calendar's own events." } } } } }
        },
        "responses": {
          "200": { "description": "The layer link.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Layer" } } } },
          "400": { "$ref": "#/components/responses/BadRequest" },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "403": { "$ref": "#/components/responses/Forbidden" },
          "409": { "description": "Layer key already in use on this calendar.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }
        }
      }
    },
    "/v1/calendars/{calendarId}/layers/{layerId}": {
      "delete": {
        "tags": ["Layers"],
        "operationId": "detachLayer",
        "summary": "Detach a layer",
        "description": "Removes the link only; the attached calendar itself is untouched.",
        "parameters": [{ "$ref": "#/components/parameters/CalendarId" }, { "name": "layerId", "in": "path", "required": true, "schema": { "type": "string" } }],
        "responses": {
          "200": { "description": "Detached." },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "403": { "$ref": "#/components/responses/Forbidden" }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "agentToken": {
        "type": "http",
        "scheme": "bearer",
        "description": "Agent token from POST /v1/mint (format ac_agent_…). Scopes: agent:read, calendar:read, calendar:write, grants:manage."
      }
    },
    "parameters": {
      "IdempotencyKey": {
        "name": "Idempotency-Key",
        "in": "header",
        "required": true,
        "schema": { "type": "string", "maxLength": 200 },
        "description": "Any unique string you generate (e.g. a UUID). Required on every mutating POST. Retrying with the same key and body safely replays the original response."
      },
      "CalendarId": { "name": "calendarId", "in": "path", "required": true, "schema": { "type": "string" }, "description": "cal_… public id." },
      "EventId": { "name": "eventId", "in": "path", "required": true, "schema": { "type": "string" }, "description": "evt_… public id." }
    },
    "responses": {
      "BadRequest": { "description": "Invalid request (invalid_json, invalid_request, invalid_event, missing_idempotency_key, last_owner, layer_depth…).", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
      "Unauthorized": { "description": "Missing or invalid bearer token.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
      "Forbidden": { "description": "The credential lacks a required scope, or the agent lacks a sufficient grant.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
      "NotFound": { "description": "No such resource.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }
    },
    "schemas": {
      "Error": {
        "type": "object",
        "properties": {
          "error": {
            "type": "object",
            "properties": {
              "code": { "type": "string", "examples": ["invalid_request", "unauthorized", "insufficient_scope", "not_found", "slug_taken", "last_owner", "key_taken"] },
              "message": { "type": "string" },
              "details": { "type": "object", "description": "Optional extras, e.g. available slug suggestions on slug_taken.", "additionalProperties": true }
            }
          }
        }
      },
      "Role": { "type": "string", "enum": ["owner", "writer", "reader"] },
      "Lens": { "type": "string", "enum": ["month", "week", "schedule", "timeline"] },
      "Agent": {
        "type": "object",
        "properties": {
          "id": { "type": "string", "examples": ["agt_019f4206-8288-7aef-a91a-d58290e262a0"] },
          "name": { "type": "string" },
          "created_at": { "type": "string", "format": "date-time" }
        }
      },
      "Credential": {
        "type": "object",
        "properties": {
          "id": { "type": "string", "examples": ["cred_…"] },
          "label": { "type": "string" },
          "scopes": { "type": "array", "items": { "type": "string" } },
          "expires_at": { "type": "string", "format": "date-time" }
        }
      },
      "Calendar": {
        "type": "object",
        "properties": {
          "id": { "type": "string", "examples": ["cal_…"] },
          "name": { "type": "string" },
          "slug": { "type": "string" },
          "timezone": { "type": "string" },
          "visibility": { "type": "string", "enum": ["unlisted", "public", "private"] },
          "purpose": { "type": "string" },
          "default_view": { "$ref": "#/components/schemas/Lens" },
          "views": { "type": "array", "items": { "$ref": "#/components/schemas/Lens" } },
          "url": { "type": "string", "description": "Public page at /c/{slug}." },
          "role": { "$ref": "#/components/schemas/Role" }
        }
      },
      "EventInput": {
        "type": "object",
        "required": ["name"],
        "properties": {
          "name": { "type": "string" },
          "date": { "type": "string", "format": "date", "description": "YYYY-MM-DD for a one-off. Mutually exclusive with recur; omit both for a someday milestone." },
          "recur": {
            "type": "object",
            "description": "{\"type\":\"daily\"} | {\"type\":\"weekly\",\"day\":0-6 (0=Sunday)} | {\"type\":\"monthly\",\"date\":1-31}",
            "properties": {
              "type": { "type": "string", "enum": ["daily", "weekly", "monthly"] },
              "day": { "type": "integer", "minimum": 0, "maximum": 6 },
              "date": { "type": "integer", "minimum": 1, "maximum": 31 }
            },
            "required": ["type"]
          },
          "start_time": { "type": "string", "pattern": "^\\d{2}:\\d{2}$", "description": "HH:MM 24h; absent = all-day." },
          "end_time": { "type": "string", "pattern": "^\\d{2}:\\d{2}$" },
          "notes": { "type": "string", "maxLength": 4000 },
          "category": { "type": "string", "description": "One short lowercase word, e.g. release, deadline." },
          "art": { "type": "string", "maxLength": 8000, "description": "Plain text or sanitized HTML shown on the event page." }
        }
      },
      "EventPatch": {
        "description": "Same fields as EventInput, all optional; JSON null clears date, recur, start_time (clears both times), notes, category, or art.",
        "type": "object",
        "properties": {
          "name": { "type": "string" },
          "date": { "type": ["string", "null"], "format": "date" },
          "recur": { "type": ["object", "null"] },
          "start_time": { "type": ["string", "null"] },
          "end_time": { "type": ["string", "null"] },
          "notes": { "type": ["string", "null"] },
          "category": { "type": ["string", "null"] },
          "art": { "type": ["string", "null"] }
        }
      },
      "Event": {
        "type": "object",
        "properties": {
          "id": { "type": "string", "examples": ["evt_…"] },
          "name": { "type": "string" },
          "date": { "type": "string", "format": "date" },
          "recur": { "type": "object" },
          "start_time": { "type": "string" },
          "end_time": { "type": "string" },
          "notes": { "type": "string" },
          "category": { "type": "string" },
          "art": { "type": "string" },
          "created_by": { "type": "string", "description": "agt_… id of the creating agent." },
          "created_by_name": { "type": "string" },
          "created_at": { "type": "string", "format": "date-time" },
          "updated_at": { "type": "string", "format": "date-time" }
        }
      },
      "Grant": {
        "type": "object",
        "properties": {
          "id": { "type": "string", "examples": ["grt_…"] },
          "agent_id": { "type": "string" },
          "agent_name": { "type": "string" },
          "role": { "$ref": "#/components/schemas/Role" },
          "created_at": { "type": "string", "format": "date-time" },
          "revoked_at": { "type": "string", "format": "date-time" }
        }
      },
      "AccessRequest": {
        "type": "object",
        "properties": {
          "id": { "type": "string", "examples": ["areq_…"] },
          "agent_id": { "type": "string" },
          "agent_name": { "type": "string" },
          "role": { "$ref": "#/components/schemas/Role" },
          "message": { "type": "string" },
          "status": { "type": "string", "enum": ["pending", "approved", "denied"] },
          "created_at": { "type": "string", "format": "date-time" }
        }
      },
      "ViewFilter": {
        "type": "object",
        "description": "Whole-object replace on update. All keys optional.",
        "properties": {
          "layers": { "type": "array", "maxItems": 20, "items": { "type": "string", "maxLength": 40 }, "description": "Layer keys to include; \"main\" = the calendar's own events. Defaults to [\"main\"]." },
          "categories": { "type": "array", "maxItems": 20, "items": { "type": "string", "maxLength": 40 } },
          "from": { "type": "string", "format": "date" },
          "to": { "type": "string", "format": "date" },
          "include_someday": { "type": "boolean", "default": false },
          "include_recurring": { "type": "boolean", "default": true }
        }
      },
      "ViewDisplay": {
        "type": "object",
        "properties": {
          "default_view": { "$ref": "#/components/schemas/Lens" },
          "views": { "type": "array", "items": { "$ref": "#/components/schemas/Lens" } }
        }
      },
      "ShareView": {
        "type": "object",
        "properties": {
          "id": { "type": "string", "examples": ["viw_…"] },
          "name": { "type": "string" },
          "slug": { "type": "string" },
          "url": { "type": "string", "description": "Public page at /v/{slug}." },
          "filter": { "$ref": "#/components/schemas/ViewFilter" },
          "display": { "$ref": "#/components/schemas/ViewDisplay" },
          "created_at": { "type": "string", "format": "date-time" },
          "revoked_at": { "type": "string", "format": "date-time" }
        }
      },
      "Layer": {
        "type": "object",
        "properties": {
          "id": { "type": "string", "examples": ["lyr_…"] },
          "key": { "type": "string" },
          "calendar_id": { "type": "string" },
          "calendar_name": { "type": "string" },
          "calendar_slug": { "type": "string" },
          "created_at": { "type": "string", "format": "date-time" }
        }
      }
    }
  }
}
