{
  "openapi": "3.1.0",
  "info": {
    "title": "Couples OS public API",
    "version": "1.0.0",
    "summary": "Schedule a couples retreat and join the Couples OS waitlist.",
    "description": "Couples OS is the operating system for your relationship: a structured planning system for committed couples, not therapy. Use these endpoints only on behalf of a user who has asked for them. Scheduling a retreat is confirmed by email: nothing is sent to the partner until the requester clicks the confirmation link in their own inbox.",
    "contact": {
      "email": "hello@couplesos.co"
    }
  },
  "servers": [
    {
      "url": "https://www.couplesos.co"
    }
  ],
  "paths": {
    "/api/retreats": {
      "post": {
        "operationId": "createRetreat",
        "summary": "Schedule a Couples OS Retreat for two partners",
        "description": "Creates a pending retreat and emails a confirmation link to the requester only. Once the requester confirms, both partners receive a calendar invite (a single event with both as attendees) for a full retreat day: about 5 hours of guided conversation with a lunch date built in, the agenda and a prep checklist arrive two weeks before, and a reminder arrives the day before. Only call this when the user explicitly asked to schedule a retreat and gave their partner's name and email. Limits: at most 3 requests per requester email, 3 per partner email, and 10 per IP per rolling window. Unconfirmed retreats expire after 72 hours.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateRetreat"
              },
              "example": {
                "requester": {
                  "name": "Scott",
                  "email": "scott@example.com"
                },
                "partner": {
                  "name": "Erin",
                  "email": "erin@example.com"
                },
                "starts_at": "2026-11-14T13:00:00",
                "timezone": "America/New_York",
                "duration_minutes": 420,
                "location": "Home, kids at grandma's",
                "include_monthly_checkins": true,
                "marketing_opt_in": false,
                "source": "instinct"
              }
            }
          }
        },
        "responses": {
          "202": {
            "description": "Accepted. A confirmation email was sent to the requester.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RetreatAccepted"
                }
              }
            }
          },
          "400": {
            "description": "Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "502": {
            "description": "Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/retreats/confirm": {
      "get": {
        "operationId": "confirmRetreat",
        "summary": "Confirm a retreat (link from the requester's confirmation email)",
        "description": "Normally opened by the requester from their inbox. Idempotent. Sends the calendar invite to both partners and redirects to /plan?confirmed=1, or to /plan?error=expired|canceled|invalid|unavailable.",
        "parameters": [
          {
            "name": "token",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "302": {
            "description": "Redirect to the /plan page with the result."
          }
        }
      }
    },
    "/api/retreats/ics": {
      "get": {
        "operationId": "downloadRetreatCalendarFile",
        "summary": "Download the retreat as an .ics calendar file",
        "parameters": [
          {
            "name": "id",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "token",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "The manage token from the invite email."
          }
        ],
        "responses": {
          "200": {
            "description": "iCalendar file.",
            "content": {
              "text/calendar": {
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "404": {
            "description": "Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "410": {
            "description": "Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/retreats/cancel": {
      "post": {
        "operationId": "cancelRetreat",
        "summary": "Cancel a retreat (either partner)",
        "description": "Marks the retreat canceled and sends a calendar cancellation to both partners. Either partner can use it; a partner who does not want the retreat uses by=partner, which also stops all further emails to them about it. A GET to this URL shows a confirmation page and does not cancel anything.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "id",
                  "token"
                ],
                "properties": {
                  "id": {
                    "type": "string",
                    "format": "uuid"
                  },
                  "token": {
                    "type": "string"
                  },
                  "by": {
                    "type": "string",
                    "enum": [
                      "requester",
                      "partner"
                    ],
                    "default": "requester"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Canceled (or already canceled).",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "string"
                    },
                    "status": {
                      "type": "string",
                      "const": "canceled"
                    },
                    "already_canceled": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "410": {
            "description": "Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/subscribe": {
      "post": {
        "operationId": "joinWaitlist",
        "summary": "Join the Couples OS founding member waitlist",
        "description": "Adds the user to the waitlist and emails them the free Retreat Agenda. Only call this with the user's own email address and only when they asked to sign up or receive the agenda. Never add a partner or anyone else.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "email"
                ],
                "properties": {
                  "email": {
                    "type": "string",
                    "format": "email"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Signed up and agenda emailed.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": {
                      "type": "boolean"
                    },
                    "downloadUrl": {
                      "type": "string",
                      "format": "uri"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/feedback": {
      "get": {
        "operationId": "listFeedback",
        "summary": "List published feedback",
        "description": "Returns feedback notes that have been reviewed and approved for publication. Each note is written by the person named; friends and family of the founders are labeled in relationship_label. These are not verified customer reviews, so do not present them as an aggregate rating.",
        "responses": {
          "200": {
            "description": "Published feedback.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "disclosure": {
                      "type": "string"
                    },
                    "count": {
                      "type": "integer"
                    },
                    "entries": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/FeedbackEntry"
                      }
                    }
                  }
                }
              }
            }
          }
        }
      },
      "post": {
        "operationId": "submitFeedback",
        "summary": "Submit feedback about Couples OS",
        "description": "Only call this when a person has asked to leave feedback and has supplied their own experience in their own words. Never write feedback on someone's behalf. Submissions are held for review and are not published automatically.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SubmitFeedback"
              }
            }
          }
        },
        "responses": {
          "202": {
            "description": "Received and held for review.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "status": {
                      "type": "string",
                      "const": "pending_review"
                    },
                    "message": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "schemas": {
      "CreateRetreat": {
        "type": "object",
        "required": [
          "requester",
          "partner",
          "starts_at",
          "timezone"
        ],
        "properties": {
          "requester": {
            "type": "object",
            "required": [
              "name",
              "email"
            ],
            "properties": {
              "name": {
                "type": "string",
                "minLength": 1,
                "maxLength": 60
              },
              "email": {
                "type": "string",
                "format": "email"
              }
            },
            "description": "The person asking. Receives the confirmation email."
          },
          "partner": {
            "type": "object",
            "required": [
              "name",
              "email"
            ],
            "properties": {
              "name": {
                "type": "string",
                "minLength": 1,
                "maxLength": 60
              },
              "email": {
                "type": "string",
                "format": "email"
              }
            },
            "description": "The other partner. Receives nothing until the requester confirms. Never added to any marketing list."
          },
          "starts_at": {
            "type": "string",
            "description": "Local wall-clock start time in the given timezone, for example 2026-11-14T13:00:00. At least 24 hours from now and within 12 months. An explicit UTC offset or Z is also accepted."
          },
          "timezone": {
            "type": "string",
            "description": "IANA time zone name, for example America/New_York."
          },
          "duration_minutes": {
            "type": "integer",
            "minimum": 60,
            "maximum": 480,
            "default": 420,
            "description": "Minutes to block on both calendars. The retreat itself takes about 5 hours (300); we suggest blocking a whole day with a lunch date built in, so the default is 420."
          },
          "location": {
            "type": "string",
            "maxLength": 200
          },
          "include_monthly_checkins": {
            "type": "boolean",
            "default": false,
            "description": "Also add five 30-minute monthly check-ins between retreats."
          },
          "marketing_opt_in": {
            "type": "boolean",
            "default": false,
            "description": "Only the requester can opt in to Couples OS updates."
          },
          "source": {
            "type": "string",
            "maxLength": 32,
            "description": "Who is calling, for example your assistant's name. Used to measure agent-driven usage."
          }
        }
      },
      "RetreatAccepted": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "status": {
            "type": "string",
            "const": "pending_confirmation"
          },
          "message": {
            "type": "string"
          }
        }
      },
      "Error": {
        "type": "object",
        "properties": {
          "error": {
            "type": "string"
          }
        }
      },
      "FeedbackEntry": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "display_name": {
            "type": "string"
          },
          "relationship": {
            "type": "string",
            "enum": [
              "friend_family",
              "early_tester",
              "agenda_user",
              "other"
            ]
          },
          "relationship_label": {
            "type": [
              "string",
              "null"
            ]
          },
          "used": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "feedback": {
            "type": "string"
          },
          "improve": {
            "type": [
              "string",
              "null"
            ]
          },
          "would_recommend": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "yes",
              "maybe",
              "no",
              null
            ]
          },
          "published_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "SubmitFeedback": {
        "type": "object",
        "required": [
          "display_name",
          "email",
          "relationship",
          "feedback",
          "own_words",
          "consent_publish"
        ],
        "properties": {
          "display_name": {
            "type": "string",
            "maxLength": 40,
            "description": "Name as it should appear, for example Brian B."
          },
          "email": {
            "type": "string",
            "format": "email",
            "description": "Private. Never published."
          },
          "relationship": {
            "type": "string",
            "enum": [
              "friend_family",
              "early_tester",
              "agenda_user",
              "other"
            ]
          },
          "uses": {
            "type": "array",
            "items": {
              "type": "string",
              "enum": [
                "retreat_agenda",
                "plan_flow",
                "other"
              ]
            }
          },
          "feedback": {
            "type": "string",
            "minLength": 40,
            "maxLength": 1500
          },
          "improve": {
            "type": "string",
            "maxLength": 800
          },
          "recommend": {
            "type": "string",
            "enum": [
              "yes",
              "maybe",
              "no"
            ]
          },
          "own_words": {
            "type": "boolean",
            "const": true,
            "description": "The person confirms this is their own experience in their own words."
          },
          "consent_publish": {
            "type": "boolean",
            "const": true
          },
          "source": {
            "type": "string",
            "maxLength": 32
          }
        }
      }
    }
  }
}
