{
  "openapi": "3.0.3",
  "info": {
    "title": "Calendar Bridge",
    "version": "1.1.1",
    "description": "Privacy-limited Bitrix24 calendar bridge. REST webhook methods expose merged anonymous occupancy and only client-owned details. The webhook path key is a proxy credential, never the Bitrix24 credential."
  },
  "servers": [
    {
      "url": "https://calendar.b24util01.net.ru"
    }
  ],
  "security": [
    {
      "bearerAuth": []
    }
  ],
  "paths": {
    "/v1/availability": {
      "get": {
        "summary": "Read busy intervals, with no event metadata",
        "parameters": [
          {
            "$ref": "#/components/parameters/From"
          },
          {
            "$ref": "#/components/parameters/To"
          }
        ],
        "responses": {
          "200": {
            "description": "Merged intervals; range is inclusive, maximum 31 days",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "busy",
                    "timezone"
                  ],
                  "additionalProperties": false,
                  "properties": {
                    "busy": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Interval"
                      }
                    },
                    "timezone": {
                      "type": "string",
                      "example": "Europe/Moscow"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "422": {
            "$ref": "#/components/responses/Error"
          },
          "503": {
            "$ref": "#/components/responses/Error"
          }
        }
      }
    },
    "/v1/events": {
      "get": {
        "summary": "List this client's original booking snapshots",
        "parameters": [
          {
            "$ref": "#/components/parameters/From"
          },
          {
            "$ref": "#/components/parameters/To"
          }
        ],
        "responses": {
          "200": {
            "description": "At most 300 client-owned events, ordered by original start",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "events"
                  ],
                  "properties": {
                    "events": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Event"
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "422": {
            "$ref": "#/components/responses/Error"
          }
        }
      },
      "post": {
        "summary": "Create an event in available time",
        "description": "On timeout or booking_uncertain, repeat identical JSON and Idempotency-Key. If booking_uncertain is returned, the reservation remains until owner verification; retries never adopt events or issue another write. Never generate a new key to retry an uncertain write. No atomic lock exists against manual Bitrix writers. The service rechecks and compensates only its newly created event when a race is detected.",
        "parameters": [
          {
            "in": "header",
            "name": "Idempotency-Key",
            "required": true,
            "schema": {
              "type": "string",
              "pattern": "^[A-Za-z0-9._:-]{8,100}$"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/NewEvent"
              }
            }
          }
        },
        "responses": {
          "201": {
            "$ref": "#/components/responses/Booking"
          },
          "200": {
            "$ref": "#/components/responses/Booking"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "409": {
            "$ref": "#/components/responses/Error"
          },
          "413": {
            "$ref": "#/components/responses/Error"
          },
          "415": {
            "$ref": "#/components/responses/Error"
          },
          "422": {
            "$ref": "#/components/responses/Error"
          },
          "503": {
            "$ref": "#/components/responses/Error"
          }
        }
      }
    },
    "/v1/events/{id}": {
      "get": {
        "summary": "Read current name and description of this client's event",
        "description": "A foreign or unknown ID is rejected before an upstream detail read. Dates remain the original booking snapshot.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer",
              "minimum": 1
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Owned event",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "event": {
                      "$ref": "#/components/schemas/Event"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          },
          "410": {
            "$ref": "#/components/responses/Error"
          },
          "503": {
            "$ref": "#/components/responses/Error"
          }
        }
      }
    },
    "/health": {
      "get": {
        "summary": "Local configuration health; does not verify Bitrix permissions",
        "security": [],
        "responses": {
          "200": {
            "description": "Configured service"
          },
          "503": {
            "$ref": "#/components/responses/Error"
          }
        }
      }
    },
    "/rest/12/{webhookKey}/calendar.accessibility.get.json": {
      "post": {
        "operationId": "bitrix_calendar_accessibility_get",
        "summary": "calendar.accessibility.get",
        "description": "Bitrix-compatible calendar subset; foreign details and IDs are omitted. Only owner12/section10 are supported. See README for supported fields and privacy limitations.",
        "security": [],
        "parameters": [
          {
            "name": "webhookKey",
            "in": "path",
            "required": true,
            "description": "Secret proxy client key (32-256 base64url characters); never publish or log this URL.",
            "schema": {
              "type": "string",
              "minLength": 32,
              "maxLength": 256,
              "format": "password"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "additionalProperties": false,
                "properties": {
                  "users": {
                    "type": "array",
                    "items": {
                      "type": "integer",
                      "enum": [
                        12
                      ]
                    }
                  },
                  "from": {
                    "type": "string",
                    "format": "date"
                  },
                  "to": {
                    "type": "string",
                    "format": "date"
                  }
                }
              }
            },
            "application/x-www-form-urlencoded": {
              "schema": {
                "type": "object",
                "additionalProperties": false,
                "properties": {
                  "users": {
                    "type": "array",
                    "items": {
                      "type": "integer",
                      "enum": [
                        12
                      ]
                    }
                  },
                  "from": {
                    "type": "string",
                    "format": "date"
                  },
                  "to": {
                    "type": "string",
                    "format": "date"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Native result envelope. Add returns integer ID; getbyid returns own object; availability returns {12:[anonymous intervals]}; lists use result/total/next."
          },
          "401": {
            "description": "INVALID_CREDENTIALS"
          },
          "404": {
            "description": "ERROR_NOT_FOUND or ERROR_METHOD_NOT_FOUND"
          },
          "409": {
            "description": "TIME_BUSY or idempotency conflict"
          },
          "422": {
            "description": "Unsupported parameters or target"
          },
          "503": {
            "description": "Availability unavailable or booking outcome needs review"
          }
        }
      }
    },
    "/rest/12/{webhookKey}/calendar.section.get.json": {
      "post": {
        "operationId": "bitrix_calendar_section_get",
        "summary": "calendar.section.get",
        "description": "Bitrix-compatible calendar subset; foreign details and IDs are omitted. Only owner12/section10 are supported. See README for supported fields and privacy limitations.",
        "security": [],
        "parameters": [
          {
            "name": "webhookKey",
            "in": "path",
            "required": true,
            "description": "Secret proxy client key (32-256 base64url characters); never publish or log this URL.",
            "schema": {
              "type": "string",
              "minLength": 32,
              "maxLength": 256,
              "format": "password"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "additionalProperties": false,
                "properties": {
                  "type": {
                    "type": "string",
                    "enum": [
                      "user"
                    ]
                  },
                  "ownerId": {
                    "type": "integer",
                    "enum": [
                      12
                    ]
                  },
                  "start": {
                    "type": "integer",
                    "minimum": 0
                  }
                }
              }
            },
            "application/x-www-form-urlencoded": {
              "schema": {
                "type": "object",
                "additionalProperties": false,
                "properties": {
                  "type": {
                    "type": "string",
                    "enum": [
                      "user"
                    ]
                  },
                  "ownerId": {
                    "type": "integer",
                    "enum": [
                      12
                    ]
                  },
                  "start": {
                    "type": "integer",
                    "minimum": 0
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Native result envelope. Add returns integer ID; getbyid returns own object; availability returns {12:[anonymous intervals]}; lists use result/total/next."
          },
          "401": {
            "description": "INVALID_CREDENTIALS"
          },
          "404": {
            "description": "ERROR_NOT_FOUND or ERROR_METHOD_NOT_FOUND"
          },
          "409": {
            "description": "TIME_BUSY or idempotency conflict"
          },
          "422": {
            "description": "Unsupported parameters or target"
          },
          "503": {
            "description": "Availability unavailable or booking outcome needs review"
          }
        }
      }
    },
    "/rest/12/{webhookKey}/calendar.event.get.json": {
      "post": {
        "operationId": "bitrix_calendar_event_get",
        "summary": "calendar.event.get",
        "description": "Bitrix-compatible calendar subset; foreign details and IDs are omitted. Only owner12/section10 are supported. See README for supported fields and privacy limitations.",
        "security": [],
        "parameters": [
          {
            "name": "webhookKey",
            "in": "path",
            "required": true,
            "description": "Secret proxy client key (32-256 base64url characters); never publish or log this URL.",
            "schema": {
              "type": "string",
              "minLength": 32,
              "maxLength": 256,
              "format": "password"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "additionalProperties": false,
                "properties": {
                  "type": {
                    "type": "string",
                    "enum": [
                      "user"
                    ]
                  },
                  "ownerId": {
                    "type": "integer",
                    "enum": [
                      12
                    ]
                  },
                  "section": {
                    "type": "array",
                    "items": {
                      "type": "integer",
                      "enum": [
                        10
                      ]
                    }
                  },
                  "from": {
                    "type": "string",
                    "format": "date"
                  },
                  "to": {
                    "type": "string",
                    "format": "date"
                  },
                  "start": {
                    "type": "integer",
                    "minimum": 0
                  }
                }
              }
            },
            "application/x-www-form-urlencoded": {
              "schema": {
                "type": "object",
                "additionalProperties": false,
                "properties": {
                  "type": {
                    "type": "string",
                    "enum": [
                      "user"
                    ]
                  },
                  "ownerId": {
                    "type": "integer",
                    "enum": [
                      12
                    ]
                  },
                  "section": {
                    "type": "array",
                    "items": {
                      "type": "integer",
                      "enum": [
                        10
                      ]
                    }
                  },
                  "from": {
                    "type": "string",
                    "format": "date"
                  },
                  "to": {
                    "type": "string",
                    "format": "date"
                  },
                  "start": {
                    "type": "integer",
                    "minimum": 0
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Native result envelope. Add returns integer ID; getbyid returns own object; availability returns {12:[anonymous intervals]}; lists use result/total/next."
          },
          "401": {
            "description": "INVALID_CREDENTIALS"
          },
          "404": {
            "description": "ERROR_NOT_FOUND or ERROR_METHOD_NOT_FOUND"
          },
          "409": {
            "description": "TIME_BUSY or idempotency conflict"
          },
          "422": {
            "description": "Unsupported parameters or target"
          },
          "503": {
            "description": "Availability unavailable or booking outcome needs review"
          }
        }
      }
    },
    "/rest/12/{webhookKey}/calendar.event.getbyid.json": {
      "post": {
        "operationId": "bitrix_calendar_event_getbyid",
        "summary": "calendar.event.getbyid",
        "description": "Bitrix-compatible calendar subset; foreign details and IDs are omitted. Only owner12/section10 are supported. See README for supported fields and privacy limitations.",
        "security": [],
        "parameters": [
          {
            "name": "webhookKey",
            "in": "path",
            "required": true,
            "description": "Secret proxy client key (32-256 base64url characters); never publish or log this URL.",
            "schema": {
              "type": "string",
              "minLength": 32,
              "maxLength": 256,
              "format": "password"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "additionalProperties": false,
                "properties": {
                  "id": {
                    "type": "integer",
                    "minimum": 1
                  }
                }
              }
            },
            "application/x-www-form-urlencoded": {
              "schema": {
                "type": "object",
                "additionalProperties": false,
                "properties": {
                  "id": {
                    "type": "integer",
                    "minimum": 1
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Native result envelope. Add returns integer ID; getbyid returns own object; availability returns {12:[anonymous intervals]}; lists use result/total/next."
          },
          "401": {
            "description": "INVALID_CREDENTIALS"
          },
          "404": {
            "description": "ERROR_NOT_FOUND or ERROR_METHOD_NOT_FOUND"
          },
          "409": {
            "description": "TIME_BUSY or idempotency conflict"
          },
          "422": {
            "description": "Unsupported parameters or target"
          },
          "503": {
            "description": "Availability unavailable or booking outcome needs review"
          }
        }
      }
    },
    "/rest/12/{webhookKey}/calendar.event.add.json": {
      "post": {
        "operationId": "bitrix_calendar_event_add",
        "summary": "calendar.event.add",
        "description": "Bitrix-compatible calendar subset; foreign details and IDs are omitted. Only owner12/section10 are supported. See README for supported fields and privacy limitations.",
        "security": [],
        "parameters": [
          {
            "name": "webhookKey",
            "in": "path",
            "required": true,
            "description": "Secret proxy client key (32-256 base64url characters); never publish or log this URL.",
            "schema": {
              "type": "string",
              "minLength": 32,
              "maxLength": 256,
              "format": "password"
            }
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Stable key on retries. When absent, derived from normalized payload.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "additionalProperties": false,
                "properties": {
                  "type": {
                    "type": "string",
                    "enum": [
                      "user"
                    ]
                  },
                  "ownerId": {
                    "type": "integer",
                    "enum": [
                      12
                    ]
                  },
                  "section": {
                    "type": "integer",
                    "enum": [
                      10
                    ]
                  },
                  "name": {
                    "type": "string",
                    "maxLength": 200
                  },
                  "description": {
                    "type": "string",
                    "maxLength": 8000
                  },
                  "from": {
                    "type": "string"
                  },
                  "to": {
                    "type": "string"
                  },
                  "from_ts": {
                    "type": "integer"
                  },
                  "to_ts": {
                    "type": "integer"
                  },
                  "timezone_from": {
                    "type": "string"
                  },
                  "timezone_to": {
                    "type": "string"
                  },
                  "private_event": {
                    "type": "string",
                    "enum": [
                      "N",
                      "Y"
                    ]
                  },
                  "auto_detect_section": {
                    "type": "string",
                    "enum": [
                      "N",
                      "Y"
                    ]
                  },
                  "skip_time": {
                    "type": "string",
                    "enum": [
                      "N"
                    ]
                  },
                  "is_meeting": {
                    "type": "string",
                    "enum": [
                      "N"
                    ]
                  },
                  "accessibility": {
                    "type": "string",
                    "enum": [
                      "busy"
                    ]
                  }
                }
              }
            },
            "application/x-www-form-urlencoded": {
              "schema": {
                "type": "object",
                "additionalProperties": false,
                "properties": {
                  "type": {
                    "type": "string",
                    "enum": [
                      "user"
                    ]
                  },
                  "ownerId": {
                    "type": "integer",
                    "enum": [
                      12
                    ]
                  },
                  "section": {
                    "type": "integer",
                    "enum": [
                      10
                    ]
                  },
                  "name": {
                    "type": "string",
                    "maxLength": 200
                  },
                  "description": {
                    "type": "string",
                    "maxLength": 8000
                  },
                  "from": {
                    "type": "string"
                  },
                  "to": {
                    "type": "string"
                  },
                  "from_ts": {
                    "type": "integer"
                  },
                  "to_ts": {
                    "type": "integer"
                  },
                  "timezone_from": {
                    "type": "string"
                  },
                  "timezone_to": {
                    "type": "string"
                  },
                  "private_event": {
                    "type": "string",
                    "enum": [
                      "N",
                      "Y"
                    ]
                  },
                  "auto_detect_section": {
                    "type": "string",
                    "enum": [
                      "N",
                      "Y"
                    ]
                  },
                  "skip_time": {
                    "type": "string",
                    "enum": [
                      "N"
                    ]
                  },
                  "is_meeting": {
                    "type": "string",
                    "enum": [
                      "N"
                    ]
                  },
                  "accessibility": {
                    "type": "string",
                    "enum": [
                      "busy"
                    ]
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Native result envelope. Add returns integer ID; getbyid returns own object; availability returns {12:[anonymous intervals]}; lists use result/total/next."
          },
          "401": {
            "description": "INVALID_CREDENTIALS"
          },
          "404": {
            "description": "ERROR_NOT_FOUND or ERROR_METHOD_NOT_FOUND"
          },
          "409": {
            "description": "TIME_BUSY or idempotency conflict"
          },
          "422": {
            "description": "Unsupported parameters or target"
          },
          "503": {
            "description": "Availability unavailable or booking outcome needs review"
          }
        }
      }
    },
    "/rest/12/{webhookKey}/profile.json": {
      "post": {
        "operationId": "bitrix_profile",
        "summary": "profile",
        "description": "Bitrix-compatible calendar subset; foreign details and IDs are omitted. Only owner12/section10 are supported. See README for supported fields and privacy limitations.",
        "security": [],
        "parameters": [
          {
            "name": "webhookKey",
            "in": "path",
            "required": true,
            "description": "Secret proxy client key (32-256 base64url characters); never publish or log this URL.",
            "schema": {
              "type": "string",
              "minLength": 32,
              "maxLength": 256,
              "format": "password"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "additionalProperties": false,
                "properties": {}
              }
            },
            "application/x-www-form-urlencoded": {
              "schema": {
                "type": "object",
                "additionalProperties": false,
                "properties": {}
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Native result envelope. Add returns integer ID; getbyid returns own object; availability returns {12:[anonymous intervals]}; lists use result/total/next."
          },
          "401": {
            "description": "INVALID_CREDENTIALS"
          },
          "404": {
            "description": "ERROR_NOT_FOUND or ERROR_METHOD_NOT_FOUND"
          },
          "409": {
            "description": "TIME_BUSY or idempotency conflict"
          },
          "422": {
            "description": "Unsupported parameters or target"
          },
          "503": {
            "description": "Availability unavailable or booking outcome needs review"
          }
        }
      }
    },
    "/rest/12/{webhookKey}/calendar.user.settings.get.json": {
      "post": {
        "operationId": "bitrix_calendar_user_settings_get",
        "summary": "calendar.user.settings.get",
        "description": "Bitrix-compatible calendar subset; foreign details and IDs are omitted. Only owner12/section10 are supported. See README for supported fields and privacy limitations.",
        "security": [],
        "parameters": [
          {
            "name": "webhookKey",
            "in": "path",
            "required": true,
            "description": "Secret proxy client key (32-256 base64url characters); never publish or log this URL.",
            "schema": {
              "type": "string",
              "minLength": 32,
              "maxLength": 256,
              "format": "password"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "additionalProperties": false,
                "properties": {}
              }
            },
            "application/x-www-form-urlencoded": {
              "schema": {
                "type": "object",
                "additionalProperties": false,
                "properties": {}
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Native result envelope. Add returns integer ID; getbyid returns own object; availability returns {12:[anonymous intervals]}; lists use result/total/next."
          },
          "401": {
            "description": "INVALID_CREDENTIALS"
          },
          "404": {
            "description": "ERROR_NOT_FOUND or ERROR_METHOD_NOT_FOUND"
          },
          "409": {
            "description": "TIME_BUSY or idempotency conflict"
          },
          "422": {
            "description": "Unsupported parameters or target"
          },
          "503": {
            "description": "Availability unavailable or booking outcome needs review"
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "bearerAuth": {
        "type": "http",
        "scheme": "bearer"
      }
    },
    "parameters": {
      "From": {
        "in": "query",
        "name": "from",
        "required": true,
        "schema": {
          "type": "string",
          "format": "date"
        }
      },
      "To": {
        "in": "query",
        "name": "to",
        "required": true,
        "schema": {
          "type": "string",
          "format": "date"
        }
      }
    },
    "schemas": {
      "Interval": {
        "type": "object",
        "required": [
          "start",
          "end"
        ],
        "additionalProperties": false,
        "properties": {
          "start": {
            "type": "string",
            "format": "date-time"
          },
          "end": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "NewEvent": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "name",
          "start",
          "end"
        ],
        "properties": {
          "name": {
            "type": "string",
            "minLength": 1,
            "maxLength": 200
          },
          "description": {
            "type": "string",
            "maxLength": 8000,
            "default": ""
          },
          "start": {
            "type": "string",
            "format": "date-time",
            "description": "Future time, with explicit UTC offset, at most 366 days ahead"
          },
          "end": {
            "type": "string",
            "format": "date-time",
            "description": "After start, maximum duration 24 hours"
          }
        }
      },
      "Event": {
        "type": "object",
        "required": [
          "id",
          "name",
          "description",
          "start",
          "end",
          "status",
          "created_at"
        ],
        "additionalProperties": false,
        "properties": {
          "id": {
            "type": "integer"
          },
          "name": {
            "type": "string"
          },
          "description": {
            "type": "string"
          },
          "start": {
            "type": "string",
            "format": "date-time"
          },
          "end": {
            "type": "string",
            "format": "date-time"
          },
          "status": {
            "type": "string",
            "enum": [
              "created",
              "needs_review"
            ]
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "Error": {
        "type": "object",
        "required": [
          "error"
        ],
        "additionalProperties": false,
        "properties": {
          "error": {
            "type": "string"
          }
        }
      }
    },
    "responses": {
      "Error": {
        "description": "Bounded error code, without upstream data. 409 time_busy = occupied; 409 booking_in_progress = retry same request later; 503 booking_uncertain = reservation held, owner verification required; 503 booking_needs_review = administrator verification needed.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "Booking": {
        "description": "201 new booking; 200 idempotent replay",
        "content": {
          "application/json": {
            "schema": {
              "type": "object",
              "required": [
                "event",
                "replayed"
              ],
              "additionalProperties": false,
              "properties": {
                "event": {
                  "$ref": "#/components/schemas/Event"
                },
                "replayed": {
                  "type": "boolean"
                }
              }
            }
          }
        }
      }
    }
  }
}
