openapi: 3.1.0
info:
  title: Its Time Agenda Universal
  version: 1.0.0
  description: |
    Contrato público e independente de parceiro para sincronização de agenda.

    A integração é bidirecional e baseada em webhooks. O Its Time é a autoridade
    para agendamentos, unidades, profissionais, disponibilidade e pacientes. O
    parceiro recebe esses eventos e reporta somente o resultado final do
    atendimento por meio de `appointment.status_reported`.
  license:
    name: Proprietary
    identifier: LicenseRef-Proprietary
  contact:
    name: Its Time Developer Relations
    url: https://itstime.pro/dev/documentacao/agenda
servers:
  - url: https://api.itstime.pro
    description: Produção
security:
  - agendaTimestamp: []
    agendaSignature: []
    idempotencyKey: []
paths:
  /api/integrations/agenda/v1/connections/{publicConnectionId}/events:
    post:
      operationId: reportAgendaAppointmentStatus
      summary: Reporta o resultado de um atendimento
      description: |
        Aceita `appointment.status_reported`. Eventos desconhecidos são registrados
        para idempotência e ignorados com HTTP 200.

        Assine o corpo com o `inboundSecret` da conexão. O `Idempotency-Key` deve
        ser byte a byte igual ao `eventId` do JSON.
      parameters:
        - name: publicConnectionId
          in: path
          required: true
          schema:
            type: string
            pattern: "^[0-9a-f]{48}$"
        - $ref: "#/components/parameters/IdempotencyKey"
        - $ref: "#/components/parameters/AgendaTimestamp"
        - $ref: "#/components/parameters/AgendaSignature"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "./agenda-universal-v1.schema.json#/$defs/InboundRequest"
            examples:
              appointmentStatusReported:
                summary: Atendimento concluído
                value:
                  schemaVersion: "1.0"
                  eventId: 4b1d2d8a-5bd2-4f4c-9a9e-26a9a0e2d001
                  eventType: appointment.status_reported
                  occurredAt: "2026-09-15T18:00:00Z"
                  resource:
                    appointmentId: 6f0f3d0f-9db5-4f35-a6e8-0a0c4d0e1001
                    status: done
                    reason: null
                    reportedAt: "2026-09-15T18:00:00Z"
                    baseResourceVersion: 7
                    metadata:
                      source: reception
      responses:
        "200":
          description: Evento desconhecido ignorado ou recibo já concluído.
          content:
            application/json:
              schema:
                $ref: "./agenda-universal-v1.schema.json#/$defs/Receipt"
              example:
                accepted: false
                duplicate: false
                ignored: true
                eventId: 4b1d2d8a-5bd2-4f4c-9a9e-26a9a0e2d001
        "202":
          description: Evento aceito ou duplicado de processamento idempotente.
          content:
            application/json:
              schema:
                $ref: "./agenda-universal-v1.schema.json#/$defs/Receipt"
              examples:
                accepted:
                  value:
                    accepted: true
                    duplicate: false
                    eventId: 4b1d2d8a-5bd2-4f4c-9a9e-26a9a0e2d001
                    resourceVersion: 8
                duplicate:
                  value:
                    accepted: true
                    duplicate: true
                    eventId: 4b1d2d8a-5bd2-4f4c-9a9e-26a9a0e2d001
                    resourceVersion: 8
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "404":
          $ref: "#/components/responses/NotFound"
        "409":
          $ref: "#/components/responses/Conflict"
        "413":
          $ref: "#/components/responses/PayloadTooLarge"
        "415":
          $ref: "#/components/responses/UnsupportedMediaType"
        "422":
          $ref: "#/components/responses/UnprocessableEntity"
        "429":
          $ref: "#/components/responses/TooManyRequests"
        "503":
          $ref: "#/components/responses/Unavailable"
