{
  "openapi": "3.0.3",
  "info": {
    "title": "TinyLaunch Agent API",
    "version": "1.0.0",
    "description": "Sign up users, manage maker profiles, register startups, and schedule launches on TinyLaunch from an AI agent. See /llms.txt for a narrative guide.",
    "contact": {
      "email": "chris@tinylaunch.com"
    }
  },
  "servers": [
    {
      "url": "https://www.tinylaunch.com/api/v1"
    }
  ],
  "components": {
    "securitySchemes": {
      "bearerAuth": {
        "type": "http",
        "scheme": "bearer",
        "bearerFormat": "JWT"
      }
    },
    "schemas": {
      "Error": {
        "type": "object",
        "required": [
          "error"
        ],
        "properties": {
          "error": {
            "type": "string"
          },
          "detail": {}
        }
      },
      "Maker": {
        "type": "object",
        "properties": {
          "id": {
            "type": "integer"
          },
          "user_id": {
            "type": "string",
            "format": "uuid"
          },
          "first_name": {
            "type": "string"
          },
          "last_name": {
            "type": "string",
            "nullable": true
          },
          "handle": {
            "type": "string"
          },
          "x_handle": {
            "type": "string",
            "nullable": true
          }
        }
      },
      "MakerInput": {
        "type": "object",
        "required": [
          "firstName",
          "handle"
        ],
        "properties": {
          "firstName": {
            "type": "string",
            "minLength": 1,
            "maxLength": 20
          },
          "lastName": {
            "type": "string",
            "maxLength": 20,
            "nullable": true
          },
          "handle": {
            "type": "string",
            "minLength": 5,
            "maxLength": 20,
            "pattern": "^[a-zA-Z0-9_]+$"
          },
          "xHandle": {
            "type": "string",
            "maxLength": 20,
            "nullable": true,
            "description": "Must start with @ followed by [a-zA-Z0-9_]"
          }
        }
      },
      "LogoInput": {
        "type": "object",
        "required": [
          "filename",
          "content_base64"
        ],
        "properties": {
          "filename": {
            "type": "string",
            "description": "Must end in .png, .jpg, .jpeg, or .webp"
          },
          "content_base64": {
            "type": "string",
            "description": "Base64-encoded image bytes (max 209715 bytes ≈ 200 KB)."
          }
        }
      },
      "StartupInput": {
        "type": "object",
        "required": [
          "name",
          "url",
          "category_id"
        ],
        "properties": {
          "name": {
            "type": "string",
            "minLength": 1,
            "maxLength": 30
          },
          "tagline": {
            "type": "string",
            "maxLength": 60
          },
          "description": {
            "type": "string",
            "maxLength": 10000,
            "description": "HTML (restricted): <p>, <br>, <h1>..<h3>, <strong>/<b>, <em>/<i>, <u>, <s>, <ul>, <ol>, <li>. Other tags are stripped server-side."
          },
          "url": {
            "type": "string",
            "format": "uri",
            "pattern": "^https?://"
          },
          "category_id": {
            "type": "integer",
            "description": "Required. Fetch the full id→name list at GET /api/v1/categories and pick the best semantic match.",
            "enum": [
              5,
              7,
              6,
              8,
              11,
              10,
              12,
              9,
              20,
              21,
              22,
              15,
              13,
              14,
              16,
              32,
              33,
              31,
              26,
              27,
              28,
              34,
              35,
              36,
              18,
              19,
              17,
              23,
              25,
              24,
              2,
              3,
              1,
              4,
              30,
              29
            ]
          },
          "logo": {
            "$ref": "#/components/schemas/LogoInput"
          }
        }
      },
      "Startup": {
        "type": "object",
        "properties": {
          "id": {
            "type": "integer"
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "name": {
            "type": "string"
          },
          "tagline": {
            "type": "string",
            "nullable": true
          },
          "description": {
            "type": "string",
            "nullable": true
          },
          "url": {
            "type": "string"
          },
          "logo_path": {
            "type": "string",
            "nullable": true
          },
          "image_paths": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "nullable": true
          },
          "maker_id": {
            "type": "integer"
          },
          "slug": {
            "type": "string"
          },
          "category_id": {
            "type": "integer",
            "nullable": true
          },
          "category_name": {
            "type": "string",
            "nullable": true
          }
        }
      },
      "LaunchWindow": {
        "type": "object",
        "properties": {
          "date": {
            "type": "string",
            "format": "date"
          },
          "available_slots": {
            "type": "integer"
          },
          "status": {
            "type": "string",
            "enum": [
              "available",
              "full"
            ]
          },
          "premium_only": {
            "type": "boolean"
          }
        }
      },
      "Launch": {
        "type": "object",
        "properties": {
          "id": {
            "type": "integer"
          },
          "startup_id": {
            "type": "integer"
          },
          "date": {
            "type": "string",
            "format": "date"
          },
          "premium": {
            "type": "boolean"
          },
          "paid": {
            "type": "boolean"
          },
          "authorized": {
            "type": "boolean"
          }
        }
      }
    }
  },
  "security": [
    {
      "bearerAuth": []
    }
  ],
  "paths": {
    "/auth/request-code": {
      "post": {
        "summary": "Request an OTP code",
        "security": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "email"
                ],
                "properties": {
                  "email": {
                    "type": "string",
                    "format": "email"
                  },
                  "create_user": {
                    "type": "boolean",
                    "default": true
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Code sent (or pretended-sent to prevent enumeration).",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "status": {
                      "type": "string",
                      "enum": [
                        "sent"
                      ]
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/auth/verify": {
      "post": {
        "summary": "Verify OTP and get a bearer token",
        "security": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "email",
                  "code"
                ],
                "properties": {
                  "email": {
                    "type": "string",
                    "format": "email"
                  },
                  "code": {
                    "type": "string"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Verified. Use access_token as Bearer.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "access_token": {
                      "type": "string"
                    },
                    "refresh_token": {
                      "type": "string"
                    },
                    "user_id": {
                      "type": "string"
                    },
                    "email": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "verification_failed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/categories": {
      "get": {
        "summary": "List all startup categories (id + name + group)",
        "description": "Always call this before creating a startup so the agent picks a real category instead of guessing.",
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "categories": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "id": {
                            "type": "integer"
                          },
                          "category_group": {
                            "type": "string"
                          },
                          "category_name": {
                            "type": "string"
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/me": {
      "get": {
        "summary": "Current user info",
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "user_id": {
                      "type": "string"
                    },
                    "email": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/maker": {
      "get": {
        "summary": "Get the current user’s maker profile",
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Maker"
                }
              }
            }
          },
          "404": {
            "description": "not_found"
          }
        }
      },
      "post": {
        "summary": "Create the current user’s maker profile",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/MakerInput"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Created",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Maker"
                }
              }
            }
          },
          "409": {
            "description": "maker_exists or handle_taken"
          }
        }
      },
      "patch": {
        "summary": "Update the current user’s maker profile",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "allOf": [
                  {
                    "$ref": "#/components/schemas/MakerInput"
                  }
                ],
                "description": "All fields optional on PATCH."
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Updated",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Maker"
                }
              }
            }
          },
          "404": {
            "description": "not_found"
          },
          "409": {
            "description": "handle_taken"
          }
        }
      }
    },
    "/startups": {
      "get": {
        "summary": "List the current user’s startups",
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "startups": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Startup"
                      }
                    }
                  }
                }
              }
            }
          }
        }
      },
      "post": {
        "summary": "Create a startup",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/StartupInput"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Created",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Startup"
                }
              }
            }
          },
          "400": {
            "description": "invalid_request, invalid_category, invalid_logo_format, or invalid_logo_encoding"
          },
          "409": {
            "description": "no_maker_profile"
          },
          "413": {
            "description": "logo_too_large"
          }
        }
      }
    },
    "/startups/{id}": {
      "parameters": [
        {
          "name": "id",
          "in": "path",
          "required": true,
          "schema": {
            "type": "integer"
          }
        }
      ],
      "get": {
        "summary": "Get one of your startups",
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Startup"
                }
              }
            }
          },
          "403": {
            "description": "forbidden"
          },
          "404": {
            "description": "not_found"
          }
        }
      },
      "patch": {
        "summary": "Update one of your startups",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "allOf": [
                  {
                    "$ref": "#/components/schemas/StartupInput"
                  }
                ],
                "description": "All fields optional on PATCH."
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Updated",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Startup"
                }
              }
            }
          },
          "403": {
            "description": "forbidden"
          },
          "404": {
            "description": "not_found"
          },
          "413": {
            "description": "logo_too_large"
          }
        }
      }
    },
    "/launch-dates": {
      "get": {
        "summary": "List upcoming launch windows",
        "parameters": [
          {
            "name": "offset",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 0,
              "maximum": 200,
              "default": 0
            },
            "description": "Skip this many valid launch days. Page size is fixed at 10."
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "windows": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/LaunchWindow"
                      }
                    },
                    "offset": {
                      "type": "integer"
                    },
                    "page_size": {
                      "type": "integer"
                    },
                    "next_offset": {
                      "type": "integer",
                      "nullable": true
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/launches": {
      "post": {
        "summary": "Schedule a free or premium launch",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "startup_id",
                  "date"
                ],
                "properties": {
                  "startup_id": {
                    "type": "integer"
                  },
                  "date": {
                    "type": "string",
                    "format": "date",
                    "description": "YYYY-MM-DD. Must be one of the dates returned by GET /launch-dates."
                  },
                  "premium": {
                    "type": "boolean",
                    "default": false,
                    "description": "If true, schedules a premium (paid) launch. Response includes payment_url; launch stays paid:false until the user completes checkout."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Launch scheduled. For premium launches, the response also includes a payment_url the user must visit to pay; the launch is not active until payment completes.",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Launch"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "payment_url": {
                          "type": "string",
                          "format": "uri",
                          "description": "Dodo Payments checkout URL — present only when premium=true. Forward this link to the user."
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "description": "invalid_request or date_not_bookable"
          },
          "403": {
            "description": "forbidden"
          },
          "409": {
            "description": "duplicate_launch or date_full"
          }
        }
      }
    }
  }
}