openapi: 3.1.0
info:
  title: PietroCal API
  version: 1.2.0
  summary: API unificada para integraciones de terceros con escrituras idempotentes
  description: >-
    Contrato de API actualizado hasta 9Q03.2. Los endpoints son los mismos
    controladores usados por la APP y aceptan Personal Access Tokens u OAuth
    2.1. Los scopes se indican mediante `x-pietrocal-scope` y las creaciones
    compatibles publican `x-pietrocal-idempotency: supported`.
  contact:
    name: PietroCal
servers:
- url: https://pietrocal.com/api/v1
  description: Producción
tags:
- name: Perfil
- name: Calendarios
- name: Suscripciones
- name: Eventos
- name: Recordatorios
- name: Contactos
- name: Disponibilidad
- name: Reservas
paths:
  /me_app.php:
    get:
      tags:
      - Perfil
      summary: Obtener perfil y contexto efectivo
      operationId: obtener_perfil_y_contexto_efectivo
      security:
      - bearerAuth: []
      x-pietrocal-scope: profile:read
      description: Requiere `profile:read`. Reutiliza el mismo controlador y permisos de la aplicación PietroCal.
      responses:
        '200':
          description: Operación correcta
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ProfileEnvelope'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '409':
          $ref: '#/components/responses/Conflict'
        '422':
          $ref: '#/components/responses/Unprocessable'
        '429':
          $ref: '#/components/responses/RateLimited'
        '500':
          $ref: '#/components/responses/ServerError'
  /calendars.php:
    get:
      tags:
      - Calendarios
      summary: Listar calendarios
      operationId: listar_calendarios
      security:
      - bearerAuth: []
      x-pietrocal-scope: calendars:read
      description: Requiere `calendars:read`. Reutiliza el mismo controlador y permisos de la aplicación PietroCal.
      responses:
        '200':
          description: Operación correcta
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/Calendar'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '409':
          $ref: '#/components/responses/Conflict'
        '422':
          $ref: '#/components/responses/Unprocessable'
        '429':
          $ref: '#/components/responses/RateLimited'
        '500':
          $ref: '#/components/responses/ServerError'
    post:
      tags:
      - Calendarios
      summary: Crear calendario
      operationId: crear_calendario
      security:
      - bearerAuth: []
      x-pietrocal-scope: calendars:write
      description: Requiere `calendars:write`. Reutiliza el mismo controlador y permisos de la aplicación PietroCal.
      x-pietrocal-idempotency: supported
      parameters:
      - $ref: '#/components/parameters/IdempotencyKey'
      responses:
        '201':
          description: Operación correcta
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '409':
          $ref: '#/components/responses/Conflict'
        '422':
          $ref: '#/components/responses/Unprocessable'
        '425':
          $ref: '#/components/responses/TooEarly'
        '429':
          $ref: '#/components/responses/RateLimited'
        '500':
          $ref: '#/components/responses/ServerError'
        '503':
          $ref: '#/components/responses/ServiceUnavailable'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CalendarWrite'
    patch:
      tags:
      - Calendarios
      summary: Actualizar calendario
      operationId: actualizar_calendario
      security:
      - bearerAuth: []
      x-pietrocal-scope: calendars:write
      description: Requiere `calendars:write`. Reutiliza el mismo controlador y permisos de la aplicación PietroCal.
      responses:
        '200':
          description: Operación correcta
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '409':
          $ref: '#/components/responses/Conflict'
        '422':
          $ref: '#/components/responses/Unprocessable'
        '429':
          $ref: '#/components/responses/RateLimited'
        '500':
          $ref: '#/components/responses/ServerError'
      parameters:
      - name: id
        in: query
        required: true
        schema:
          oneOf:
          - type: integer
          - type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CalendarWrite'
    put:
      tags:
      - Calendarios
      summary: Reemplazar calendario
      operationId: reemplazar_calendario
      security:
      - bearerAuth: []
      x-pietrocal-scope: calendars:write
      description: Requiere `calendars:write`. Reutiliza el mismo controlador y permisos de la aplicación PietroCal.
      responses:
        '200':
          description: Operación correcta
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '409':
          $ref: '#/components/responses/Conflict'
        '422':
          $ref: '#/components/responses/Unprocessable'
        '429':
          $ref: '#/components/responses/RateLimited'
        '500':
          $ref: '#/components/responses/ServerError'
      parameters:
      - name: id
        in: query
        required: true
        schema:
          oneOf:
          - type: integer
          - type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CalendarWrite'
    delete:
      tags:
      - Calendarios
      summary: Eliminar calendario
      operationId: eliminar_calendario
      security:
      - bearerAuth: []
      x-pietrocal-scope: calendars:write
      description: Requiere `calendars:write`. Reutiliza el mismo controlador y permisos de la aplicación PietroCal.
      responses:
        '200':
          description: Operación correcta
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '409':
          $ref: '#/components/responses/Conflict'
        '422':
          $ref: '#/components/responses/Unprocessable'
        '429':
          $ref: '#/components/responses/RateLimited'
        '500':
          $ref: '#/components/responses/ServerError'
      parameters:
      - name: id
        in: query
        required: true
        schema:
          oneOf:
          - type: integer
          - type: string
  /calendar_subscriptions.php:
    get:
      tags:
      - Suscripciones
      summary: Listar suscripciones de calendario
      operationId: listar_suscripciones_de_calendario
      security:
      - bearerAuth: []
      x-pietrocal-scope: calendars:read
      description: Requiere `calendars:read`. Reutiliza el mismo controlador y permisos de la aplicación PietroCal.
      responses:
        '200':
          description: Operación correcta
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '409':
          $ref: '#/components/responses/Conflict'
        '422':
          $ref: '#/components/responses/Unprocessable'
        '429':
          $ref: '#/components/responses/RateLimited'
        '500':
          $ref: '#/components/responses/ServerError'
    post:
      tags:
      - Suscripciones
      summary: Crear suscripción de calendario
      operationId: crear_suscripci_n_de_calendario
      security:
      - bearerAuth: []
      x-pietrocal-scope: calendars:write
      description: Requiere `calendars:write`. Reutiliza el mismo controlador y permisos de la aplicación PietroCal.
      x-pietrocal-idempotency: supported
      parameters:
      - $ref: '#/components/parameters/IdempotencyKey'
      responses:
        '201':
          description: Operación correcta
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '409':
          $ref: '#/components/responses/Conflict'
        '422':
          $ref: '#/components/responses/Unprocessable'
        '425':
          $ref: '#/components/responses/TooEarly'
        '429':
          $ref: '#/components/responses/RateLimited'
        '500':
          $ref: '#/components/responses/ServerError'
        '503':
          $ref: '#/components/responses/ServiceUnavailable'
      requestBody:
        required: true
        content:
          application/json:
            schema: &id001
              type: object
              additionalProperties: true
    patch:
      tags:
      - Suscripciones
      summary: Actualizar suscripción de calendario
      operationId: actualizar_suscripci_n_de_calendario
      security:
      - bearerAuth: []
      x-pietrocal-scope: calendars:write
      description: Requiere `calendars:write`. Reutiliza el mismo controlador y permisos de la aplicación PietroCal.
      responses:
        '200':
          description: Operación correcta
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '409':
          $ref: '#/components/responses/Conflict'
        '422':
          $ref: '#/components/responses/Unprocessable'
        '429':
          $ref: '#/components/responses/RateLimited'
        '500':
          $ref: '#/components/responses/ServerError'
      requestBody:
        required: true
        content:
          application/json:
            schema: *id001
    put:
      tags:
      - Suscripciones
      summary: Reemplazar suscripción de calendario
      operationId: reemplazar_suscripci_n_de_calendario
      security:
      - bearerAuth: []
      x-pietrocal-scope: calendars:write
      description: Requiere `calendars:write`. Reutiliza el mismo controlador y permisos de la aplicación PietroCal.
      responses:
        '200':
          description: Operación correcta
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '409':
          $ref: '#/components/responses/Conflict'
        '422':
          $ref: '#/components/responses/Unprocessable'
        '429':
          $ref: '#/components/responses/RateLimited'
        '500':
          $ref: '#/components/responses/ServerError'
      requestBody:
        required: true
        content:
          application/json:
            schema: *id001
    delete:
      tags:
      - Suscripciones
      summary: Eliminar suscripción de calendario
      operationId: eliminar_suscripci_n_de_calendario
      security:
      - bearerAuth: []
      x-pietrocal-scope: calendars:write
      description: Requiere `calendars:write`. Reutiliza el mismo controlador y permisos de la aplicación PietroCal.
      responses:
        '200':
          description: Operación correcta
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '409':
          $ref: '#/components/responses/Conflict'
        '422':
          $ref: '#/components/responses/Unprocessable'
        '429':
          $ref: '#/components/responses/RateLimited'
        '500':
          $ref: '#/components/responses/ServerError'
      parameters:
      - name: id
        in: query
        required: true
        schema:
          oneOf:
          - type: integer
          - type: string
  /events.php:
    get:
      tags:
      - Eventos
      summary: Listar u obtener eventos
      operationId: listar_u_obtener_eventos
      security:
      - bearerAuth: []
      x-pietrocal-scope: events:read
      description: Requiere `events:read`. Reutiliza el mismo controlador y permisos de la aplicación PietroCal.
      responses:
        '200':
          description: Operación correcta
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/Event'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '409':
          $ref: '#/components/responses/Conflict'
        '422':
          $ref: '#/components/responses/Unprocessable'
        '429':
          $ref: '#/components/responses/RateLimited'
        '500':
          $ref: '#/components/responses/ServerError'
      parameters:
      - name: calendarId
        in: query
        schema:
          type: integer
      - name: eventId
        in: query
        schema:
          type: string
      - &id002
        name: page
        in: query
        schema:
          type: integer
          minimum: 1
          default: 1
        description: Sólo para Bearer.
      - &id003
        name: perPage
        in: query
        schema:
          type: integer
          minimum: 1
          maximum: 100
          default: 50
        description: Sólo para Bearer; máximo 100.
    post:
      tags:
      - Eventos
      summary: Crear evento
      operationId: crear_evento
      security:
      - bearerAuth: []
      x-pietrocal-scope: events:write
      description: Requiere `events:write`. Reutiliza el mismo controlador y permisos de la aplicación PietroCal.
      x-pietrocal-idempotency: supported
      parameters:
      - $ref: '#/components/parameters/IdempotencyKey'
      responses:
        '201':
          description: Operación correcta
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '409':
          $ref: '#/components/responses/Conflict'
        '422':
          $ref: '#/components/responses/Unprocessable'
        '425':
          $ref: '#/components/responses/TooEarly'
        '429':
          $ref: '#/components/responses/RateLimited'
        '500':
          $ref: '#/components/responses/ServerError'
        '503':
          $ref: '#/components/responses/ServiceUnavailable'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/EventWrite'
    patch:
      tags:
      - Eventos
      summary: Actualizar evento
      operationId: actualizar_evento
      security:
      - bearerAuth: []
      x-pietrocal-scope: events:write
      description: Requiere `events:write`. Reutiliza el mismo controlador y permisos de la aplicación PietroCal.
      responses:
        '200':
          description: Operación correcta
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '409':
          $ref: '#/components/responses/Conflict'
        '422':
          $ref: '#/components/responses/Unprocessable'
        '429':
          $ref: '#/components/responses/RateLimited'
        '500':
          $ref: '#/components/responses/ServerError'
      parameters:
      - name: eventId
        in: query
        required: true
        schema:
          oneOf:
          - type: integer
          - type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/EventWrite'
    put:
      tags:
      - Eventos
      summary: Reemplazar evento
      operationId: reemplazar_evento
      security:
      - bearerAuth: []
      x-pietrocal-scope: events:write
      description: Requiere `events:write`. Reutiliza el mismo controlador y permisos de la aplicación PietroCal.
      responses:
        '200':
          description: Operación correcta
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '409':
          $ref: '#/components/responses/Conflict'
        '422':
          $ref: '#/components/responses/Unprocessable'
        '429':
          $ref: '#/components/responses/RateLimited'
        '500':
          $ref: '#/components/responses/ServerError'
      parameters:
      - name: eventId
        in: query
        required: true
        schema:
          oneOf:
          - type: integer
          - type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/EventWrite'
    delete:
      tags:
      - Eventos
      summary: Eliminar evento
      operationId: eliminar_evento
      security:
      - bearerAuth: []
      x-pietrocal-scope: events:write
      description: Requiere `events:write`. Reutiliza el mismo controlador y permisos de la aplicación PietroCal.
      responses:
        '200':
          description: Operación correcta
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '409':
          $ref: '#/components/responses/Conflict'
        '422':
          $ref: '#/components/responses/Unprocessable'
        '429':
          $ref: '#/components/responses/RateLimited'
        '500':
          $ref: '#/components/responses/ServerError'
      parameters:
      - name: eventId
        in: query
        required: true
        schema:
          oneOf:
          - type: integer
          - type: string
  /tasks.php:
    get:
      tags:
      - Recordatorios
      summary: Listar u obtener recordatorios
      operationId: listar_u_obtener_recordatorios
      security:
      - bearerAuth: []
      x-pietrocal-scope: tasks:read
      description: Requiere `tasks:read`. Reutiliza el mismo controlador y permisos de la aplicación PietroCal.
      responses:
        '200':
          description: Operación correcta
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '409':
          $ref: '#/components/responses/Conflict'
        '422':
          $ref: '#/components/responses/Unprocessable'
        '429':
          $ref: '#/components/responses/RateLimited'
        '500':
          $ref: '#/components/responses/ServerError'
      parameters:
      - name: calendarId
        in: query
        schema:
          type: integer
      - name: taskId
        in: query
        schema:
          type: string
    post:
      tags:
      - Recordatorios
      summary: Crear recordatorio
      operationId: crear_recordatorio
      security:
      - bearerAuth: []
      x-pietrocal-scope: tasks:write
      description: Requiere `tasks:write`. Reutiliza el mismo controlador y permisos de la aplicación PietroCal.
      x-pietrocal-idempotency: supported
      parameters:
      - $ref: '#/components/parameters/IdempotencyKey'
      responses:
        '201':
          description: Operación correcta
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '409':
          $ref: '#/components/responses/Conflict'
        '422':
          $ref: '#/components/responses/Unprocessable'
        '425':
          $ref: '#/components/responses/TooEarly'
        '429':
          $ref: '#/components/responses/RateLimited'
        '500':
          $ref: '#/components/responses/ServerError'
        '503':
          $ref: '#/components/responses/ServiceUnavailable'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/TaskWrite'
    patch:
      tags:
      - Recordatorios
      summary: Actualizar recordatorio
      operationId: actualizar_recordatorio
      security:
      - bearerAuth: []
      x-pietrocal-scope: tasks:write
      description: Requiere `tasks:write`. Reutiliza el mismo controlador y permisos de la aplicación PietroCal.
      responses:
        '200':
          description: Operación correcta
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '409':
          $ref: '#/components/responses/Conflict'
        '422':
          $ref: '#/components/responses/Unprocessable'
        '429':
          $ref: '#/components/responses/RateLimited'
        '500':
          $ref: '#/components/responses/ServerError'
      parameters:
      - name: taskId
        in: query
        required: true
        schema:
          oneOf:
          - type: integer
          - type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/TaskWrite'
    put:
      tags:
      - Recordatorios
      summary: Reemplazar recordatorio
      operationId: reemplazar_recordatorio
      security:
      - bearerAuth: []
      x-pietrocal-scope: tasks:write
      description: Requiere `tasks:write`. Reutiliza el mismo controlador y permisos de la aplicación PietroCal.
      responses:
        '200':
          description: Operación correcta
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '409':
          $ref: '#/components/responses/Conflict'
        '422':
          $ref: '#/components/responses/Unprocessable'
        '429':
          $ref: '#/components/responses/RateLimited'
        '500':
          $ref: '#/components/responses/ServerError'
      parameters:
      - name: taskId
        in: query
        required: true
        schema:
          oneOf:
          - type: integer
          - type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/TaskWrite'
    delete:
      tags:
      - Recordatorios
      summary: Eliminar recordatorio
      operationId: eliminar_recordatorio
      security:
      - bearerAuth: []
      x-pietrocal-scope: tasks:write
      description: Requiere `tasks:write`. Reutiliza el mismo controlador y permisos de la aplicación PietroCal.
      responses:
        '200':
          description: Operación correcta
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '409':
          $ref: '#/components/responses/Conflict'
        '422':
          $ref: '#/components/responses/Unprocessable'
        '429':
          $ref: '#/components/responses/RateLimited'
        '500':
          $ref: '#/components/responses/ServerError'
      parameters:
      - name: taskId
        in: query
        required: true
        schema:
          oneOf:
          - type: integer
          - type: string
  /contacts.php:
    get:
      tags:
      - Contactos
      summary: Listar contactos o libretas
      operationId: listar_contactos_o_libretas
      security:
      - bearerAuth: []
      x-pietrocal-scope: contacts:read
      description: Requiere `contacts:read`. Reutiliza el mismo controlador y permisos de la aplicación PietroCal.
      responses:
        '200':
          description: Operación correcta
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '409':
          $ref: '#/components/responses/Conflict'
        '422':
          $ref: '#/components/responses/Unprocessable'
        '429':
          $ref: '#/components/responses/RateLimited'
        '500':
          $ref: '#/components/responses/ServerError'
      parameters:
      - name: addressBook
        in: query
        schema:
          type: string
      - name: uri
        in: query
        schema:
          type: string
      - name: q
        in: query
        schema:
          type: string
      - *id002
      - *id003
    post:
      tags:
      - Contactos
      summary: Crear contacto o libreta
      operationId: crear_contacto_o_libreta
      security:
      - bearerAuth: []
      x-pietrocal-scope: contacts:write
      description: Requiere `contacts:write`. Reutiliza el mismo controlador y permisos de la aplicación PietroCal.
      x-pietrocal-idempotency: supported
      parameters:
      - $ref: '#/components/parameters/IdempotencyKey'
      responses:
        '201':
          description: Operación correcta
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '409':
          $ref: '#/components/responses/Conflict'
        '422':
          $ref: '#/components/responses/Unprocessable'
        '425':
          $ref: '#/components/responses/TooEarly'
        '429':
          $ref: '#/components/responses/RateLimited'
        '500':
          $ref: '#/components/responses/ServerError'
        '503':
          $ref: '#/components/responses/ServiceUnavailable'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ContactWrite'
    patch:
      tags:
      - Contactos
      summary: Actualizar contacto o libreta
      operationId: actualizar_contacto_o_libreta
      security:
      - bearerAuth: []
      x-pietrocal-scope: contacts:write
      description: Requiere `contacts:write`. Reutiliza el mismo controlador y permisos de la aplicación PietroCal.
      responses:
        '200':
          description: Operación correcta
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '409':
          $ref: '#/components/responses/Conflict'
        '422':
          $ref: '#/components/responses/Unprocessable'
        '429':
          $ref: '#/components/responses/RateLimited'
        '500':
          $ref: '#/components/responses/ServerError'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ContactWrite'
    put:
      tags:
      - Contactos
      summary: Reemplazar contacto o libreta
      operationId: reemplazar_contacto_o_libreta
      security:
      - bearerAuth: []
      x-pietrocal-scope: contacts:write
      description: Requiere `contacts:write`. Reutiliza el mismo controlador y permisos de la aplicación PietroCal.
      responses:
        '200':
          description: Operación correcta
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '409':
          $ref: '#/components/responses/Conflict'
        '422':
          $ref: '#/components/responses/Unprocessable'
        '429':
          $ref: '#/components/responses/RateLimited'
        '500':
          $ref: '#/components/responses/ServerError'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ContactWrite'
    delete:
      tags:
      - Contactos
      summary: Eliminar contacto o libreta
      operationId: eliminar_contacto_o_libreta
      security:
      - bearerAuth: []
      x-pietrocal-scope: contacts:write
      description: Requiere `contacts:write`. Reutiliza el mismo controlador y permisos de la aplicación PietroCal.
      responses:
        '200':
          description: Operación correcta
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '409':
          $ref: '#/components/responses/Conflict'
        '422':
          $ref: '#/components/responses/Unprocessable'
        '429':
          $ref: '#/components/responses/RateLimited'
        '500':
          $ref: '#/components/responses/ServerError'
      parameters:
      - name: addressBook
        in: query
        schema:
          type: string
      - name: uri
        in: query
        schema:
          type: string
  /availability_links.php:
    get:
      tags:
      - Disponibilidad
      summary: Listar enlaces de disponibilidad
      operationId: listar_enlaces_de_disponibilidad
      security:
      - bearerAuth: []
      x-pietrocal-scope: availability:read
      description: Requiere `availability:read`. Reutiliza el mismo controlador y permisos de la aplicación PietroCal.
      responses:
        '200':
          description: Operación correcta
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '409':
          $ref: '#/components/responses/Conflict'
        '422':
          $ref: '#/components/responses/Unprocessable'
        '429':
          $ref: '#/components/responses/RateLimited'
        '500':
          $ref: '#/components/responses/ServerError'
    post:
      tags:
      - Disponibilidad
      summary: Crear enlace de disponibilidad
      operationId: crear_enlace_de_disponibilidad
      security:
      - bearerAuth: []
      x-pietrocal-scope: availability:write
      description: Requiere `availability:write`. Reutiliza el mismo controlador y permisos de la aplicación PietroCal.
      x-pietrocal-idempotency: supported
      parameters:
      - $ref: '#/components/parameters/IdempotencyKey'
      responses:
        '201':
          description: Operación correcta
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '409':
          $ref: '#/components/responses/Conflict'
        '422':
          $ref: '#/components/responses/Unprocessable'
        '425':
          $ref: '#/components/responses/TooEarly'
        '429':
          $ref: '#/components/responses/RateLimited'
        '500':
          $ref: '#/components/responses/ServerError'
        '503':
          $ref: '#/components/responses/ServiceUnavailable'
      requestBody:
        required: true
        content:
          application/json:
            schema: *id001
    patch:
      tags:
      - Disponibilidad
      summary: Actualizar enlace de disponibilidad
      operationId: actualizar_enlace_de_disponibilidad
      security:
      - bearerAuth: []
      x-pietrocal-scope: availability:write
      description: Requiere `availability:write`. Reutiliza el mismo controlador y permisos de la aplicación PietroCal.
      responses:
        '200':
          description: Operación correcta
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '409':
          $ref: '#/components/responses/Conflict'
        '422':
          $ref: '#/components/responses/Unprocessable'
        '429':
          $ref: '#/components/responses/RateLimited'
        '500':
          $ref: '#/components/responses/ServerError'
      requestBody:
        required: true
        content:
          application/json:
            schema: *id001
    put:
      tags:
      - Disponibilidad
      summary: Reemplazar enlace de disponibilidad
      operationId: reemplazar_enlace_de_disponibilidad
      security:
      - bearerAuth: []
      x-pietrocal-scope: availability:write
      description: Requiere `availability:write`. Reutiliza el mismo controlador y permisos de la aplicación PietroCal.
      responses:
        '200':
          description: Operación correcta
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '409':
          $ref: '#/components/responses/Conflict'
        '422':
          $ref: '#/components/responses/Unprocessable'
        '429':
          $ref: '#/components/responses/RateLimited'
        '500':
          $ref: '#/components/responses/ServerError'
      requestBody:
        required: true
        content:
          application/json:
            schema: *id001
    delete:
      tags:
      - Disponibilidad
      summary: Eliminar enlace de disponibilidad
      operationId: eliminar_enlace_de_disponibilidad
      security:
      - bearerAuth: []
      x-pietrocal-scope: availability:write
      description: Requiere `availability:write`. Reutiliza el mismo controlador y permisos de la aplicación PietroCal.
      responses:
        '200':
          description: Operación correcta
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '409':
          $ref: '#/components/responses/Conflict'
        '422':
          $ref: '#/components/responses/Unprocessable'
        '429':
          $ref: '#/components/responses/RateLimited'
        '500':
          $ref: '#/components/responses/ServerError'
      parameters:
      - name: id
        in: query
        required: true
        schema:
          oneOf:
          - type: integer
          - type: string
  /freebusy.php:
    post:
      tags:
      - Disponibilidad
      summary: Consultar disponibilidad Free/Busy
      operationId: consultar_disponibilidad_free_busy
      security:
      - bearerAuth: []
      x-pietrocal-scope: availability:read
      description: Requiere `availability:read`. Reutiliza el mismo controlador y permisos de la aplicación PietroCal.
      responses:
        '200':
          description: Operación correcta
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '409':
          $ref: '#/components/responses/Conflict'
        '422':
          $ref: '#/components/responses/Unprocessable'
        '429':
          $ref: '#/components/responses/RateLimited'
        '500':
          $ref: '#/components/responses/ServerError'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/FreeBusyRequest'
  /bookings.php:
    get:
      tags:
      - Reservas
      summary: Listar reservas y consultas
      operationId: listar_reservas_y_consultas
      security:
      - bearerAuth: []
      x-pietrocal-scope: bookings:read
      description: Requiere `bookings:read`. Reutiliza el mismo controlador y permisos de la aplicación PietroCal.
      responses:
        '200':
          description: Operación correcta
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '409':
          $ref: '#/components/responses/Conflict'
        '422':
          $ref: '#/components/responses/Unprocessable'
        '429':
          $ref: '#/components/responses/RateLimited'
        '500':
          $ref: '#/components/responses/ServerError'
      parameters:
      - name: resourceId
        in: query
        schema:
          type: integer
      - name: status
        in: query
        schema:
          type: string
      - *id002
      - *id003
    post:
      tags:
      - Reservas
      summary: Crear o ejecutar acción de reserva
      operationId: crear_o_ejecutar_acci_n_de_reserva
      security:
      - bearerAuth: []
      x-pietrocal-scope: bookings:write
      description: Requiere `bookings:write`. Reutiliza el mismo controlador y permisos de la aplicación PietroCal.
      x-pietrocal-idempotency: supported
      parameters:
      - $ref: '#/components/parameters/IdempotencyKey'
      responses:
        '201':
          description: Operación correcta
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '409':
          $ref: '#/components/responses/Conflict'
        '422':
          $ref: '#/components/responses/Unprocessable'
        '425':
          $ref: '#/components/responses/TooEarly'
        '429':
          $ref: '#/components/responses/RateLimited'
        '500':
          $ref: '#/components/responses/ServerError'
        '503':
          $ref: '#/components/responses/ServiceUnavailable'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/BookingWrite'
    patch:
      tags:
      - Reservas
      summary: Actualizar reserva
      operationId: actualizar_reserva
      security:
      - bearerAuth: []
      x-pietrocal-scope: bookings:write
      description: Requiere `bookings:write`. Reutiliza el mismo controlador y permisos de la aplicación PietroCal.
      responses:
        '200':
          description: Operación correcta
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '409':
          $ref: '#/components/responses/Conflict'
        '422':
          $ref: '#/components/responses/Unprocessable'
        '429':
          $ref: '#/components/responses/RateLimited'
        '500':
          $ref: '#/components/responses/ServerError'
      parameters:
      - name: bookingId
        in: query
        required: true
        schema:
          oneOf:
          - type: integer
          - type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/BookingWrite'
    put:
      tags:
      - Reservas
      summary: Reemplazar reserva
      operationId: reemplazar_reserva
      security:
      - bearerAuth: []
      x-pietrocal-scope: bookings:write
      description: Requiere `bookings:write`. Reutiliza el mismo controlador y permisos de la aplicación PietroCal.
      responses:
        '200':
          description: Operación correcta
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '409':
          $ref: '#/components/responses/Conflict'
        '422':
          $ref: '#/components/responses/Unprocessable'
        '429':
          $ref: '#/components/responses/RateLimited'
        '500':
          $ref: '#/components/responses/ServerError'
      parameters:
      - name: bookingId
        in: query
        required: true
        schema:
          oneOf:
          - type: integer
          - type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/BookingWrite'
    delete:
      tags:
      - Reservas
      summary: Eliminar o archivar reserva
      operationId: eliminar_o_archivar_reserva
      security:
      - bearerAuth: []
      x-pietrocal-scope: bookings:write
      description: Requiere `bookings:write`. Reutiliza el mismo controlador y permisos de la aplicación PietroCal.
      responses:
        '200':
          description: Operación correcta
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '409':
          $ref: '#/components/responses/Conflict'
        '422':
          $ref: '#/components/responses/Unprocessable'
        '429':
          $ref: '#/components/responses/RateLimited'
        '500':
          $ref: '#/components/responses/ServerError'
      parameters:
      - name: bookingId
        in: query
        required: true
        schema:
          oneOf:
          - type: integer
          - type: string

  /oauth/authorize.php:
    servers:
      - url: https://pietrocal.com
        description: Servidor de autorización OAuth
    get:
      tags: [OAuth 2.1]
      summary: Solicitar autorización del usuario
      description: Inicia Authorization Code con PKCE S256. Requiere sesión web y muestra consentimiento.
      security: []
      parameters:
        - in: query
          name: response_type
          required: true
          schema: { type: string, enum: [code] }
        - in: query
          name: client_id
          required: true
          schema: { type: string, pattern: '^pc_oauth_' }
        - in: query
          name: redirect_uri
          required: true
          schema: { type: string, format: uri }
        - in: query
          name: scope
          required: true
          schema: { type: string }
        - in: query
          name: state
          required: true
          schema: { type: string, maxLength: 512 }
        - in: query
          name: code_challenge
          required: true
          schema: { type: string, minLength: 43, maxLength: 128 }
        - in: query
          name: code_challenge_method
          required: true
          schema: { type: string, enum: [S256] }
      responses:
        '302':
          description: Redirección al callback con code/state o error/state.
        '400':
          $ref: '#/components/responses/BadRequest'
  /oauth/token.php:
    servers:
      - url: https://pietrocal.com
        description: Servidor de autorización OAuth
    post:
      tags: [OAuth 2.1]
      summary: Intercambiar código o renovar tokens
      description: Acepta application/x-www-form-urlencoded. Soporta authorization_code y refresh_token.
      security: []
      requestBody:
        required: true
        content:
          application/x-www-form-urlencoded:
            schema:
              oneOf:
                - $ref: '#/components/schemas/OAuthAuthorizationCodeRequest'
                - $ref: '#/components/schemas/OAuthRefreshTokenRequest'
      responses:
        '200':
          description: Tokens emitidos o renovados.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/OAuthTokenResponse'
        '400':
          description: invalid_request, invalid_grant o unsupported_grant_type.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/OAuthError'
        '401':
          description: invalid_client.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/OAuthError'
  /oauth/revoke.php:
    servers:
      - url: https://pietrocal.com
        description: Servidor de autorización OAuth
    post:
      tags: [OAuth 2.1]
      summary: Revocar access token o refresh token
      description: Revocar refresh token invalida toda la conexión. Token desconocido también devuelve 200.
      security: []
      requestBody:
        required: true
        content:
          application/x-www-form-urlencoded:
            schema:
              $ref: '#/components/schemas/OAuthRevocationRequest'
      responses:
        '200':
          description: Revocación procesada.
        '400':
          description: Solicitud inválida.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/OAuthError'
        '401':
          description: Cliente inválido.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/OAuthError'