webhooks:
  agendaEvent:
    post:
      operationId: receiveAgendaEvent
      summary: Recebe um evento da Agenda Universal
      description: |
        A URL é configurada por conexão. O consumidor deve ignorar `eventType`
        e campos desconhecidos. `publicConnectionId` identifica a conexão de origem
        e pode ser mapeado para a base do grupo; vários IDs podem apontar para a
        mesma base. `Idempotency-Key` é idêntico a `eventId`.

        Na ativação inicial ou em uma ressincronização, cada recurso é enviado
        em uma requisição separada, nesta ordem: unidade, profissional,
        disponibilidade, paciente, agendamento. Depois são entregues as mudanças
        capturadas durante o snapshot. Os exemplos `resync*` mostram os envelopes
        individuais desse snapshot.

        Verifique a assinatura com o `outboundSecret` da conexão e responda
        rapidamente com qualquer status 2xx depois de persistir o evento.
      parameters:
        - $ref: "#/components/parameters/IdempotencyKey"
        - $ref: "#/components/parameters/AgendaTimestamp"
        - $ref: "#/components/parameters/AgendaSignature"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "./agenda-universal-v1.schema.json#/$defs/OutboundEnvelope"
            examples:
              integrationTest:
                summary: Teste de conexão
                value:
                  schemaVersion: "1.0"
                  publicConnectionId: 4f5a39c0d310a4b91f8de2c770a1b2c3d4e5f60718293a4b
                  eventId: 88888888-8888-4888-8888-888888888888
                  eventType: integration.test
                  occurredAt: "2026-09-25T12:00:00.000Z"
                  resource:
                    message: Its Time Agenda Universal connection test
              appointmentCreated:
                summary: Novo agendamento
                value:
                  schemaVersion: "1.0"
                  publicConnectionId: 4f5a39c0d310a4b91f8de2c770a1b2c3d4e5f60718293a4b
                  eventId: 9c0c3d11-1e3e-4a18-8c19-02ab7c4d2001
                  eventType: appointment.created
                  occurredAt: "2026-09-15T15:00:00Z"
                  resourceVersion: 1
                  resource:
                    id: 6f0f3d0f-9db5-4f35-a6e8-0a0c4d0e1001
                    status: scheduled
                    startTime: "2026-09-20T14:00:00Z"
                    endTime: "2026-09-20T14:30:00Z"
                    timezone: America/Sao_Paulo
                    durationMinutes: 30
                    unitId: 7de2e00c-0b1e-4f3a-9c2b-01c8b6d30001
                    locationName: Unidade Centro
                    professionalId: 8de2e00c-0b1e-4f3a-9c2b-01c8b6d30001
                    assignmentId: 9de2e00c-0b1e-4f3a-9c2b-01c8b6d30001
                    patientId: 0de2e00c-0b1e-4f3a-9c2b-01c8b6d30001
                    service:
                      id: 1de2e00c-0b1e-4f3a-9c2b-01c8b6d30001
                      name: Consulta
                      durationMinutes: 30
                      price: "123.45"
                      currency: BRL
                    origin: api
                    notes: null
                    cancelReason: null
                    metadata: {}
                    updatedAt: "2026-09-15T15:00:00Z"
              resyncUnit:
                summary: Snapshot inicial — unidade
                value:
                  schemaVersion: "1.0"
                  publicConnectionId: 4f5a39c0d310a4b91f8de2c770a1b2c3d4e5f60718293a4b
                  eventId: 11111111-1111-4111-8111-111111111111
                  eventType: unit.upserted
                  occurredAt: "2026-09-25T12:00:00.000Z"
                  resourceVersion: 1
                  resource:
                    id: 6f0f3d0f-9db5-4f35-a6e8-0a0c4d0e1001
                    cnpj: "12345678000190"
                    name: Clínica Central de Exemplo
                    address: "Av. Exemplo, 100"
                    city: São Paulo
                    state: SP
                    isActive: true
                    closures: []
                    metadata: {}
                    updatedAt: "2026-09-25T12:00:00.000Z"
              resyncProfessional:
                summary: Snapshot inicial — profissional
                value:
                  schemaVersion: "1.0"
                  publicConnectionId: 4f5a39c0d310a4b91f8de2c770a1b2c3d4e5f60718293a4b
                  eventId: 22222222-2222-4222-8222-222222222222
                  eventType: professional.upserted
                  occurredAt: "2026-09-25T12:00:00.000Z"
                  resourceVersion: 1
                  resource:
                    id: 8de2e00c-0b1e-4f3a-9c2b-01c8b6d30001
                    name: Dra. Ana Exemplo
                    specialty: Clínica geral
                    isActive: true
                    assignments:
                      - assignmentId: 9de2e00c-0b1e-4f3a-9c2b-01c8b6d30001
                        unitId: 6f0f3d0f-9db5-4f35-a6e8-0a0c4d0e1001
                        locationName: null
                        isActive: true
                    metadata: {}
                    updatedAt: "2026-09-25T12:00:00.000Z"
              resyncAvailability:
                summary: Snapshot inicial — disponibilidade
                value:
                  schemaVersion: "1.0"
                  publicConnectionId: 4f5a39c0d310a4b91f8de2c770a1b2c3d4e5f60718293a4b
                  eventId: 33333333-3333-4333-8333-333333333333
                  eventType: availability.upserted
                  occurredAt: "2026-09-25T12:00:00.000Z"
                  resourceVersion: 1
                  resource:
                    assignmentId: 9de2e00c-0b1e-4f3a-9c2b-01c8b6d30001
                    professionalId: 8de2e00c-0b1e-4f3a-9c2b-01c8b6d30001
                    unitId: 6f0f3d0f-9db5-4f35-a6e8-0a0c4d0e1001
                    timezone: America/Sao_Paulo
                    weeklyGrid:
                      - weekday: 1
                        startTime: "09:00"
                        endTime: "17:00"
                        validFrom: "2026-09-01"
                        validUntil: null
                    exceptions: []
                    updatedAt: "2026-09-25T12:00:00.000Z"
              resyncPatient:
                summary: Snapshot inicial — paciente relacionado ao escopo
                value:
                  schemaVersion: "1.0"
                  publicConnectionId: 4f5a39c0d310a4b91f8de2c770a1b2c3d4e5f60718293a4b
                  eventId: 44444444-4444-4444-8444-444444444444
                  eventType: patient.upserted
                  occurredAt: "2026-09-25T12:00:00.000Z"
                  resourceVersion: 1
                  resource:
                    id: 0de2e00c-0b1e-4f3a-9c2b-01c8b6d30001
                    name: Paciente de Exemplo
                    phoneE164: "+5511999999999"
                    birthDate: null
                    document: null
                    homeUnitId: 6f0f3d0f-9db5-4f35-a6e8-0a0c4d0e1001
                    metadata: {}
                    updatedAt: "2026-09-25T12:00:00.000Z"
              resyncAppointment:
                summary: Snapshot inicial — agendamento ativo futuro
                value:
                  schemaVersion: "1.0"
                  publicConnectionId: 4f5a39c0d310a4b91f8de2c770a1b2c3d4e5f60718293a4b
                  eventId: 55555555-5555-4555-8555-555555555555
                  eventType: appointment.created
                  occurredAt: "2026-09-25T12:00:00.000Z"
                  resourceVersion: 1
                  resource:
                    id: 77777777-7777-4777-8777-777777777777
                    status: scheduled
                    startTime: "2026-10-01T14:00:00Z"
                    endTime: "2026-10-01T14:30:00Z"
                    timezone: America/Sao_Paulo
                    durationMinutes: 30
                    unitId: 6f0f3d0f-9db5-4f35-a6e8-0a0c4d0e1001
                    locationName: null
                    professionalId: 8de2e00c-0b1e-4f3a-9c2b-01c8b6d30001
                    assignmentId: 9de2e00c-0b1e-4f3a-9c2b-01c8b6d30001
                    patientId: 0de2e00c-0b1e-4f3a-9c2b-01c8b6d30001
                    service:
                      id: 1de2e00c-0b1e-4f3a-9c2b-01c8b6d30001
                      name: Consulta
                      durationMinutes: 30
                      price: "123.45"
                      currency: BRL
                    origin: api
                    notes: null
                    cancelReason: null
                    metadata: {}
                    updatedAt: "2026-09-25T12:00:00.000Z"
      responses:
        "200":
          description: Evento aceito ou evento desconhecido ignorado.
        "202":
          description: Evento aceito de forma assíncrona.
        "204":
          description: Evento aceito sem corpo de resposta.
        "400":
          description: Requisição inválida; falha permanente.
        "401":
          description: Assinatura inválida; falha permanente.
        "408":
          description: Timeout; entrega será repetida.
        "425":
          description: Requisição antecipada; entrega será repetida.
        "429":
          description: Limite excedido; entrega será repetida.
        "500":
          description: Erro do consumidor; entrega será repetida.
