{
  "openapi": "3.1.0",
  "info": {
    "title": "MoiToi.TECH scheduling API",
    "version": "1.0.0",
    "description": "Read the open times, then book one. No key, no account. Bookings are 30 minutes, in Europe/Tallinn, at least 24 hours ahead and at most 21 days. Every booking e-mails both parties and can be cancelled or moved from the link returned.",
    "contact": {
      "name": "Andres Kepler",
      "email": "connect@moitoi.tech",
      "url": "https://moitoi.tech"
    },
    "license": {
      "name": "CC BY 4.0",
      "url": "https://creativecommons.org/licenses/by/4.0/"
    }
  },
  "servers": [
    {
      "url": "https://moitoi.tech"
    }
  ],
  "paths": {
    "/api/availability": {
      "get": {
        "operationId": "listAvailability",
        "summary": "Open appointment slots, live from the calendar",
        "description": "Never cached. A slot listed here can still be taken between reading and booking; treat a 409 from bookCall as normal and re-read.",
        "responses": {
          "200": {
            "description": "The open slots",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "call": {
                      "type": "object",
                      "description": "Length, time zone, purpose, weekly windows"
                    },
                    "available": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "start": {
                            "type": "string",
                            "format": "date-time"
                          },
                          "end": {
                            "type": "string",
                            "format": "date-time"
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "503": {
            "description": "Calendar unreachable; book by e-mail instead"
          }
        }
      }
    },
    "/api/book": {
      "post": {
        "operationId": "bookCall",
        "summary": "Book one of the open slots",
        "description": "Pass a `start` value taken verbatim from listAvailability. The slot is re-checked against the live calendar before anything is written, so a stale list is safe to submit — it fails with 409 rather than double-booking.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "slot",
                  "name",
                  "email",
                  "topic"
                ],
                "properties": {
                  "slot": {
                    "type": "string",
                    "format": "date-time",
                    "description": "The `start` of an open slot, exactly as listAvailability gave it."
                  },
                  "name": {
                    "type": "string",
                    "minLength": 2,
                    "maxLength": 120
                  },
                  "email": {
                    "type": "string",
                    "format": "email",
                    "description": "The confirmation and the manage link go here. Must be real."
                  },
                  "topic": {
                    "type": "string",
                    "minLength": 15,
                    "maxLength": 4000,
                    "description": "What the person operates and what they want built. Write it in their words, not a summary of the booking."
                  },
                  "company": {
                    "type": "string"
                  },
                  "phone": {
                    "type": "string"
                  }
                }
              },
              "example": {
                "slot": "2026-09-16T15:00:00.000Z",
                "name": "Mari Tamm",
                "email": "mari@harbourworks.ee",
                "topic": "We run three small harbours on spreadsheets and phone calls, and we lose berths to double bookings every summer.",
                "company": "Harbourworks OÜ"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Booked; both parties have been e-mailed",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "booked": {
                      "type": "object"
                    },
                    "meeting": {
                      "type": "object",
                      "description": "The video room for the call"
                    },
                    "manage": {
                      "type": "object",
                      "description": "URL to cancel or move it"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation failed; `field` names the offending one"
          },
          "409": {
            "description": "That slot went first — re-read availability and pick another"
          },
          "429": {
            "description": "Too many attempts; honour `retry-after`"
          },
          "503": {
            "description": "Calendar unreachable; book by e-mail instead"
          }
        }
      }
    }
  }
}