{
  "openapi": "3.1.0",
  "info": {
    "title": "LoopGantt API",
    "description": "Create and manage Gantt chart projects programmatically. Perfect for AI-generated project plans.",
    "version": "1.0.0",
    "contact": {
      "name": "LoopGantt Support",
      "url": "https://loopgantt.com"
    }
  },
  "servers": [
    {
      "url": "https://loopgantt.com/api/v1",
      "description": "Production API"
    }
  ],
  "security": [
    {
      "bearerAuth": []
    }
  ],
  "paths": {
    "/projects": {
      "post": {
        "operationId": "createProject",
        "summary": "Create a project with tasks (authenticated)",
        "description": "Creates a new project with tasks and dependencies. Requires API key authentication. Returns a URL to view the Gantt chart.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateProjectRequest"
              },
              "example": {
                "name": "Kitchen Renovation",
                "description": "Complete kitchen renovation project",
                "startDate": "2024-02-01",
                "tasks": [
                  {
                    "name": "Design planning",
                    "duration": 5,
                    "description": "Create detailed design plans"
                  },
                  {
                    "name": "Order materials",
                    "duration": 3,
                    "dependencies": [0],
                    "description": "Order cabinets, countertops, and appliances"
                  },
                  {
                    "name": "Demolition",
                    "duration": 2,
                    "dependencies": [0],
                    "description": "Remove old cabinets and fixtures"
                  },
                  {
                    "name": "Install cabinets",
                    "duration": 4,
                    "dependencies": [1, 2],
                    "description": "Install new cabinetry"
                  },
                  {
                    "name": "Install countertops",
                    "duration": 2,
                    "dependencies": [3],
                    "description": "Install granite countertops"
                  },
                  {
                    "name": "Final inspection",
                    "duration": 1,
                    "dependencies": [4],
                    "isMilestone": true,
                    "description": "Project completion milestone"
                  }
                ]
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Project created successfully",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CreateProjectResponse"
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Authentication required",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "get": {
        "operationId": "listProjects",
        "summary": "List user's projects",
        "description": "Returns a list of all projects owned by the authenticated user.",
        "responses": {
          "200": {
            "description": "List of projects",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ListProjectsResponse"
                }
              }
            }
          },
          "401": {
            "description": "Authentication required",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/gantt": {
      "post": {
        "operationId": "createGantt",
        "summary": "Create a Gantt chart without an account",
        "description": "Keyless: no API key, no account. Send the plan (tasks with durations and dependencies); LoopGantt stores it as an unclaimed project, schedules it with its critical-path engine and returns a claim link where the user can view and export the chart (PNG/PDF) and save it to a free account. Unclaimed charts expire after 7 days (30 days once the link is opened). Rate limited: 10 per hour and 30 per day per network.",
        "security": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateGanttRequest"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Gantt chart created",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CreateGanttResponse"
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "413": {
            "description": "Body larger than 2 MB",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "The plan could not be scheduled",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded (see Retry-After)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Rate limiting temporarily unavailable — retry",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/gantt/{token}": {
      "get": {
        "operationId": "getGanttByToken",
        "summary": "Read the chart behind a claim link",
        "description": "Returns the unclaimed project, its tasks, dependencies and computed schedule. The token is the last path segment of the claim link. Opening extends the expiry to 30 days from creation.",
        "security": [],
        "parameters": [
          {
            "name": "token",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Chart data",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GanttPreviewResponse"
                }
              }
            }
          },
          "404": {
            "description": "Unknown link",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "410": {
            "description": "Expired",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Temporarily unavailable — the link is not gone, retry",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/schedule": {
      "post": {
        "operationId": "scheduleProject",
        "summary": "Compute a schedule without storing anything",
        "description": "Critical-path scheduling for a task list: dates, critical path, float and project end. Nothing is stored; no account, no key. Same validation and limits as /gantt. Rate limited: 60 per hour per network.",
        "security": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ScheduleRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Schedule",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ScheduleResponse"
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "413": {
            "description": "Body larger than 2 MB",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "The plan could not be scheduled",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded (see Retry-After)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/templates": {
      "get": {
        "operationId": "listTemplates",
        "summary": "List the industry templates",
        "description": "Software, construction, marketing, events, IT, HR and finance templates with task counts and typical durations. No key. Cached for an hour.",
        "security": [],
        "responses": {
          "200": {
            "description": "Templates",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/TemplateSummary"
                      }
                    },
                    "meta": {
                      "type": "object",
                      "properties": {
                        "count": {
                          "type": "integer"
                        }
                      }
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/templates/{slug}": {
      "get": {
        "operationId": "getTemplate",
        "summary": "One template with its task list",
        "description": "The task list (durations, milestones) ready to adapt and pass to POST /gantt.",
        "security": [],
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Template",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/TemplateDetail"
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "Unknown template",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/gantt/{token}/image.png": {
      "get": {
        "operationId": "getGanttImage",
        "summary": "The chart behind a claim link as a PNG",
        "description": "Server-rendered picture of the scheduled chart (critical path highlighted, milestones as diamonds, at most 40 rows). Does not extend the expiry.",
        "security": [],
        "parameters": [
          {
            "name": "token",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "PNG image",
            "content": {
              "image/png": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              }
            }
          },
          "404": {
            "description": "Unknown link",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "410": {
            "description": "Expired",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Temporarily unavailable — the link is not gone, retry",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "bearerAuth": {
        "type": "http",
        "scheme": "bearer",
        "description": "API key in format: sk_live_xxx"
      }
    },
    "schemas": {
      "CreateProjectRequest": {
        "type": "object",
        "required": ["name", "tasks"],
        "properties": {
          "name": {
            "type": "string",
            "description": "Project name",
            "maxLength": 200
          },
          "description": {
            "type": "string",
            "description": "Optional project description"
          },
          "startDate": {
            "type": "string",
            "format": "date",
            "description": "Project start date (YYYY-MM-DD). Defaults to today if not specified."
          },
          "tasks": {
            "type": "array",
            "minItems": 1,
            "description": "List of tasks to create",
            "items": {
              "$ref": "#/components/schemas/TaskInput"
            }
          }
        }
      },
      "TaskInput": {
        "type": "object",
        "required": ["name"],
        "properties": {
          "name": {
            "type": "string",
            "description": "Task name",
            "maxLength": 200
          },
          "duration": {
            "type": "integer",
            "minimum": 1,
            "default": 1,
            "description": "Duration in days"
          },
          "dependencies": {
            "type": "array",
            "items": {
              "type": "integer",
              "minimum": 0
            },
            "description": "Array of task indices this task depends on (0-based). Dependencies must reference earlier tasks."
          },
          "description": {
            "type": "string",
            "description": "Optional task description"
          },
          "isMilestone": {
            "type": "boolean",
            "default": false,
            "description": "If true, task is displayed as a milestone (diamond shape)"
          }
        }
      },
      "CreateProjectResponse": {
        "type": "object",
        "properties": {
          "data": {
            "type": "object",
            "properties": {
              "id": {
                "type": "string",
                "format": "uuid",
                "description": "Project ID"
              },
              "name": {
                "type": "string",
                "description": "Project name"
              },
              "description": {
                "type": "string",
                "nullable": true,
                "description": "Project description"
              },
              "startDate": {
                "type": "string",
                "format": "date",
                "description": "Project start date"
              },
              "taskCount": {
                "type": "integer",
                "description": "Number of tasks created"
              },
              "dependencyCount": {
                "type": "integer",
                "description": "Number of dependencies created"
              },
              "url": {
                "type": "string",
                "format": "uri",
                "description": "URL to view the Gantt chart"
              },
              "imageUrl": {
                "type": "string",
                "format": "uri",
                "description": "URL to a preview image (for link previews and sharing)"
              }
            }
          },
          "meta": {
            "type": "object",
            "properties": {
              "message": {
                "type": "string",
                "description": "Success message"
              }
            }
          }
        }
      },
      "ListProjectsResponse": {
        "type": "object",
        "properties": {
          "data": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "id": {
                  "type": "string",
                  "format": "uuid"
                },
                "name": {
                  "type": "string"
                },
                "description": {
                  "type": "string",
                  "nullable": true
                },
                "startDate": {
                  "type": "string",
                  "format": "date"
                },
                "status": {
                  "type": "string",
                  "enum": ["active", "archived"]
                },
                "createdAt": {
                  "type": "string",
                  "format": "date-time"
                },
                "url": {
                  "type": "string",
                  "format": "uri"
                }
              }
            }
          },
          "meta": {
            "type": "object",
            "properties": {
              "count": {
                "type": "integer"
              }
            }
          }
        }
      },
      "Error": {
        "type": "object",
        "properties": {
          "error": {
            "type": "object",
            "properties": {
              "code": {
                "type": "string",
                "description": "Error code"
              },
              "message": {
                "type": "string",
                "description": "Human-readable error message"
              }
            }
          }
        }
      },
      "DependencyInput": {
        "type": "object",
        "required": ["task"],
        "properties": {
          "task": {
            "type": "integer",
            "minimum": 0,
            "description": "Index (0-based) of an EARLIER task this task depends on"
          },
          "type": {
            "type": "string",
            "enum": ["FS", "SS", "FF", "SF"],
            "default": "FS",
            "description": "Dependency type: finish-to-start (default), start-to-start, finish-to-finish, start-to-finish"
          },
          "lag": {
            "type": "integer",
            "minimum": -365,
            "maximum": 365,
            "default": 0,
            "description": "Lag (positive) or lead (negative) in working days"
          }
        }
      },
      "GanttTaskInput": {
        "type": "object",
        "required": ["name"],
        "properties": {
          "name": {
            "type": "string",
            "maxLength": 200,
            "description": "Task name"
          },
          "duration": {
            "type": "integer",
            "minimum": 0,
            "maximum": 3650,
            "default": 1,
            "description": "Duration in working days. 0 makes the task a milestone."
          },
          "isMilestone": {
            "type": "boolean",
            "default": false,
            "description": "Milestone (zero duration, drawn as a diamond)"
          },
          "description": {
            "type": "string",
            "maxLength": 2000
          },
          "dependencies": {
            "type": "array",
            "maxItems": 20,
            "description": "Predecessors: plain task indices (0-based, earlier tasks only) or objects with type/lag",
            "items": {
              "oneOf": [
                {
                  "type": "integer",
                  "minimum": 0
                },
                {
                  "$ref": "#/components/schemas/DependencyInput"
                }
              ]
            }
          }
        }
      },
      "CreateGanttRequest": {
        "type": "object",
        "required": ["name", "tasks"],
        "properties": {
          "name": {
            "type": "string",
            "maxLength": 200,
            "description": "Project name"
          },
          "description": {
            "type": "string",
            "maxLength": 2000
          },
          "startDate": {
            "type": "string",
            "format": "date",
            "description": "Project start (YYYY-MM-DD). Defaults to today."
          },
          "workDays": {
            "type": "array",
            "items": {
              "type": "integer",
              "minimum": 1,
              "maximum": 7
            },
            "description": "Working weekdays as ISO numbers (1 = Monday … 7 = Sunday). Defaults to Mon–Fri."
          },
          "holidays": {
            "type": "array",
            "maxItems": 200,
            "items": {
              "type": "string",
              "format": "date"
            },
            "description": "Non-working dates (YYYY-MM-DD)"
          },
          "tasks": {
            "type": "array",
            "minItems": 1,
            "maxItems": 300,
            "items": {
              "$ref": "#/components/schemas/GanttTaskInput"
            },
            "description": "Tasks in execution order. Durations + lags may add up to at most 5000 days."
          }
        }
      },
      "ScheduledTask": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "start": {
            "type": "string",
            "format": "date"
          },
          "end": {
            "description": "Last working day of the task (inclusive). Milestones: start = end.",
            "type": "string",
            "format": "date"
          },
          "durationDays": {
            "type": "integer"
          },
          "isMilestone": {
            "type": "boolean"
          },
          "isCritical": {
            "type": "boolean",
            "description": "On the critical path (zero total float)"
          },
          "totalFloat": {
            "type": "integer",
            "description": "Working days the task can slip without moving the project end"
          }
        }
      },
      "ScheduleSummary": {
        "type": "object",
        "description": "Computed by the same critical-path engine as the LoopGantt app",
        "properties": {
          "projectStartDate": {
            "type": "string",
            "format": "date"
          },
          "projectEndDate": {
            "type": "string",
            "format": "date"
          },
          "projectDurationDays": {
            "type": "integer"
          },
          "criticalPath": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Task ids on the critical path, in order"
          },
          "tasks": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ScheduledTask"
            }
          },
          "errors": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "type": {
                  "type": "string"
                },
                "message": {
                  "type": "string"
                },
                "taskIds": {
                  "type": "array",
                  "items": {
                    "type": "string"
                  }
                }
              }
            }
          }
        }
      },
      "CreateGanttResponse": {
        "type": "object",
        "properties": {
          "data": {
            "type": "object",
            "properties": {
              "name": {
                "type": "string"
              },
              "description": {
                "type": "string",
                "nullable": true
              },
              "startDate": {
                "type": "string",
                "format": "date"
              },
              "taskCount": {
                "type": "integer"
              },
              "dependencyCount": {
                "type": "integer"
              },
              "claimUrl": {
                "type": "string",
                "format": "uri",
                "description": "Open to view + export the chart (PNG/PDF) and to save it to an account. Share this link with the user — it is the only handle to the chart."
              },
              "previewUrl": {
                "type": "string",
                "format": "uri",
                "description": "Same as claimUrl"
              },
              "expiresAt": {
                "type": "string",
                "format": "date-time",
                "description": "Deleted after this time unless opened (then 30 days from creation) or claimed"
              },
              "expiresInDays": {
                "type": "integer"
              },
              "schedule": {
                "$ref": "#/components/schemas/ScheduleSummary"
              },
              "imageUrl": {
                "type": "string",
                "format": "uri",
                "description": "PNG of the chart (same token). Show it inline when the client supports images."
              }
            }
          },
          "meta": {
            "type": "object",
            "properties": {
              "message": {
                "type": "string"
              },
              "rateLimit": {
                "type": "object",
                "properties": {
                  "limit": {
                    "type": "integer"
                  },
                  "remaining": {
                    "type": "integer"
                  }
                }
              }
            }
          }
        }
      },
      "GanttPreviewResponse": {
        "type": "object",
        "properties": {
          "data": {
            "type": "object",
            "properties": {
              "project": {
                "type": "object",
                "description": "Project without secrets"
              },
              "tasks": {
                "type": "array",
                "items": {
                  "type": "object"
                }
              },
              "dependencies": {
                "type": "array",
                "items": {
                  "type": "object"
                }
              },
              "schedule": {
                "$ref": "#/components/schemas/ScheduleSummary"
              },
              "expiresAt": {
                "type": "string",
                "format": "date-time"
              },
              "imageUrl": {
                "type": "string",
                "format": "uri"
              }
            }
          }
        }
      },
      "ScheduleRequest": {
        "type": "object",
        "required": ["tasks"],
        "description": "Same shape as CreateGanttRequest; `name` is optional because nothing is stored.",
        "properties": {
          "name": {
            "type": "string",
            "maxLength": 200,
            "description": "Project name"
          },
          "description": {
            "type": "string",
            "maxLength": 2000
          },
          "startDate": {
            "type": "string",
            "format": "date",
            "description": "Project start (YYYY-MM-DD). Defaults to today."
          },
          "workDays": {
            "type": "array",
            "items": {
              "type": "integer",
              "minimum": 1,
              "maximum": 7
            },
            "description": "Working weekdays as ISO numbers (1 = Monday … 7 = Sunday). Defaults to Mon–Fri."
          },
          "holidays": {
            "type": "array",
            "maxItems": 200,
            "items": {
              "type": "string",
              "format": "date"
            },
            "description": "Non-working dates (YYYY-MM-DD)"
          },
          "tasks": {
            "type": "array",
            "minItems": 1,
            "maxItems": 300,
            "items": {
              "$ref": "#/components/schemas/GanttTaskInput"
            },
            "description": "Tasks in execution order. Durations + lags may add up to at most 5000 days."
          }
        }
      },
      "ScheduleResponse": {
        "type": "object",
        "properties": {
          "data": {
            "type": "object",
            "properties": {
              "projectStartDate": {
                "type": "string",
                "format": "date"
              },
              "projectEndDate": {
                "type": "string",
                "format": "date"
              },
              "projectDurationDays": {
                "type": "integer"
              },
              "criticalPath": {
                "type": "array",
                "items": {
                  "type": "integer"
                },
                "description": "Task indices on the critical path, in order"
              },
              "tasks": {
                "type": "array",
                "items": {
                  "type": "object",
                  "properties": {
                    "index": {
                      "type": "integer"
                    },
                    "name": {
                      "type": "string"
                    },
                    "start": {
                      "type": "string",
                      "format": "date"
                    },
                    "end": {
                      "description": "Last working day of the task (inclusive). Milestones: start = end.",
                      "type": "string",
                      "format": "date"
                    },
                    "durationDays": {
                      "type": "integer"
                    },
                    "isMilestone": {
                      "type": "boolean"
                    },
                    "isCritical": {
                      "type": "boolean"
                    },
                    "totalFloat": {
                      "type": "integer"
                    }
                  }
                }
              }
            }
          },
          "meta": {
            "type": "object",
            "properties": {
              "rateLimit": {
                "type": "object",
                "properties": {
                  "limit": {
                    "type": "integer"
                  },
                  "remaining": {
                    "type": "integer"
                  }
                }
              }
            }
          }
        }
      },
      "TemplateSummary": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "slug": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "description": {
            "type": "string"
          },
          "category": {
            "type": "string"
          },
          "categoryLabel": {
            "type": "string"
          },
          "taskCount": {
            "type": "integer"
          },
          "estimatedDays": {
            "type": "integer"
          },
          "url": {
            "type": "string",
            "format": "uri",
            "description": "The template page on loopgantt.com"
          }
        }
      },
      "TemplateDetail": {
        "allOf": [
          {
            "$ref": "#/components/schemas/TemplateSummary"
          },
          {
            "type": "object",
            "properties": {
              "useCase": {
                "type": "string"
              },
              "whyLoopGantt": {
                "type": "string"
              },
              "tasks": {
                "type": "array",
                "items": {
                  "type": "object",
                  "properties": {
                    "index": {
                      "type": "integer"
                    },
                    "name": {
                      "type": "string"
                    },
                    "duration": {
                      "type": "integer"
                    },
                    "description": {
                      "type": "string"
                    },
                    "isMilestone": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          }
        ]
      }
    }
  }
}
