{
  "openapi": "3.1.0",
  "info": {
    "title": "Harmony REST API",
    "version": "1.0",
    "summary": "Work with your AI Agents in Harmony.",
    "description": "Harmony is the shared surface for human–agent teams: one board where people\nand agents take an idea to a shipped PR.\n\n## Authentication\n\nTwo credentials reach this API, both in a request header. Every endpoint\nbelow needs one, except `GET /v1` and `GET /v1/health`, which are public.\n\n- `X-API-Key: hmy_...` — an API key created at https://gethmy.com/user/keys.\n  It acts for the user who created it, across every workspace that user\n  belongs to.\n- `Authorization: Bearer hmy_at_...` — an OAuth 2.1 access token issued by\n  https://app.gethmy.com. Harmony issues one named scope, `mcp`, and binds\n  each grant to the set of workspaces the user selected at consent time. A\n  request outside that set is refused with `403`, so the workspace binding\n  is the least-privilege boundary, not the scope string. The matching\n  RFC 9728 metadata is published at\n  https://gethmy.com/.well-known/oauth-protected-resource/api.\n\n## Errors\n\nEvery failure answers a JSON body of the shape `{\"error\": \"...\"}` with the\nstatus code listed on the operation.\n\n## Rate limiting\n\nRequests are rate limited per user. Over the limit answers `429`.\n\n## Links\n\n- Marketing: https://gethmy.com\n- Quick setup: https://gethmy.com/llms.txt\n- Full docs: https://gethmy.com/llms-full.txt\n- Authentication guide: https://gethmy.com/auth.md\n- MCP package: https://www.npmjs.com/package/@gethmy/mcp\n- Create an API key: https://gethmy.com/user/keys\n",
    "termsOfService": "https://gethmy.com/terms",
    "contact": {
      "name": "Harmony",
      "email": "hello@gethmy.com",
      "url": "https://gethmy.com"
    },
    "license": {
      "name": "Proprietary",
      "url": "https://gethmy.com/terms"
    }
  },
  "externalDocs": {
    "description": "Full documentation for agents",
    "url": "https://gethmy.com/llms-full.txt"
  },
  "servers": [
    {
      "url": "https://gethmy.com/api",
      "description": "Production (marketing domain)"
    },
    {
      "url": "https://app.gethmy.com/api",
      "description": "Production (app domain)"
    }
  ],
  "security": [
    {
      "ApiKeyAuth": []
    },
    {
      "OAuth2": [
        "mcp"
      ]
    }
  ],
  "tags": [
    {
      "name": "Discovery",
      "description": "Public endpoints that need no credential."
    },
    {
      "name": "Workspaces",
      "description": "Workspaces the credential can reach."
    },
    {
      "name": "Projects",
      "description": "Projects inside a workspace."
    },
    {
      "name": "Board",
      "description": "The full board state for a project."
    },
    {
      "name": "Cards",
      "description": "Create, read, update, move, and search cards."
    },
    {
      "name": "Columns",
      "description": "The columns a card moves through."
    },
    {
      "name": "Labels",
      "description": "Labels on a board and on a card."
    },
    {
      "name": "Subtasks",
      "description": "Checklist items on a card."
    },
    {
      "name": "Links",
      "description": "Relationships between cards."
    },
    {
      "name": "Agents",
      "description": "Prompts generated from a card for an agent to work from."
    },
    {
      "name": "NLU",
      "description": "Natural-language commands against the board."
    },
    {
      "name": "Desktop",
      "description": "Published Harmony Quickbar builds and their download."
    }
  ],
  "paths": {
    "/v1": {
      "get": {
        "tags": [
          "Discovery"
        ],
        "operationId": "getApiIndex",
        "summary": "API index",
        "description": "Public index of this API — the spec URL, the accepted credentials, and\nthe endpoint list. Needs no credential, so an agent can confirm the\nsurface exists before it asks a user for a key.\n",
        "security": [],
        "responses": {
          "200": {
            "description": "The API index.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiIndex"
                }
              }
            }
          }
        }
      }
    },
    "/v1/health": {
      "get": {
        "tags": [
          "Discovery"
        ],
        "operationId": "getHealth",
        "summary": "Liveness probe",
        "description": "Public and unrated. A rate-limited liveness probe reports a false\noutage under exactly the load you need it during.\n",
        "security": [],
        "responses": {
          "200": {
            "description": "The service is up.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Health"
                }
              }
            }
          }
        }
      }
    },
    "/v1/workspaces": {
      "get": {
        "tags": [
          "Workspaces"
        ],
        "operationId": "listWorkspaces",
        "summary": "List workspaces accessible to the credential",
        "description": "An OAuth token lists only the workspaces its grant covers. An API key\nlists every workspace its user belongs to.\n",
        "responses": {
          "200": {
            "description": "The workspaces this credential can reach.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/Workspace"
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/workspaces/{id}/projects": {
      "get": {
        "tags": [
          "Projects"
        ],
        "operationId": "listProjects",
        "summary": "List projects in a workspace",
        "parameters": [
          {
            "$ref": "#/components/parameters/Id"
          }
        ],
        "responses": {
          "200": {
            "description": "The projects in the workspace.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/Project"
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/workspaces/{id}/members": {
      "get": {
        "tags": [
          "Workspaces"
        ],
        "operationId": "listWorkspaceMembers",
        "summary": "Get workspace members",
        "parameters": [
          {
            "$ref": "#/components/parameters/Id"
          }
        ],
        "responses": {
          "200": {
            "description": "The members of the workspace.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/WorkspaceMember"
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/board/{projectId}": {
      "get": {
        "tags": [
          "Board"
        ],
        "operationId": "getBoard",
        "summary": "Get the full board state for a project",
        "parameters": [
          {
            "in": "path",
            "name": "projectId",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Columns, cards, and labels for the project.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Board"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/cards": {
      "post": {
        "tags": [
          "Cards"
        ],
        "operationId": "createCard",
        "summary": "Create a card",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CardCreate"
              },
              "example": {
                "projectId": "6590761b-5b8e-4909-a8d2-c47bc920fbfc",
                "columnId": "c5b4fa13-1db3-40c7-a3ca-8e9426f7b8b8",
                "title": "Return a real 404 for unknown paths",
                "priority": "high"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The created card.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Card"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/cards/{id}": {
      "get": {
        "tags": [
          "Cards"
        ],
        "operationId": "getCard",
        "summary": "Get a card",
        "parameters": [
          {
            "$ref": "#/components/parameters/Id"
          }
        ],
        "responses": {
          "200": {
            "description": "The card.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Card"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      },
      "patch": {
        "tags": [
          "Cards"
        ],
        "operationId": "updateCard",
        "summary": "Update a card",
        "parameters": [
          {
            "$ref": "#/components/parameters/Id"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CardUpdate"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The updated card.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Card"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      },
      "delete": {
        "tags": [
          "Cards"
        ],
        "operationId": "deleteCard",
        "summary": "Delete a card",
        "parameters": [
          {
            "$ref": "#/components/parameters/Id"
          }
        ],
        "responses": {
          "204": {
            "description": "The card was deleted."
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/cards/{id}/move": {
      "post": {
        "tags": [
          "Cards"
        ],
        "operationId": "moveCard",
        "summary": "Move a card to a different column",
        "parameters": [
          {
            "$ref": "#/components/parameters/Id"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "columnId"
                ],
                "properties": {
                  "columnId": {
                    "type": "string",
                    "format": "uuid"
                  },
                  "position": {
                    "type": "integer"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The moved card.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Card"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/search": {
      "get": {
        "tags": [
          "Cards"
        ],
        "operationId": "searchCards",
        "summary": "Search cards",
        "description": "Searches titles and descriptions. An OAuth token only ever matches\ncards inside the workspaces its grant covers.\n",
        "parameters": [
          {
            "in": "query",
            "name": "q",
            "required": true,
            "schema": {
              "type": "string"
            },
            "example": "agent readiness"
          }
        ],
        "responses": {
          "200": {
            "description": "The matching cards.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/Card"
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/columns": {
      "post": {
        "tags": [
          "Columns"
        ],
        "operationId": "createColumn",
        "summary": "Create a column",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "projectId",
                  "name"
                ],
                "properties": {
                  "projectId": {
                    "type": "string",
                    "format": "uuid"
                  },
                  "name": {
                    "type": "string"
                  },
                  "position": {
                    "type": "integer"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The created column.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Column"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          }
        }
      }
    },
    "/v1/columns/{id}": {
      "patch": {
        "tags": [
          "Columns"
        ],
        "operationId": "updateColumn",
        "summary": "Update a column",
        "parameters": [
          {
            "$ref": "#/components/parameters/Id"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "name": {
                    "type": "string"
                  },
                  "position": {
                    "type": "integer"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The updated column.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Column"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      },
      "delete": {
        "tags": [
          "Columns"
        ],
        "operationId": "deleteColumn",
        "summary": "Delete a column",
        "parameters": [
          {
            "$ref": "#/components/parameters/Id"
          }
        ],
        "responses": {
          "204": {
            "description": "The column was deleted."
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/v1/labels": {
      "post": {
        "tags": [
          "Labels"
        ],
        "operationId": "createLabel",
        "summary": "Create a label",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "projectId",
                  "name"
                ],
                "properties": {
                  "projectId": {
                    "type": "string",
                    "format": "uuid"
                  },
                  "name": {
                    "type": "string"
                  },
                  "color": {
                    "type": "string"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The created label.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Label"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      }
    },
    "/v1/cards/{id}/labels": {
      "post": {
        "tags": [
          "Labels"
        ],
        "operationId": "addLabelToCard",
        "summary": "Add a label to a card",
        "parameters": [
          {
            "$ref": "#/components/parameters/Id"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "labelId"
                ],
                "properties": {
                  "labelId": {
                    "type": "string",
                    "format": "uuid"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The label is on the card."
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/v1/cards/{id}/labels/{labelId}": {
      "delete": {
        "tags": [
          "Labels"
        ],
        "operationId": "removeLabelFromCard",
        "summary": "Remove a label from a card",
        "parameters": [
          {
            "$ref": "#/components/parameters/Id"
          },
          {
            "in": "path",
            "name": "labelId",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "204": {
            "description": "The label is off the card."
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/v1/subtasks": {
      "post": {
        "tags": [
          "Subtasks"
        ],
        "operationId": "createSubtask",
        "summary": "Create a subtask",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "cardId",
                  "title"
                ],
                "properties": {
                  "cardId": {
                    "type": "string",
                    "format": "uuid"
                  },
                  "title": {
                    "type": "string"
                  },
                  "position": {
                    "type": "integer"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The created subtask.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Subtask"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      }
    },
    "/v1/subtasks/{id}/toggle": {
      "post": {
        "tags": [
          "Subtasks"
        ],
        "operationId": "toggleSubtask",
        "summary": "Toggle subtask completion",
        "parameters": [
          {
            "$ref": "#/components/parameters/Id"
          }
        ],
        "responses": {
          "200": {
            "description": "The subtask, with its new completion state.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Subtask"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/v1/subtasks/{id}": {
      "delete": {
        "tags": [
          "Subtasks"
        ],
        "operationId": "deleteSubtask",
        "summary": "Delete a subtask",
        "parameters": [
          {
            "$ref": "#/components/parameters/Id"
          }
        ],
        "responses": {
          "204": {
            "description": "The subtask was deleted."
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/v1/cards/{id}/links": {
      "get": {
        "tags": [
          "Links"
        ],
        "operationId": "listCardLinks",
        "summary": "Get card links",
        "parameters": [
          {
            "$ref": "#/components/parameters/Id"
          }
        ],
        "responses": {
          "200": {
            "description": "The links on the card.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/CardLink"
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      },
      "post": {
        "tags": [
          "Links"
        ],
        "operationId": "createCardLink",
        "summary": "Create a card link",
        "parameters": [
          {
            "$ref": "#/components/parameters/Id"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "targetId",
                  "linkType"
                ],
                "properties": {
                  "targetId": {
                    "type": "string",
                    "format": "uuid"
                  },
                  "linkType": {
                    "type": "string",
                    "enum": [
                      "relates_to",
                      "blocks",
                      "duplicates",
                      "is_part_of"
                    ]
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The created link.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CardLink"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/v1/links/{id}": {
      "delete": {
        "tags": [
          "Links"
        ],
        "operationId": "deleteCardLink",
        "summary": "Delete a card link",
        "parameters": [
          {
            "$ref": "#/components/parameters/Id"
          }
        ],
        "responses": {
          "204": {
            "description": "The link was deleted."
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/v1/cards/{id}/prompt": {
      "get": {
        "tags": [
          "Agents"
        ],
        "operationId": "generateCardPrompt",
        "summary": "Generate an AI-ready prompt from a card",
        "description": "Builds role framing, focus areas, subtasks, and linked cards into one\nprompt an agent can work from.\n",
        "parameters": [
          {
            "$ref": "#/components/parameters/Id"
          },
          {
            "in": "query",
            "name": "variant",
            "description": "`analysis` to understand and plan, `draft` to design a solution,\n`execute` to implement fully. Defaults to `execute`.\n",
            "schema": {
              "type": "string",
              "enum": [
                "analysis",
                "draft",
                "execute"
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The generated prompt.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "prompt": {
                      "type": "string"
                    },
                    "variant": {
                      "type": "string",
                      "enum": [
                        "analysis",
                        "draft",
                        "execute"
                      ]
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/v1/nlu": {
      "post": {
        "tags": [
          "NLU"
        ],
        "operationId": "processCommand",
        "summary": "Process a natural-language command",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "command"
                ],
                "properties": {
                  "command": {
                    "type": "string"
                  }
                }
              },
              "example": {
                "command": "move card 42 to review"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The interpreted command and what it did.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/desktop/releases/latest": {
      "get": {
        "tags": [
          "Desktop"
        ],
        "operationId": "getLatestDesktopRelease",
        "summary": "Latest published desktop build",
        "description": "The manifest for the newest Harmony Quickbar build. The desktop app\npolls this at launch and every 24 hours to decide whether to offer an\nupdate.\n\n`version` is always bare three-segment semver — no `v` prefix, no\nsurrounding whitespace, never four segments. The desktop client refuses\nanything else instead of cleaning it up, and refusing shows as \"no\nupdate\" rather than as an error, so the shape is part of the contract\nrather than a formatting preference.\n\nThe manifest carries no download URL on purpose. Use\n`GET /v1/desktop/download` for the bytes, or send a person to\n`/user/apps`.\n",
        "parameters": [
          {
            "in": "query",
            "name": "platform",
            "required": false,
            "schema": {
              "type": "string",
              "default": "darwin-arm64"
            },
            "description": "Which build to describe. Only `darwin-arm64` is published today."
          }
        ],
        "responses": {
          "200": {
            "description": "The newest build for that platform.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "version",
                    "notes",
                    "sha256",
                    "sizeBytes",
                    "publishedAt"
                  ],
                  "properties": {
                    "version": {
                      "type": "string",
                      "pattern": "^[0-9]+\\.[0-9]+\\.[0-9]+$"
                    },
                    "notes": {
                      "type": "string"
                    },
                    "sha256": {
                      "type": "string",
                      "pattern": "^[0-9a-f]{64}$"
                    },
                    "sizeBytes": {
                      "type": "integer"
                    },
                    "publishedAt": {
                      "type": "string",
                      "format": "date-time"
                    }
                  }
                },
                "example": {
                  "version": "0.2.0",
                  "notes": "Faster panel.",
                  "sha256": "b847cd8b6fc9742740b5cc5773c89abd4b4b177147fedabeb120a51fd8788175",
                  "sizeBytes": 119086531,
                  "publishedAt": "2026-09-10T18:00:00.000Z"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "description": "Nothing is published for that platform yet."
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/desktop/download": {
      "get": {
        "tags": [
          "Desktop"
        ],
        "operationId": "downloadDesktopRelease",
        "summary": "Download the latest desktop build",
        "description": "Redirects to a short-lived signed URL for the build's bytes. The bytes\nnever pass through the API.\n\nTwo response shapes, because a browser and a command line cannot use\nthe same one. By default the answer is a `302`, which is what `curl -L`\nwants. A browser cannot put a credential header on a top-level\nnavigation, so it cannot follow a `302` on an authenticated route at\nall; `?redirect=false` returns the signed URL as JSON for it to\nnavigate to instead.\n",
        "parameters": [
          {
            "in": "query",
            "name": "platform",
            "required": false,
            "schema": {
              "type": "string",
              "default": "darwin-arm64"
            }
          },
          {
            "in": "query",
            "name": "redirect",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "false"
              ]
            },
            "description": "Set to `false` to receive the signed URL as JSON instead of a 302."
          }
        ],
        "responses": {
          "200": {
            "description": "The signed URL, when `redirect=false` was passed.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "url",
                    "expiresIn",
                    "version",
                    "sha256",
                    "sizeBytes"
                  ],
                  "properties": {
                    "url": {
                      "type": "string",
                      "format": "uri"
                    },
                    "expiresIn": {
                      "type": "integer",
                      "description": "Seconds the URL stays valid."
                    },
                    "version": {
                      "type": "string"
                    },
                    "sha256": {
                      "type": "string"
                    },
                    "sizeBytes": {
                      "type": "integer"
                    }
                  }
                }
              }
            }
          },
          "302": {
            "description": "Redirect to the signed URL for the build.",
            "headers": {
              "Location": {
                "schema": {
                  "type": "string",
                  "format": "uri"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "description": "Nothing is published for that platform yet."
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/desktop/updates/{platform}/{file}": {
      "get": {
        "tags": [
          "Desktop"
        ],
        "operationId": "getDesktopUpdateFile",
        "summary": "The auto-update feed",
        "description": "The base URL electron-updater's generic provider reads, as a redirect to\na short-lived signed URL. This is a subtree, not a single object: the\nupdater fetches `latest-mac.yml` at the base and then resolves each\nartifact reference in that file against the same base, so both the\nchannel file and `<version>/<artifact>` come through here.\n\nIt exists because the generic provider cannot read the JSON manifest at\n`/v1/desktop/releases/latest` — it wants YAML and `sha512`, while that\nroute answers JSON with `sha256`. What the two share is the storage\nbucket and the credential, not the manifest. The bucket is private with\nno read policy, so this route is the only way a desktop client can\nreach it.\n\nOnly paths that look like an update feed resolve: at most\n`<version>/<file>` deep, and only `.yml`, `.zip`, `.blockmap` and\n`.dmg`. Anything else is a `404`, which is also what the updater reads\nas \"nothing published for this platform\".\n",
        "parameters": [
          {
            "in": "path",
            "name": "platform",
            "required": true,
            "schema": {
              "type": "string",
              "default": "darwin-arm64"
            },
            "description": "Lowercase alphanumerics, `-` and `_`. Only `darwin-arm64` is published today."
          },
          {
            "in": "path",
            "name": "file",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "`latest-mac.yml`, or `<version>/<artifact>` for the bytes.\n"
          }
        ],
        "responses": {
          "302": {
            "description": "Redirect to the signed URL for the file.",
            "headers": {
              "Location": {
                "schema": {
                  "type": "string",
                  "format": "uri"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "description": "No such file for that platform."
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "ApiKeyAuth": {
        "type": "apiKey",
        "in": "header",
        "name": "X-API-Key",
        "description": "API key generated at https://gethmy.com/user/keys (starts with `hmy_`).\nIt acts for the user who created it, across every workspace that user\nbelongs to.\n"
      },
      "OAuth2": {
        "type": "oauth2",
        "description": "OAuth 2.1 with PKCE. Dynamic client registration (RFC 7591) and\nauthorization server metadata (RFC 8414) are published at\nhttps://app.gethmy.com/.well-known/oauth-authorization-server. The\nprotected-resource metadata for this API (RFC 9728) is at\nhttps://gethmy.com/.well-known/oauth-protected-resource/api.\n\nBeyond the `mcp` scope, each grant is bound at consent time to the set\nof workspaces the user selected. A request outside that set is refused\nwith `403`.\n",
        "flows": {
          "authorizationCode": {
            "authorizationUrl": "https://app.gethmy.com/oauth/authorize",
            "tokenUrl": "https://app.gethmy.com/oauth/token",
            "refreshUrl": "https://app.gethmy.com/oauth/token",
            "scopes": {
              "mcp": "Read and write the cards, columns, labels, subtasks, links, and\nagent sessions of the workspaces this grant covers.\n"
            }
          }
        }
      }
    },
    "parameters": {
      "Id": {
        "in": "path",
        "name": "id",
        "required": true,
        "schema": {
          "type": "string",
          "format": "uuid"
        }
      }
    },
    "responses": {
      "BadRequest": {
        "description": "The request was malformed or missing a required field.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "Unauthorized": {
        "description": "No credential, or the credential is invalid.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "example": {
              "error": "Missing API key or Authorization header"
            }
          }
        }
      },
      "Forbidden": {
        "description": "The credential is valid but does not cover this workspace. An OAuth\ntoken scoped to one set of workspaces cannot act on another.\n",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "example": {
              "error": "OAuth token is scoped to a different workspace"
            }
          }
        }
      },
      "NotFound": {
        "description": "No such resource, or the credential cannot see it.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "example": {
              "error": "Card not found"
            }
          }
        }
      },
      "RateLimited": {
        "description": "Too many requests for this user.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "example": {
              "error": "Rate limit exceeded. Please try again later."
            }
          }
        }
      }
    },
    "schemas": {
      "Error": {
        "type": "object",
        "required": [
          "error"
        ],
        "properties": {
          "error": {
            "type": "string",
            "description": "A human-readable description of what went wrong."
          }
        }
      },
      "Health": {
        "type": "object",
        "required": [
          "status",
          "service"
        ],
        "properties": {
          "status": {
            "type": "string",
            "const": "ok"
          },
          "service": {
            "type": "string",
            "const": "harmony-api"
          }
        }
      },
      "ApiIndex": {
        "type": "object",
        "description": "The public index of this API. Lists the spec, the accepted credentials,\nand the endpoints.\n",
        "required": [
          "status",
          "service",
          "name",
          "baseUrl"
        ],
        "properties": {
          "status": {
            "type": "string",
            "const": "ok"
          },
          "service": {
            "type": "string",
            "const": "harmony-api"
          },
          "name": {
            "type": "string"
          },
          "description": {
            "type": "string"
          },
          "version": {
            "type": "string"
          },
          "baseUrl": {
            "type": "string",
            "format": "uri"
          },
          "documentation": {
            "type": "object",
            "additionalProperties": {
              "type": "string",
              "format": "uri"
            }
          },
          "authentication": {
            "type": "object",
            "properties": {
              "required": {
                "type": "boolean"
              },
              "schemes": {
                "type": "array",
                "items": {
                  "type": "object",
                  "additionalProperties": true
                }
              }
            }
          },
          "publicEndpoints": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/EndpointSummary"
            }
          },
          "endpoints": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/EndpointSummary"
            }
          },
          "contact": {
            "type": "object",
            "properties": {
              "email": {
                "type": "string",
                "format": "email"
              },
              "url": {
                "type": "string",
                "format": "uri"
              }
            }
          }
        }
      },
      "EndpointSummary": {
        "type": "object",
        "required": [
          "method",
          "path"
        ],
        "properties": {
          "method": {
            "type": "string"
          },
          "path": {
            "type": "string"
          },
          "summary": {
            "type": "string"
          }
        }
      },
      "Workspace": {
        "type": "object",
        "required": [
          "id",
          "name"
        ],
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "name": {
            "type": "string"
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "role": {
            "type": "string",
            "description": "The calling user's role in this workspace."
          }
        }
      },
      "WorkspaceMember": {
        "type": "object",
        "properties": {
          "user_id": {
            "type": "string",
            "format": "uuid"
          },
          "role": {
            "type": "string"
          },
          "full_name": {
            "type": [
              "string",
              "null"
            ]
          },
          "avatar_url": {
            "type": [
              "string",
              "null"
            ]
          }
        }
      },
      "Project": {
        "type": "object",
        "required": [
          "id",
          "name"
        ],
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "workspace_id": {
            "type": "string",
            "format": "uuid"
          },
          "name": {
            "type": "string"
          },
          "slug": {
            "type": "string"
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "Column": {
        "type": "object",
        "required": [
          "id",
          "name"
        ],
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "project_id": {
            "type": "string",
            "format": "uuid"
          },
          "name": {
            "type": "string"
          },
          "position": {
            "type": "integer"
          }
        }
      },
      "Label": {
        "type": "object",
        "required": [
          "id",
          "name"
        ],
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "project_id": {
            "type": "string",
            "format": "uuid"
          },
          "name": {
            "type": "string"
          },
          "color": {
            "type": "string"
          }
        }
      },
      "Subtask": {
        "type": "object",
        "required": [
          "id",
          "title"
        ],
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "card_id": {
            "type": "string",
            "format": "uuid"
          },
          "title": {
            "type": "string"
          },
          "completed": {
            "type": "boolean"
          },
          "position": {
            "type": "integer"
          }
        }
      },
      "CardLink": {
        "type": "object",
        "required": [
          "id",
          "link_type"
        ],
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "source_card_id": {
            "type": "string",
            "format": "uuid"
          },
          "target_card_id": {
            "type": "string",
            "format": "uuid"
          },
          "link_type": {
            "type": "string",
            "enum": [
              "relates_to",
              "blocks",
              "duplicates",
              "is_part_of"
            ]
          }
        }
      },
      "Priority": {
        "type": "string",
        "enum": [
          "low",
          "medium",
          "high",
          "urgent"
        ]
      },
      "Card": {
        "type": "object",
        "description": "A card. The API returns the stored row plus its hydrated relations, so\nfields beyond the ones named here may appear.\n",
        "additionalProperties": true,
        "required": [
          "id",
          "title",
          "project_id",
          "column_id"
        ],
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "short_id": {
            "type": "integer",
            "description": "The per-project number a card is referenced by, e.g. 42."
          },
          "project_id": {
            "type": "string",
            "format": "uuid"
          },
          "column_id": {
            "type": "string",
            "format": "uuid"
          },
          "title": {
            "type": "string"
          },
          "description": {
            "type": [
              "string",
              "null"
            ]
          },
          "priority": {
            "$ref": "#/components/schemas/Priority"
          },
          "assignee_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid"
          },
          "due_date": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "estimate_hours": {
            "type": [
              "number",
              "null"
            ]
          },
          "position": {
            "type": "number"
          },
          "done": {
            "type": "boolean"
          },
          "archived_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "updated_at": {
            "type": "string",
            "format": "date-time"
          },
          "labelIds": {
            "type": "array",
            "items": {
              "type": "string",
              "format": "uuid"
            }
          },
          "subtasks": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Subtask"
            }
          },
          "column": {
            "$ref": "#/components/schemas/Column"
          }
        }
      },
      "CardCreate": {
        "type": "object",
        "required": [
          "projectId",
          "title"
        ],
        "properties": {
          "projectId": {
            "type": "string",
            "format": "uuid"
          },
          "columnId": {
            "type": "string",
            "format": "uuid",
            "description": "The column to create the card in. Must belong to `projectId` — a column from another project is refused with 400, and an unknown one with 404. Omit it to use the project's default column (its `is_default` column, else the leftmost)."
          },
          "title": {
            "type": "string"
          },
          "description": {
            "type": "string"
          },
          "priority": {
            "$ref": "#/components/schemas/Priority"
          }
        }
      },
      "CardUpdate": {
        "type": "object",
        "description": "Only the fields present are changed.",
        "properties": {
          "title": {
            "type": "string"
          },
          "description": {
            "type": "string"
          },
          "priority": {
            "$ref": "#/components/schemas/Priority"
          },
          "done": {
            "type": "boolean"
          },
          "assigneeId": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid"
          },
          "dueDate": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          }
        }
      },
      "Board": {
        "type": "object",
        "description": "The full board state for one project.",
        "properties": {
          "project": {
            "$ref": "#/components/schemas/Project"
          },
          "columns": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Column"
            }
          },
          "cards": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Card"
            }
          },
          "labels": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Label"
            }
          }
        }
      }
    }
  }
}