components:
  securitySchemes:
    agendaTimestamp:
      type: apiKey
      in: header
      name: X-Agenda-Timestamp
      description: Unix timestamp incluído no conteúdo assinado.
    agendaSignature:
      type: apiKey
      in: header
      name: X-Agenda-Signature
      description: Assinatura HMAC-SHA256 da conexão.
    idempotencyKey:
      type: apiKey
      in: header
      name: Idempotency-Key
      description: UUID idêntico ao eventId.
  parameters:
    IdempotencyKey:
      name: Idempotency-Key
      in: header
      required: true
      description: UUID byte a byte igual ao `eventId` do envelope.
      schema:
        type: string
        format: uuid
    AgendaTimestamp:
      name: X-Agenda-Timestamp
      in: header
      required: true
      description: Unix timestamp em segundos ou milissegundos, com tolerância de cinco minutos.
      schema:
        type: string
        pattern: "^[0-9]{10}([0-9]{3})?$"
    AgendaSignature:
      name: X-Agenda-Signature
      in: header
      required: true
      description: HMAC-SHA256 do texto `timestamp.rawBody`.
      schema:
        type: string
        pattern: "^sha256=[0-9a-fA-F]{64}$"
  responses:
    BadRequest:
      description: JSON ou cabeçalhos inválidos.
      content:
        application/json:
          schema:
            $ref: "./agenda-universal-v1.schema.json#/$defs/Error"
    Unauthorized:
      description: Conexão, timestamp ou assinatura inválidos.
      content:
        application/json:
          schema:
            $ref: "./agenda-universal-v1.schema.json#/$defs/Error"
    NotFound:
      description: Agendamento não encontrado no tenant da conexão.
      content:
        application/json:
          schema:
            $ref: "./agenda-universal-v1.schema.json#/$defs/Error"
    Conflict:
      description: Conflito de idempotência ou resourceVersion obsoleta.
      content:
        application/json:
          schema:
            $ref: "./agenda-universal-v1.schema.json#/$defs/Error"
    PayloadTooLarge:
      description: Corpo acima do limite permitido.
      content:
        application/json:
          schema:
            $ref: "./agenda-universal-v1.schema.json#/$defs/Error"
    UnsupportedMediaType:
      description: Content-Type diferente de application/json.
      content:
        application/json:
          schema:
            $ref: "./agenda-universal-v1.schema.json#/$defs/Error"
    UnprocessableEntity:
      description: Transição de estado inválida.
      content:
        application/json:
          schema:
            $ref: "./agenda-universal-v1.schema.json#/$defs/Error"
    TooManyRequests:
      description: Limite de requisições excedido.
      headers:
        Retry-After:
          schema:
            type: integer
            minimum: 1
      content:
        application/json:
          schema:
            $ref: "./agenda-universal-v1.schema.json#/$defs/Error"
    Unavailable:
      description: Processamento temporariamente indisponível.
      content:
        application/json:
          schema:
            $ref: "./agenda-universal-v1.schema.json#/$defs/Error"