components:
  securitySchemes:
    oauth2:
      type: oauth2
      description: OAuth 2.1 Authorization Code con PKCE S256.
      flows:
        authorizationCode:
          authorizationUrl: https://pietrocal.com/oauth/authorize.php
          tokenUrl: https://pietrocal.com/oauth/token.php
          refreshUrl: https://pietrocal.com/oauth/token.php
          scopes:
            profile:read: Leer perfil y workspace.
            calendars:read: Leer calendarios.
            calendars:write: Administrar calendarios.
            events:read: Leer eventos.
            events:write: Administrar eventos.
            tasks:read: Leer recordatorios.
            tasks:write: Administrar recordatorios.
            contacts:read: Leer contactos.
            contacts:write: Administrar contactos.
            availability:read: Leer disponibilidad.
            availability:write: Administrar disponibilidad.
            bookings:read: Leer reservas.
            bookings:write: Administrar reservas.
    bearerAuth:
      type: http
      scheme: bearer
      bearerFormat: pc_live_...
      description: Token personal creado en Configuración → Integraciones y API.
  parameters:
    IdempotencyKey:
      name: Idempotency-Key
      in: header
      required: false
      description: >-
        Clave estable de 8 a 200 caracteres para evitar duplicados en escrituras
        Bearer. Repetir la misma clave con el mismo contenido reproduce la
        primera respuesta; reutilizarla con otro contenido devuelve 409.
      schema:
        type: string
        minLength: 8
        maxLength: 200
        pattern: '^[A-Za-z0-9][A-Za-z0-9._:-]{7,199}$'
  responses:
    BadRequest:
      description: Solicitud inválida
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
    Unauthorized:
      description: Token ausente, inválido, vencido o revocado
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
    Forbidden:
      description: Scope insuficiente o permiso real del recurso denegado
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
    NotFound:
      description: Recurso inexistente
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
    Conflict:
      description: Conflicto de versión, ETag, duplicado o estado
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
    Unprocessable:
      description: Validación de datos fallida
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
    TooEarly:
      description: La misma Idempotency-Key todavía está siendo procesada
      headers:
        Retry-After:
          schema:
            type: integer
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
    RateLimited:
      description: Límite por minuto, presupuesto mensual de la credencial o cuota mensual del titular superados
      headers:
        Retry-After:
          schema:
            type: integer
        X-RateLimit-Limit:
          description: Tope efectivo por minuto del token; -1 significa ilimitado.
          schema:
            type: integer
            minimum: -1
        X-RateLimit-Remaining:
          description: Solicitudes restantes en el minuto; -1 significa ilimitado.
          schema:
            type: integer
            minimum: -1
        X-RateLimit-Reset:
          description: Unix timestamp UTC del próximo minuto.
          schema:
            type: integer
        X-PietroCal-Quota-Limit:
          description: Cuota mensual compartida por el titular; -1 significa ilimitada.
          schema:
            type: integer
            minimum: -1
        X-PietroCal-Quota-Remaining:
          description: Unidades mensuales restantes; -1 significa ilimitado.
          schema:
            type: integer
            minimum: -1
        X-PietroCal-Quota-Used:
          description: Solicitudes medidas en el mes UTC actual.
          schema:
            type: integer
            minimum: 0
        X-PietroCal-Quota-Reset:
          description: Unix timestamp UTC del primer instante del próximo mes.
          schema:
            type: integer
        X-PietroCal-Token-Budget-Limit:
          description: Presupuesto mensual efectivo de la credencial; -1 significa ilimitado.
          schema:
            type: integer
            minimum: -1
        X-PietroCal-Token-Budget-Remaining:
          description: Solicitudes restantes para la credencial durante el mes UTC; -1 significa ilimitado.
          schema:
            type: integer
            minimum: -1
        X-PietroCal-Token-Budget-Used:
          description: Solicitudes medidas para la credencial durante el mes UTC actual.
          schema:
            type: integer
            minimum: 0
        X-PietroCal-Token-Budget-Reset:
          description: Unix timestamp UTC del primer instante del próximo mes.
          schema:
            type: integer
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
    ServerError:
      description: Error interno
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
    ServiceUnavailable:
      description: La protección idempotente o de cuotas no está disponible, o falta una migración requerida
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
  schemas:

    OAuthAuthorizationCodeRequest:
      type: object
      required: [grant_type, client_id, code, redirect_uri, code_verifier]
      properties:
        grant_type: { type: string, enum: [authorization_code] }
        client_id: { type: string, pattern: '^pc_oauth_' }
        client_secret: { type: string, description: Sólo clientes confidenciales. }
        code: { type: string, pattern: '^pc_code_' }
        redirect_uri: { type: string, format: uri }
        code_verifier: { type: string, minLength: 43, maxLength: 128 }
    OAuthRefreshTokenRequest:
      type: object
      required: [grant_type, client_id, refresh_token]
      properties:
        grant_type: { type: string, enum: [refresh_token] }
        client_id: { type: string, pattern: '^pc_oauth_' }
        client_secret: { type: string, description: Sólo clientes confidenciales. }
        refresh_token: { type: string, pattern: '^pc_refresh_' }
    OAuthRevocationRequest:
      type: object
      required: [client_id, token]
      properties:
        client_id: { type: string, pattern: '^pc_oauth_' }
        client_secret: { type: string, description: Sólo clientes confidenciales. }
        token: { type: string }
        token_type_hint:
          type: string
          enum: [access_token, refresh_token]
    OAuthTokenResponse:
      type: object
      required: [access_token, token_type, expires_in, refresh_token, refresh_token_expires_in, scope]
      properties:
        access_token: { type: string, pattern: '^pc_live_' }
        token_type: { type: string, enum: [Bearer] }
        expires_in: { type: integer, example: 3600 }
        refresh_token: { type: string, pattern: '^pc_refresh_' }
        refresh_token_expires_in: { type: integer, example: 2592000 }
        scope: { type: string }
    OAuthError:
      type: object
      required: [error]
      properties:
        error:
          type: string
          enum: [invalid_request, invalid_client, invalid_grant, unsupported_grant_type, temporarily_unavailable, server_error]
        error_description: { type: string }
    ErrorResponse:
      type: object
      required:
      - success
      - error
      properties:
        success:
          type: boolean
          const: false
        error:
          type: object
          required:
          - code
          - message
          properties:
            code:
              type: string
            message:
              type: string
            details: {}
    ProfileEnvelope:
      type: object
      properties:
        success:
          type: boolean
        data:
          type: object
          additionalProperties: true
    Calendar:
      type: object
      additionalProperties: true
      properties:
        id:
          oneOf:
          - type: integer
          - type: string
        displayName:
          type: string
        name:
          type: string
        color:
          type: string
        readonly:
          type: boolean
        components:
          type: array
          items:
            type: string
    CalendarWrite:
      type: object
      additionalProperties: true
      properties:
        name:
          type: string
        displayName:
          type: string
        color:
          type: string
        components:
          type: array
          items:
            type: string
    Event:
      type: object
      additionalProperties: true
      properties:
        id:
          type: string
        calendarId:
          type: integer
        title:
          type: string
        start:
          type: string
        end:
          type: string
        allDay:
          type: boolean
    EventWrite:
      type: object
      required:
      - calendarId
      - title
      - start
      - end
      additionalProperties: true
      properties:
        calendarId:
          type: integer
        eventId:
          type: string
        title:
          type: string
        start:
          type: string
          format: date-time
        end:
          type: string
          format: date-time
        allDay:
          type: boolean
          default: false
        location:
          type: string
        description:
          type: string
        rrule:
          type: string
    TaskWrite:
      type: object
      required:
      - calendarId
      - title
      additionalProperties: true
      properties:
        calendarId:
          type: integer
        taskId:
          type: string
        title:
          type: string
        due:
          type: string
          format: date-time
        completed:
          type: boolean
        description:
          type: string
    ContactWrite:
      type: object
      additionalProperties: true
      properties:
        addressBook:
          type: string
        uri:
          type: string
        etag:
          type: string
        displayName:
          type: string
        firstName:
          type: string
        lastName:
          type: string
        email:
          type: string
          format: email
        phone:
          type: string
        organization:
          type: string
        categories:
          type: array
          items:
            type: string
    FreeBusyRequest:
      type: object
      required:
      - start
      - end
      additionalProperties: true
      properties:
        calendarIds:
          type: array
          items:
            type: integer
        start:
          type: string
          format: date-time
        end:
          type: string
          format: date-time
        timezone:
          type: string
    BookingWrite:
      type: object
      additionalProperties: true
      properties:
        bookingId:
          type: integer
        action:
          type: string
        resourceId:
          type: integer
        title:
          type: string
        startAt:
          type: string
          format: date-time
        endAt:
          type: string
          format: date-time
        status:
          type: string
        customerName:
          type: string
        customerEmail:
          type: string
          format: email
