{
  "openapi": "3.1.1",
  "info": {
    "title": "Bonsai API",
    "version": "1",
    "description": "The Bonsai API lets you read and write the records behind your business:\nclients and contacts, deals, projects, tasks, time entries and invoices.\nIt is organized around REST, takes and returns JSON, and uses standard HTTP\nverbs, status codes and authentication.\n\n## Authentication\n\nEvery request is authenticated with a bearer token, sent in the\n`Authorization` header:\n\n```\nAuthorization: Bearer bonsai_at_...\n```\n\nThere are two ways to get one, and they grant exactly the same access:\n\n- **API key** — create one in your Bonsai settings and use it directly. Best\n  for scripts and back-office integrations acting on your own account.\n- **OAuth 2.1** — run the authorization code flow with PKCE to act on another\n  Bonsai user's behalf. Best for applications you distribute.\n\nA token is scoped to one Bonsai account, and it can only ever do what the\nmember behind it can do in the Bonsai app. A member who cannot see a project\nin the app cannot see it through the API either, so plan for `403` and `404`\nresponses on records outside their reach.\n\nTokens are secrets. Never ship one in client-side code or commit it to source\ncontrol. Requests must use HTTPS; unauthenticated calls fail with `401`.\n\n## Requests and responses\n\nEvery successful response is an object with two top-level keys:\n\n```json\n{\n  \"data\": { \"...\": \"the record, or an array of records\" },\n  \"meta\": { \"request_id\": \"b1f0d2c4-7a9e-4c3b-8d5f-1e2a3b4c5d6e\" }\n}\n```\n\nWrite endpoints take their attributes wrapped in a key named after the\nresource — `{ \"task\": { \"title\": \"...\" } }` — and reject a body that is\nmissing the wrapper with `400`. `PATCH` is a partial update: only the fields\npresent in the body change. Sending an explicit `null` clears some fields but\nis ignored on others, so check the field you are writing — every field that\ncan be cleared says so.\n\nMoney is serialized as a decimal string rather than a float so no precision is\nlost in transit; dates are `YYYY-MM-DD` and timestamps are ISO 8601 in UTC.\n\n## Pagination\n\nList endpoints are paginated with `page[number]` (1-indexed) and `page[size]`\n(25 by default, 100 at most; larger values are silently reduced to 100).\nEach list response carries the cursor in `meta.pagination`:\n\n```json\n{ \"page\": 1, \"page_size\": 25, \"has_more\": true }\n```\n\nKeep requesting the next page while `has_more` is `true`. There is no total\ncount — counting every match is expensive on large accounts, and `has_more`\nis enough to walk a collection to the end.\n\n## Filtering\n\nList endpoints narrow results with nested `filter` parameters, such as\n`?filter[status]=active`. Filters that accept several values take either a\ncomma-separated string or repeated array parameters:\n\n```\n?filter[priority]=high,medium\n?filter[priority][]=high&filter[priority][]=medium\n```\n\nEach endpoint documents the filters it supports. An unknown filter key is an\nerror rather than a no-op, so a typo surfaces as `400` instead of quietly\nreturning the wrong records.\n\n## Errors\n\nBonsai uses conventional HTTP status codes, and every failure returns the\nsame envelope:\n\n```json\n{\n  \"error\": {\n    \"type\": \"validation_error\",\n    \"status\": 422,\n    \"message\": \"Validation failed\",\n    \"request_id\": \"b1f0d2c4-7a9e-4c3b-8d5f-1e2a3b4c5d6e\",\n    \"details\": [\n      {\n        \"source\": { \"pointer\": \"/data/attributes/title\" },\n        \"code\": \"blank\",\n        \"message\": \"Title can't be blank\"\n      }\n    ]\n  }\n}\n```\n\nBranch on `type` and, for field-level problems, on each entry's `code` — both\nare stable, while `message` is not. `details` lists every problem at once, so\na rejected write can be fixed in a single pass. `422` means the body was\nunderstood but rejected; `400` means the request itself could not be read.\n\nEvery response includes a `request_id`, in the body and in the\n`X-Request-Id` header. Log it: it is the fastest way for support to find a\nspecific call.\n\n## Rate limits\n\nLimits are counted per credential, not per IP, so one integration cannot\nspend another's budget:\n\n| Scope | Limit |\n| --- | --- |\n| All requests | 600 per minute, 10,000 per hour |\n| Writes (`POST`, `PATCH`, `PUT`, `DELETE`) | 120 per minute |\n\nExceeding a limit returns `429` with `Retry-After` and `RateLimit-*` headers\ndescribing when to try again. Back off for at least `Retry-After` seconds —\nideally with jitter — rather than retrying immediately.\n\n## Versioning\n\nThis is version 1 of the API, and every path is prefixed with\n`/public-api/v1`. Additive changes — a new endpoint, a new optional\nparameter, a new field in a response — can ship at any time, so treat\nunrecognized response fields as safe to ignore. Anything that would break an\nexisting integration ships as a new version instead. Fields marked deprecated\nkeep working until the next major version.\n",
    "termsOfService": "https://www.hellobonsai.com/legal/terms",
    "contact": {
      "name": "Bonsai Support",
      "url": "https://help.hellobonsai.com"
    }
  },
  "servers": [
    {
      "url": "https://app.hellobonsai.com",
      "description": "Bonsai API"
    }
  ],
  "tags": [
    {
      "name": "Companies",
      "description": "Client companies in your CRM. The API calls them companies; they are what deals, projects and invoices are billed to."
    },
    {
      "name": "Contacts",
      "description": "The people you work with at your client companies. A contact can be linked to more than one company, or to none at all."
    },
    {
      "name": "Deals",
      "description": "Opportunities moving through your sales pipeline. Each deal sits in one stage of the deal board."
    },
    {
      "name": "Projects",
      "description": "Engagements you deliver for a client, and the container tasks, time entries and invoices hang off."
    },
    {
      "name": "Tasks",
      "description": "Units of work, optionally inside a project, optionally nested under a parent task."
    },
    {
      "name": "Time Entries",
      "description": "Time logged against a project or task, and the basis for billing hourly work."
    },
    {
      "name": "Invoices",
      "description": "Bills you send to a client, and the line items on them."
    },
    {
      "name": "Comments",
      "description": "Discussion threads on a task or a deal, plus the activity entries Bonsai records automatically."
    },
    {
      "name": "Notes",
      "description": "What you write down about a client, a project or a contact. A note can be attached to several records at once, or to none."
    },
    {
      "name": "Board Groups",
      "description": "The stages of your deal and project boards. Read these to turn a stage name into the id write endpoints expect."
    },
    {
      "name": "Task Statuses",
      "description": "The columns of your task board. Read these to turn a status name into the id write endpoints expect."
    },
    {
      "name": "Company Tags",
      "description": "Labels your account applies to clients, deals, projects, tasks, time entries and expenses."
    },
    {
      "name": "Team Members",
      "description": "The people in your Bonsai account. Read these to turn a name into the id assignment fields expect."
    }
  ],
  "components": {
    "securitySchemes": {
      "oauth2": {
        "type": "oauth2",
        "description": "OAuth 2.1 authorization code flow with PKCE (S256), for applications acting on a Bonsai user's behalf. Access tokens are opaque strings prefixed `bonsai_at_` and are bound to the resource they were issued for (RFC 8707). A token carries no scopes: it can do whatever the member who granted it can do in the Bonsai app, and no more.",
        "flows": {
          "authorizationCode": {
            "authorizationUrl": "https://app.hellobonsai.com/oauth/authorize",
            "tokenUrl": "https://app.hellobonsai.com/oauth/token",
            "refreshUrl": "https://app.hellobonsai.com/oauth/token",
            "scopes": {}
          }
        }
      },
      "api_key": {
        "type": "http",
        "scheme": "bearer",
        "description": "An API key created in your Bonsai settings, for scripts and integrations acting on your own account. Send the raw `bonsai_at_` value as `Authorization: Bearer <token>`. An API key reaches exactly the same records under exactly the same permissions as an OAuth token — it is a second way to obtain a bearer token, not a wider level of access."
      }
    },
    "schemas": {
      "Meta": {
        "type": "object",
        "description": "Metadata returned alongside every single-resource response.",
        "properties": {
          "request_id": {
            "type": "string",
            "description": "Identifier for this request. Quote it when contacting support about a specific call.",
            "examples": [
              "b1f0d2c4-7a9e-4c3b-8d5f-1e2a3b4c5d6e"
            ]
          }
        },
        "required": [
          "request_id"
        ]
      },
      "PaginatedMeta": {
        "type": "object",
        "description": "Metadata returned alongside every list response.",
        "properties": {
          "request_id": {
            "type": "string",
            "description": "Identifier for this request. Quote it when contacting support about a specific call.",
            "examples": [
              "b1f0d2c4-7a9e-4c3b-8d5f-1e2a3b4c5d6e"
            ]
          },
          "pagination": {
            "$ref": "#/components/schemas/Pagination"
          }
        },
        "required": [
          "request_id",
          "pagination"
        ]
      },
      "Pagination": {
        "type": "object",
        "description": "Where the returned page sits in the full result set.",
        "properties": {
          "page": {
            "type": "integer",
            "description": "The page that was returned.",
            "examples": [
              1
            ]
          },
          "page_size": {
            "type": "integer",
            "description": "How many records this page can hold.",
            "examples": [
              25
            ]
          },
          "has_more": {
            "type": "boolean",
            "description": "Whether at least one more page follows this one. Keep incrementing `page[number]` while this is `true`.",
            "examples": [
              true
            ]
          }
        },
        "required": [
          "page",
          "page_size",
          "has_more"
        ]
      },
      "ErrorEnvelope": {
        "type": "object",
        "description": "Every 4xx and 5xx response has this shape.",
        "properties": {
          "error": {
            "$ref": "#/components/schemas/Error"
          }
        },
        "required": [
          "error"
        ]
      },
      "Error": {
        "type": "object",
        "description": "What went wrong, and which part of the request caused it.",
        "properties": {
          "type": {
            "type": "string",
            "description": "What kind of failure this is. Branch on this rather than on `message`:\n\n- `bad_request` (400) — a query parameter is unknown, malformed or conflicts with another.\n- `authentication_error` (401) — the access token is missing, expired or invalid.\n- `insufficient_permissions` (403) — the token is valid but the member behind it is not allowed to do this.\n- `not_found` (404) — no such record in your account.\n- `validation_error` (422) — the request body was understood but rejected. See `details`.\n- `rate_limited` (429) — you have exhausted a rate limit window.\n- `internal_error` (500) — something failed on our side. Safe to retry.",
            "enum": [
              "bad_request",
              "authentication_error",
              "insufficient_permissions",
              "not_found",
              "validation_error",
              "rate_limited",
              "internal_error"
            ],
            "examples": [
              "validation_error"
            ]
          },
          "status": {
            "type": "integer",
            "description": "The HTTP status code, repeated here so the body is self-contained.",
            "examples": [
              422
            ]
          },
          "message": {
            "type": "string",
            "description": "Human-readable summary of the failure. Intended for logs and developers, not for end users.",
            "examples": [
              "Validation failed"
            ]
          },
          "request_id": {
            "type": "string",
            "description": "Identifier for the failed request. Quote it when contacting support.",
            "examples": [
              "b1f0d2c4-7a9e-4c3b-8d5f-1e2a3b4c5d6e"
            ]
          },
          "details": {
            "type": "array",
            "description": "One entry per individual problem, so a rejected request can be fixed in one pass instead of one field at a time. Empty on failures that are not field-specific.",
            "items": {
              "$ref": "#/components/schemas/ErrorDetail"
            }
          }
        },
        "required": [
          "type",
          "status",
          "message",
          "request_id",
          "details"
        ]
      },
      "ErrorDetail": {
        "type": "object",
        "description": "A single problem with the request, pointing at the parameter or body field responsible.",
        "properties": {
          "source": {
            "description": "What the problem is about. Body problems carry a JSON `pointer`; query-string problems carry a `parameter` name. Exactly one of the two is present.",
            "oneOf": [
              {
                "type": "object",
                "properties": {
                  "pointer": {
                    "type": "string",
                    "description": "JSON pointer to the offending field in the request body.",
                    "examples": [
                      "/data/attributes/title"
                    ]
                  }
                },
                "required": [
                  "pointer"
                ]
              },
              {
                "type": "object",
                "properties": {
                  "parameter": {
                    "type": "string",
                    "description": "Name of the offending query parameter.",
                    "examples": [
                      "filter[status]"
                    ]
                  }
                },
                "required": [
                  "parameter"
                ]
              }
            ]
          },
          "code": {
            "type": "string",
            "description": "Machine-readable reason this field was rejected:\n\n- `blank` — a required value was empty or missing.\n- `invalid_value` — the value was the wrong shape, or outside the allowed set.\n- `not_accessible` — the value names a record that does not exist in your account, or that this token may not use.\n- `parameter_missing` — a required top-level wrapper was absent from the body.\n- `unknown_filter` — no such `filter[...]` key on this endpoint.\n- `conflicting_filters` — two filters were supplied that cannot be combined.\n- `unknown_sort_field` — no such sortable field on this endpoint.\n- `internal_error` — the failure could not be attributed to the request.",
            "enum": [
              "blank",
              "invalid_value",
              "not_accessible",
              "parameter_missing",
              "unknown_filter",
              "conflicting_filters",
              "unknown_sort_field",
              "internal_error"
            ],
            "examples": [
              "blank"
            ]
          },
          "message": {
            "type": "string",
            "description": "Human-readable description of this specific problem.",
            "examples": [
              "Title can't be blank"
            ]
          }
        },
        "required": [
          "source",
          "code",
          "message"
        ]
      },
      "BoardGroup": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid",
            "description": "Unique identifier for the stage. Pass this value as `board_group_id` when creating or updating a deal.",
            "examples": [
              "8feddf83-f0dc-4587-ac77-3236d509a5c4"
            ]
          },
          "name": {
            "type": "string",
            "description": "Stage name as it appears on the board.",
            "examples": [
              "Won"
            ]
          },
          "color": {
            "type": "string",
            "description": "Hex color the stage is drawn in.",
            "examples": [
              "#ECECEC"
            ]
          },
          "position": {
            "type": "integer",
            "description": "Position of the stage on its board, ordered left to right.",
            "examples": [
              3
            ]
          },
          "state": {
            "type": "string",
            "enum": [
              "active",
              "complete"
            ],
            "description": "Whether the stage holds work in progress (`active`) or is a closed-out stage (`complete`).",
            "examples": [
              "complete"
            ]
          },
          "deal_probability": {
            "type": [
              "string",
              "null"
            ],
            "description": "Default win probability applied to deals that reach this stage, as a decimal string percentage.",
            "examples": [
              "100.0"
            ]
          },
          "resource_type": {
            "type": "string",
            "description": "Which pipeline the stage belongs to — the deal board or the project board.",
            "enum": [
              "Deal",
              "Project"
            ],
            "examples": [
              "Deal"
            ]
          }
        },
        "required": [
          "id",
          "name",
          "color",
          "position",
          "state",
          "deal_probability",
          "resource_type"
        ],
        "description": "A stage of your deal or project board. Deals and project cards move between stages as work progresses."
      },
      "Comment": {
        "type": "object",
        "properties": {
          "id": {
            "type": "integer",
            "description": "Unique identifier for the comment.",
            "examples": [
              5821
            ]
          },
          "kind": {
            "type": "string",
            "enum": [
              "user_created",
              "event"
            ],
            "description": "`user_created` for a comment someone wrote, `event` for an entry Bonsai recorded automatically, such as a status change or a reassignment.",
            "examples": [
              "user_created"
            ]
          },
          "commentable_type": {
            "type": [
              "string",
              "null"
            ],
            "description": "Type of record the comment is attached to — `Task` or `Deal`.",
            "examples": [
              "Task"
            ]
          },
          "created_at": {
            "type": "string",
            "format": "date-time",
            "description": "When the comment was posted.",
            "examples": [
              "2026-04-14T09:12:04.000Z"
            ]
          },
          "body_plain_text": {
            "type": [
              "string",
              "null"
            ],
            "description": "The comment body with all HTML removed.",
            "examples": [
              "Looks good to me!"
            ]
          },
          "commentable_id": {
            "type": [
              "string",
              "null"
            ],
            "description": "Identifier of the record the comment is attached to, always serialized as a string: the task's `uuid` for a task comment, the numeric id for a deal comment.",
            "examples": [
              "ad3fdad8-5072-46df-8800-bb57299179df"
            ]
          },
          "author_member_id": {
            "type": [
              "integer",
              "null"
            ],
            "description": "Team member id of the author, or `null` when the author is no longer a member of your account.",
            "examples": [
              314
            ]
          }
        },
        "required": [
          "id",
          "kind",
          "commentable_type",
          "created_at",
          "body_plain_text",
          "commentable_id",
          "author_member_id"
        ],
        "description": "An entry on a task or deal thread: either something a person wrote, or activity Bonsai recorded on its own."
      },
      "CompanyTag": {
        "type": "object",
        "properties": {
          "id": {
            "type": "integer",
            "description": "Unique identifier for the tag. Pass this value in `tag_ids` when tagging a task.",
            "examples": [
              7
            ]
          },
          "name": {
            "type": "string",
            "description": "Tag name as it appears in Bonsai.",
            "examples": [
              "VIP"
            ]
          }
        },
        "required": [
          "id",
          "name"
        ],
        "description": "A tag as it appears when applied to a record — just enough to label it."
      },
      "CompanyTagDetailedTrait": {
        "type": "object",
        "properties": {
          "tag_type": {
            "type": "string",
            "description": "The kind of record this tag applies to. A tag belongs to exactly one type and cannot be moved between types.",
            "enum": [
              "client",
              "deal",
              "project",
              "vendor",
              "task",
              "time_entry",
              "expense",
              "supplier"
            ],
            "examples": [
              "client"
            ]
          },
          "color": {
            "type": "string",
            "description": "Hex color the tag is drawn in.",
            "examples": [
              "#520EB0"
            ]
          }
        },
        "required": [
          "tag_type",
          "color"
        ],
        "description": "The extra fields the tags endpoint returns on top of the basic tag."
      },
      "Company": {
        "type": "object",
        "properties": {
          "id": {
            "type": "integer",
            "description": "Unique identifier for the company.",
            "examples": [
              42
            ]
          },
          "name": {
            "type": [
              "string",
              "null"
            ],
            "description": "Company name.",
            "examples": [
              "Acme Corp"
            ]
          },
          "default_contact_id": {
            "type": [
              "integer",
              "null"
            ],
            "description": "Id of the contact treated as this company's primary point of contact, or `null` when none is set. Deals and invoices created without an explicit contact fall back to it.",
            "examples": [
              7
            ]
          },
          "description": {
            "type": [
              "string",
              "null"
            ],
            "description": "Free text notes about the company, or `null` when unset.",
            "examples": [
              "Long standing retainer client."
            ]
          },
          "industry": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "arts_and_entertainment",
              "business_and_consumer_services",
              "community_and_society",
              "computers_electronics_and_technology",
              "e_commerce_and_shopping",
              "finance",
              "food_and_drink",
              "gambling",
              "games",
              "health",
              "heavy_industry_and_engineering",
              "hobbies_and_leisure",
              "home_and_garden",
              "jobs_and_career",
              "law_and_government",
              "lifestyle",
              "luxury",
              "news_and_media",
              "pets_and_animals",
              "reference_materials",
              "science_and_education",
              "sports",
              "travel_and_tourism",
              "vehicles",
              null
            ],
            "description": "The industry the company operates in, or `null` when unset. One of a fixed list.",
            "examples": [
              "computers_electronics_and_technology"
            ]
          },
          "company_size": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "size_1_10",
              "size_11_50",
              "size_51_200",
              "size_201_1000",
              "size_1000_plus",
              null
            ],
            "description": "Headcount band for the company, or `null` when unset. One of a fixed list.",
            "examples": [
              "size_201_1000"
            ]
          },
          "source": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "organic_search",
              "paid_search",
              "email_marketing",
              "organic_social",
              "referrals",
              "other_campaigns",
              "direct_traffic",
              "offline_sources",
              "paid_social",
              "ai_referrals",
              null
            ],
            "description": "How the client was acquired, or `null` when unset. One of a fixed list.",
            "examples": [
              "organic_search"
            ]
          },
          "linkedin": {
            "type": [
              "string",
              "null"
            ],
            "description": "Link to the company's LinkedIn profile, or `null` when unset.",
            "examples": [
              "https://linkedin.com/company/acme"
            ]
          },
          "instagram": {
            "type": [
              "string",
              "null"
            ],
            "description": "Link to the company's Instagram profile, or `null` when unset.",
            "examples": [
              "https://instagram.com/acme"
            ]
          },
          "twitter": {
            "type": [
              "string",
              "null"
            ],
            "description": "Link to the company's X profile, or `null` when unset.",
            "examples": [
              "https://x.com/acme"
            ]
          },
          "facebook": {
            "type": [
              "string",
              "null"
            ],
            "description": "Link to the company's Facebook page, or `null` when unset.",
            "examples": [
              "https://facebook.com/acme"
            ]
          },
          "archived_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "When the company was archived, or `null` while it is active. Archived companies are left out of the list endpoint.",
            "examples": [
              "2026-04-14T09:12:04.000Z"
            ]
          },
          "created_at": {
            "type": "string",
            "format": "date-time",
            "description": "When the company was added to your account.",
            "examples": [
              "2026-04-14T09:12:04.000Z"
            ]
          },
          "domains": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Domains associated with the company, lowercased and stripped of any protocol or `www.` prefix. A domain can belong to only one company in your account.",
            "examples": [
              [
                "example.com",
                "example.net"
              ]
            ]
          },
          "avatar_url": {
            "type": [
              "string",
              "null"
            ],
            "description": "Link to the company's logo, or `null` when it has none. Read-only — the create and update endpoints don't accept it. Bonsai shows generated initials in the app when there is no logo; those are never returned here.",
            "examples": [
              "https://logo.clearbit.com/acme.com"
            ]
          },
          "url": {
            "type": "string",
            "description": "Absolute link to the company in the Bonsai app. Share it to send someone straight to the record.",
            "examples": [
              "https://app.hellobonsai.com/companies/42"
            ]
          }
        },
        "required": [
          "id",
          "name",
          "default_contact_id",
          "description",
          "industry",
          "company_size",
          "source",
          "linkedin",
          "instagram",
          "twitter",
          "facebook",
          "archived_at",
          "created_at",
          "domains",
          "avatar_url",
          "url"
        ],
        "description": "A client company in your CRM. Deals, projects and invoices all belong to one."
      },
      "Contact": {
        "type": "object",
        "properties": {
          "id": {
            "type": "integer",
            "description": "Unique identifier for the contact.",
            "examples": [
              7
            ]
          },
          "name": {
            "type": [
              "string",
              "null"
            ],
            "description": "The contact's full name.",
            "examples": [
              "Alex Johnson"
            ]
          },
          "email": {
            "type": [
              "string",
              "null"
            ],
            "description": "The contact's email address.",
            "examples": [
              "alex@example.com"
            ]
          },
          "job_title": {
            "type": [
              "string",
              "null"
            ],
            "description": "The contact's job title, or `null` when unset.",
            "examples": [
              "CTO"
            ]
          },
          "phone_number": {
            "type": [
              "string",
              "null"
            ],
            "description": "The contact's phone number, or `null` when unset.",
            "examples": [
              "+1-415-555-0100"
            ]
          },
          "archived_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "When the contact was archived, or `null` while they are active. Archived contacts are left out of the list endpoint.",
            "examples": [
              "2026-04-14T09:12:04.000Z"
            ]
          },
          "created_at": {
            "type": "string",
            "format": "date-time",
            "description": "When the contact was added to your account.",
            "examples": [
              "2026-04-14T09:12:04.000Z"
            ]
          },
          "company_ids": {
            "type": "array",
            "items": {
              "type": "integer"
            },
            "description": "Ids of every company this contact is linked to. A contact can belong to more than one.",
            "examples": [
              [
                42
              ]
            ]
          },
          "default_company_id": {
            "type": [
              "integer",
              "null"
            ],
            "description": "Id of the company this contact is the primary contact for, or `null` when they are not the primary contact anywhere.",
            "examples": [
              42
            ]
          },
          "url": {
            "type": "string",
            "description": "Absolute link to the contact in the Bonsai app. Share it to send someone straight to the record.",
            "examples": [
              "https://app.hellobonsai.com/contacts/7"
            ]
          }
        },
        "required": [
          "id",
          "name",
          "email",
          "job_title",
          "phone_number",
          "archived_at",
          "created_at",
          "company_ids",
          "default_company_id",
          "url"
        ],
        "description": "A person you work with at a client company, or a standalone contact who belongs to none."
      },
      "Deal": {
        "type": "object",
        "properties": {
          "id": {
            "type": "integer",
            "description": "Unique identifier for the deal.",
            "examples": [
              918
            ]
          },
          "title": {
            "type": "string",
            "description": "Deal title.",
            "examples": [
              "Acme Website Redesign"
            ]
          },
          "deal_number": {
            "type": "string",
            "description": "Human-readable deal reference, unique within your account and assigned on creation.",
            "examples": [
              "DL-42"
            ]
          },
          "deal_value": {
            "type": [
              "string",
              "null"
            ],
            "description": "What the deal is worth, as a decimal string in `currency`. `null` when no value has been set.",
            "examples": [
              "5000.00"
            ]
          },
          "currency": {
            "type": "string",
            "description": "Three-letter ISO 4217 currency code for `deal_value`.",
            "examples": [
              "USD"
            ]
          },
          "probability": {
            "type": [
              "integer",
              "null"
            ],
            "description": "Chance of winning the deal, as a whole percentage from 0 to 100. `null` when unset.",
            "examples": [
              60
            ]
          },
          "close_date": {
            "type": [
              "string",
              "null"
            ],
            "format": "date",
            "description": "Date the deal is expected to close, or `null` when unset.",
            "examples": [
              "2026-09-30"
            ]
          },
          "archived_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "When the deal was archived, or `null` while it is active.",
            "examples": [
              "2026-04-14T09:12:04.000Z"
            ]
          },
          "assignee_member_id": {
            "type": [
              "integer",
              "null"
            ],
            "description": "Team member id of the deal owner, or `null` when the deal is unassigned.",
            "examples": [
              314
            ]
          },
          "board_group_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid",
            "description": "Id of the pipeline stage the deal sits in, or `null` when it is not on a board.",
            "examples": [
              "8feddf83-f0dc-4587-ac77-3236d509a5c4"
            ]
          },
          "status": {
            "type": "string",
            "description": "Name of the pipeline stage the deal sits in — the label form of `board_group_id`. Stages are configurable per account, so this is free text rather than a fixed set of values, and it is an empty string when the deal is not on a board.",
            "examples": [
              "Won"
            ]
          },
          "company_name": {
            "type": [
              "string",
              "null"
            ],
            "description": "Name of the client the deal is with, or `null`.",
            "examples": [
              "Acme Corp"
            ]
          },
          "assignee_member_name": {
            "type": [
              "string",
              "null"
            ],
            "description": "Name of the deal owner, or `null` when the deal is unassigned.",
            "examples": [
              "Maria Santos"
            ]
          },
          "contact_ids": {
            "type": "array",
            "items": {
              "type": "integer"
            },
            "description": "Ids of the contacts attached to the deal.",
            "examples": [
              [
                7,
                12
              ]
            ]
          },
          "url": {
            "type": "string",
            "description": "Absolute link to the deal in the Bonsai app. Opens the pipeline with the deal expanded.",
            "examples": [
              "https://app.hellobonsai.com/deals?deal_id=918"
            ]
          }
        },
        "required": [
          "id",
          "title",
          "deal_number",
          "deal_value",
          "currency",
          "probability",
          "close_date",
          "archived_at",
          "assignee_member_id",
          "board_group_id",
          "status",
          "company_name",
          "assignee_member_name",
          "contact_ids",
          "url"
        ],
        "description": "An opportunity in your sales pipeline, sitting in one stage of the deal board."
      },
      "InvoiceItem": {
        "type": "object",
        "properties": {
          "id": {
            "type": "integer",
            "description": "Unique identifier for the line item.",
            "examples": [
              4471
            ]
          },
          "name": {
            "type": [
              "string",
              "null"
            ],
            "description": "Line item name, as it appears on the invoice.",
            "examples": [
              "Design"
            ]
          },
          "description": {
            "type": [
              "string",
              "null"
            ],
            "description": "Longer description shown beneath the name, or `null`.",
            "examples": [
              "Homepage and pricing page"
            ]
          },
          "amount": {
            "type": [
              "string",
              "null"
            ],
            "description": "Quantity billed, as a decimal string.",
            "examples": [
              "2.0"
            ]
          },
          "rate": {
            "type": [
              "string",
              "null"
            ],
            "description": "Price per unit, as a decimal string in the invoice currency.",
            "examples": [
              "100.0"
            ]
          },
          "unit_type": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "flat",
              "per_hour",
              "per_day",
              "per_item",
              "per_word",
              "per_week",
              "per_month",
              "per_year",
              "per_quarter",
              null
            ],
            "description": "What the rate is charged per. Defaults to `flat`.",
            "examples": [
              "per_hour"
            ]
          },
          "total": {
            "type": "string",
            "description": "`amount` multiplied by `rate`, rounded, as a decimal string.",
            "examples": [
              "200.0"
            ]
          }
        },
        "required": [
          "id",
          "name",
          "description",
          "amount",
          "rate",
          "unit_type",
          "total"
        ],
        "description": "A single line on an invoice: what you are billing for, how much of it, and at what rate."
      },
      "Invoice": {
        "type": "object",
        "properties": {
          "id": {
            "type": "integer",
            "description": "Unique identifier for the invoice.",
            "examples": [
              3310
            ]
          },
          "invoice_number": {
            "type": [
              "string",
              "null"
            ],
            "description": "Human-readable invoice reference, unique within your account. The list endpoint accepts it as `filter[invoice_number]`.",
            "examples": [
              "INV-001"
            ]
          },
          "title": {
            "type": [
              "string",
              "null"
            ],
            "description": "Invoice title shown to the client.",
            "examples": [
              "Acme Corp Invoice"
            ]
          },
          "currency": {
            "type": "string",
            "description": "Three-letter ISO 4217 currency code every amount on the invoice is expressed in.",
            "examples": [
              "USD"
            ]
          },
          "issued_date": {
            "type": [
              "string",
              "null"
            ],
            "format": "date",
            "description": "Date the invoice was sent to the client, or `null` while it is still a draft.",
            "examples": [
              "2026-04-14"
            ]
          },
          "due_date": {
            "type": [
              "string",
              "null"
            ],
            "format": "date",
            "description": "Date payment is due, or `null` when the invoice is payable on receipt.",
            "examples": [
              "2026-04-28"
            ]
          },
          "project_id": {
            "type": [
              "integer",
              "null"
            ],
            "description": "Id of the project the invoice bills.",
            "examples": [
              51
            ]
          },
          "client_email": {
            "type": [
              "string",
              "null"
            ],
            "description": "Email address the invoice is addressed to, captured from the client contact when the invoice was created.",
            "examples": [
              "alex@example.com"
            ]
          },
          "public_url_token": {
            "type": [
              "string",
              "null"
            ],
            "description": "Token in the invoice's client-facing URL. Anyone holding that link can view and pay the invoice, so treat this value as a secret.",
            "examples": [
              "x7f2a9c4b1e8d"
            ]
          },
          "created_at": {
            "type": "string",
            "format": "date-time",
            "description": "When the invoice was created.",
            "examples": [
              "2026-04-14T09:12:04.000Z"
            ]
          },
          "total_amount": {
            "type": "string",
            "description": "What the client owes, as a decimal string, after any discount and tax.",
            "examples": [
              "200.0"
            ]
          },
          "discount_amount": {
            "type": [
              "string",
              "null"
            ],
            "description": "Discount taken off the subtotal, as a decimal string, or `null` when there is none.",
            "examples": [
              "25.0"
            ]
          },
          "status": {
            "type": "string",
            "description": "Where the invoice is in its lifecycle:\n\n- `new`, `drafted` — not sent to the client yet.\n- `scheduled` — queued to send automatically on a future date.\n- `outstanding` — sent and awaiting payment.\n- `overdue` — sent, unpaid and past `due_date`.\n- `pending` — a payment has started but has not settled.\n- `paid` — paid in full.\n- `deleted` — the invoice has been deleted.\n\nAn invoice that is `paid` or `pending` can no longer be edited, and neither can its line items.",
            "enum": [
              "new",
              "drafted",
              "scheduled",
              "outstanding",
              "overdue",
              "pending",
              "paid",
              "deleted"
            ],
            "examples": [
              "outstanding"
            ]
          },
          "due_date_option": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "upon_receipt",
              "after_x_days",
              "custom",
              null
            ],
            "description": "How `due_date` was arrived at: `upon_receipt`, `after_x_days` (a fixed number of days after the issue date) or `custom` (an explicit date).",
            "examples": [
              "custom"
            ]
          },
          "company_id": {
            "type": [
              "integer",
              "null"
            ],
            "description": "Id of the company being invoiced, or `null` when the invoice is addressed to a client that is not a company record.",
            "examples": [
              42
            ]
          },
          "client_name": {
            "type": [
              "string",
              "null"
            ],
            "description": "Name the invoice is addressed to.",
            "examples": [
              "Acme Corp"
            ]
          },
          "subtotal": {
            "type": [
              "string",
              "null"
            ],
            "description": "Sum of every line item, before discount and tax, as a decimal string.",
            "examples": [
              "200.0"
            ]
          },
          "tax_amount": {
            "type": [
              "string",
              "null"
            ],
            "description": "Tax added to the invoice, as a decimal string.",
            "examples": [
              "0.0"
            ]
          },
          "kind": {
            "type": "string",
            "description": "How the invoice repeats:\n\n- `single` — a one-off invoice. Scheduling one to send later does not make it\n  recurring; `status` reports that as `scheduled`.\n- `recurring` — reissued on `repeat_interval` until you stop it or `repeat_finish_date` arrives.\n- `subscription` — recurring, and charged automatically to the card the client has on file.\n- `bundled` — one of several invoices grouped to be sent and paid together.\n\n`bundled` is read-only: bundles are assembled in the Bonsai app from\ninvoices that already exist, so it cannot be passed when creating one.",
            "enum": [
              "single",
              "recurring",
              "subscription",
              "bundled"
            ],
            "examples": [
              "recurring"
            ]
          },
          "repeat_interval": {
            "type": [
              "string",
              "null"
            ],
            "description": "How often a `recurring` or `subscription` invoice is reissued, or `null` for a one-off. It also survives on the last invoice of a series that has finished.",
            "enum": [
              "weekly",
              "every two weeks",
              "every four weeks",
              "monthly",
              "every two months",
              "quarterly",
              "every six months",
              "annually",
              null
            ],
            "examples": [
              "monthly"
            ]
          },
          "repeat_finish_date": {
            "type": [
              "string",
              "null"
            ],
            "format": "date",
            "description": "Date the series stops, or `null` when it runs until you stop it. The last invoice of the series is issued on this date.",
            "examples": [
              "2027-04-14"
            ]
          },
          "series_id": {
            "type": [
              "integer",
              "null"
            ],
            "description": "Id of the first invoice of the series this one belongs to, the same for every invoice in it, so you can group them. `null` for an invoice that is not part of a series.",
            "examples": [
              3310
            ]
          },
          "url": {
            "type": "string",
            "description": "Absolute link to the invoice in the Bonsai app. This is the contractor-facing view, not the client payment page — for that, use `public_url_token`.",
            "examples": [
              "https://app.hellobonsai.com/invoices/3391"
            ]
          },
          "invoice_items": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/InvoiceItem"
            },
            "description": "The invoice's line items, in the order they appear on it."
          }
        },
        "required": [
          "id",
          "invoice_number",
          "title",
          "currency",
          "issued_date",
          "due_date",
          "project_id",
          "client_email",
          "public_url_token",
          "created_at",
          "total_amount",
          "discount_amount",
          "status",
          "due_date_option",
          "company_id",
          "client_name",
          "subtotal",
          "tax_amount",
          "kind",
          "repeat_interval",
          "repeat_finish_date",
          "series_id",
          "url",
          "invoice_items"
        ],
        "description": "A bill you send to a client for work on one project, with its line items."
      },
      "NoteLink": {
        "type": "object",
        "properties": {
          "id": {
            "type": "integer",
            "description": "Unique identifier for the attachment between the note and the record.",
            "examples": [
              907
            ]
          },
          "record_id": {
            "type": "integer",
            "description": "Id of the attached record, in the resource named by `record_type`.",
            "examples": [
              42
            ]
          },
          "record_type": {
            "type": [
              "string",
              "null"
            ],
            "description": "Which kind of record the note is attached to.",
            "enum": [
              "company",
              "project",
              "contact",
              null
            ],
            "examples": [
              "company"
            ]
          }
        },
        "required": [
          "id",
          "record_id",
          "record_type"
        ],
        "description": "The attachment between a note and one of the records it is written about."
      },
      "Note": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid",
            "description": "Unique identifier for the note.",
            "examples": [
              "3f1c9b64-0c2d-4d19-9d4e-6c9a1f7b2f11"
            ]
          },
          "title": {
            "type": "string",
            "description": "Note title. Empty when the note was written without one.",
            "examples": [
              "Kickoff call"
            ]
          },
          "date": {
            "type": [
              "string",
              "null"
            ],
            "format": "date",
            "description": "The day the note is about, which is what the app sorts and groups notes by. Defaults to today in your account timezone.",
            "examples": [
              "2026-05-01"
            ]
          },
          "visibility": {
            "type": "string",
            "enum": [
              "everyone",
              "author_only"
            ],
            "description": "`everyone` for a note the whole account can read, `author_only` for one only its author can. Defaults to `everyone`, and only the member who wrote the note can change it.",
            "examples": [
              "everyone"
            ]
          },
          "created_by_member_id": {
            "type": [
              "integer",
              "null"
            ],
            "description": "Team member id of the author, or `null` when they are no longer a member of your account.",
            "examples": [
              314
            ]
          },
          "created_at": {
            "type": "string",
            "format": "date-time",
            "description": "When the note was written.",
            "examples": [
              "2026-04-14T09:12:04.000Z"
            ]
          },
          "updated_at": {
            "type": "string",
            "format": "date-time",
            "description": "When the note was last edited.",
            "examples": [
              "2026-04-14T09:12:04.000Z"
            ]
          },
          "note_links": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/NoteLink"
            },
            "description": "The records this note is attached to, leaving out any your token cannot reach. Empty for a workspace note that stands on its own."
          }
        },
        "required": [
          "id",
          "title",
          "date",
          "visibility",
          "created_by_member_id",
          "created_at",
          "updated_at",
          "note_links"
        ],
        "description": "Something written down about a client, a project or a contact — or nothing in particular, in which case it sits on its own in the workspace."
      },
      "NoteDetailedTrait": {
        "type": "object",
        "properties": {
          "content_html": {
            "type": [
              "string",
              "null"
            ],
            "description": "The note body in the HTML the Bonsai editor renders, scrubbed of script-execution vectors. This is the field to send back to `PATCH /public-api/v1/notes/{id}`: read it, change what you need and return it, and everything you left alone is preserved. `null` only when the note has no body at all. Returned on single-note responses only, never in a list.",
            "examples": [
              "<h2>Renewal call</h2><p>Agreed to send a <strong>revised quote</strong>.</p>"
            ]
          },
          "content_plain_text": {
            "type": [
              "string",
              "null"
            ],
            "description": "The same body with the markup removed, for when you only need to read what the note says. It cannot be sent back — the update endpoint takes `content_html`. `null` here means the body holds no text, not that the note is empty: a note containing only an image or a table returns `null` while `content_html` returns the body. Returned on single-note responses only, never in a list.",
            "examples": [
              "Discussed the renewal and agreed to send a revised quote."
            ]
          }
        },
        "required": [
          "content_html",
          "content_plain_text"
        ],
        "description": "The body of the note, returned when you write or retrieve a single note but never in a list."
      },
      "Project": {
        "type": "object",
        "properties": {
          "id": {
            "type": "integer",
            "description": "Unique identifier for the project.",
            "examples": [
              51
            ]
          },
          "title": {
            "type": [
              "string",
              "null"
            ],
            "description": "Project title.",
            "examples": [
              "Acme Website"
            ]
          },
          "public_url_token": {
            "type": [
              "string",
              "null"
            ],
            "description": "Token in the project's client-facing URL. The list endpoint accepts it as `filter[public_url_token]`.",
            "examples": [
              "acme-website-x4n2"
            ]
          },
          "currency": {
            "type": "string",
            "description": "Three-letter ISO 4217 currency code the project bills in.",
            "examples": [
              "USD"
            ]
          },
          "start_date": {
            "type": [
              "string",
              "null"
            ],
            "format": "date",
            "description": "Date work on the project starts.",
            "examples": [
              "2026-05-01"
            ]
          },
          "sequence": {
            "type": [
              "integer",
              "null"
            ],
            "description": "Counter behind `number`, incrementing per client. `null` until a number has been assigned.",
            "examples": [
              1
            ]
          },
          "number": {
            "type": [
              "string",
              "null"
            ],
            "description": "Human-readable project reference. `null` until one has been assigned.",
            "examples": [
              "ACME-0001"
            ]
          },
          "board_group_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid",
            "description": "Id of the pipeline stage the project's board card sits in, or `null` when the project is not on a board.",
            "examples": [
              "0c8f1e2a-3b4c-4d5e-6f70-8192a3b4c5d6"
            ]
          },
          "company_name": {
            "type": [
              "string",
              "null"
            ],
            "description": "Name of the client the project is for, or `null`.",
            "examples": [
              "Acme Corp"
            ]
          },
          "client_or_company_name": {
            "type": "string",
            "description": "Deprecated — read `company_name` instead, which carries the same value. This field will be removed in a future release.",
            "deprecated": true,
            "examples": [
              "Acme Corp"
            ]
          },
          "billing_type": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "time",
              "fixed_fee",
              "retainer",
              "not_billable",
              null
            ],
            "description": "How the project bills: `time` (hourly), `fixed_fee` (a single agreed fee), `retainer` (a recurring fee) or `not_billable`.",
            "examples": [
              "fixed_fee"
            ]
          },
          "billing_cycle": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "weekly",
              "monthly",
              "quarterly",
              "biannually",
              "yearly",
              null
            ],
            "description": "How often a retainer bills. `null` for every other billing type.",
            "examples": [
              "monthly"
            ]
          },
          "billing_fee": {
            "type": [
              "string",
              "null"
            ],
            "description": "The agreed fee as a decimal string, in `currency`. Set on `fixed_fee` and `retainer` projects, `null` otherwise.",
            "examples": [
              "5000.0"
            ]
          },
          "company_id": {
            "type": [
              "integer",
              "null"
            ],
            "description": "Id of the client company the project is for, or `null` when the client is not a company record.",
            "examples": [
              42
            ]
          },
          "url": {
            "type": "string",
            "description": "Absolute link to the project in the Bonsai app. Share it to send someone straight to the record.",
            "examples": [
              "https://app.hellobonsai.com/projects/acme-website-x4n2"
            ]
          }
        },
        "required": [
          "id",
          "title",
          "public_url_token",
          "currency",
          "start_date",
          "sequence",
          "number",
          "board_group_id",
          "company_name",
          "client_or_company_name",
          "billing_type",
          "billing_cycle",
          "billing_fee",
          "company_id",
          "url"
        ],
        "description": "An engagement you deliver for a client. Tasks, logged time and invoices all hang off a project."
      },
      "Task": {
        "type": "object",
        "properties": {
          "uuid": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid",
            "description": "Unique identifier for the task. This is the id every task endpoint takes in its path.",
            "examples": [
              "ad3fdad8-5072-46df-8800-bb57299179df"
            ]
          },
          "title": {
            "type": [
              "string",
              "null"
            ],
            "description": "Task title.",
            "examples": [
              "Finalize homepage mockups"
            ]
          },
          "number": {
            "type": [
              "string",
              "null"
            ],
            "description": "Human-readable task reference. `null` until one has been assigned.",
            "examples": [
              "ACME-00104"
            ]
          },
          "due_date": {
            "type": [
              "string",
              "null"
            ],
            "format": "date",
            "description": "Date the task is due, or `null` when it is unscheduled.",
            "examples": [
              "2026-05-15"
            ]
          },
          "start_date": {
            "type": [
              "string",
              "null"
            ],
            "format": "date",
            "description": "Date work on the task starts, or `null` when unset.",
            "examples": [
              "2026-05-01"
            ]
          },
          "time_estimate_in_minutes": {
            "type": [
              "integer",
              "null"
            ],
            "description": "Estimated effort in whole minutes, or `null` when no estimate is set.",
            "examples": [
              90
            ]
          },
          "priority": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "urgent",
              "high",
              "medium",
              "low",
              null
            ],
            "description": "Priority bucket, or `null` when the task has no priority.",
            "examples": [
              "high"
            ]
          },
          "completed_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "When the task was marked complete, or `null` while it is still open.",
            "examples": [
              "2026-04-14T09:12:04.000Z"
            ]
          },
          "archived_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "When the task was archived, or `null` while it is active.",
            "examples": [
              "2026-04-14T09:12:04.000Z"
            ]
          },
          "assignee_id": {
            "type": [
              "integer",
              "null"
            ],
            "description": "User id of the assignee, or `null` when unassigned. Read-only — the create and update endpoints take `assignee_member_id` instead.",
            "examples": [
              6012
            ]
          },
          "assignee_member_id": {
            "type": [
              "integer",
              "null"
            ],
            "description": "Team member id of the assignee, or `null` when unassigned. This is the value the create and update endpoints accept.",
            "examples": [
              314
            ]
          },
          "creator_member_id": {
            "type": [
              "integer",
              "null"
            ],
            "description": "Team member id of whoever created the task.",
            "examples": [
              315
            ]
          },
          "project_id": {
            "type": [
              "integer",
              "null"
            ],
            "description": "Id of the project the task belongs to, or `null` for a task that stands on its own.",
            "examples": [
              51
            ]
          },
          "billable": {
            "type": [
              "boolean",
              "null"
            ],
            "description": "Whether time logged against this task can be billed to the client. Absent unless the token is allowed to see billing.",
            "examples": [
              true
            ]
          },
          "deal_id": {
            "type": [
              "integer",
              "null"
            ],
            "description": "Id of the deal the task belongs to, or `null` when it is not attached to a deal. Absent unless the token is allowed to see deals.",
            "examples": [
              17
            ]
          },
          "parent_task_uuid": {
            "type": [
              "string",
              "null"
            ],
            "description": "`uuid` of this task's parent when it is a subtask, `null` for a top-level task.",
            "examples": [
              "e5b1c7d2-91aa-4f30-8f6d-2c1b4a5e7f80"
            ]
          },
          "project_title": {
            "type": [
              "string",
              "null"
            ],
            "description": "Title of the project the task belongs to, or `null`.",
            "examples": [
              "Acme Website"
            ]
          },
          "assignee_member_name": {
            "type": [
              "string",
              "null"
            ],
            "description": "Name of the assignee, or `null` when the task is unassigned.",
            "examples": [
              "Maria Santos"
            ]
          },
          "url": {
            "type": "string",
            "description": "Absolute link to the task in the Bonsai app. Resolves to the task on its project board, or to the standalone task list when it belongs to no project.",
            "examples": [
              "https://app.hellobonsai.com/tasks/ad3fdad8-5072-46df-8800-bb57299179df"
            ]
          },
          "task_status": {
            "description": "The board column the task currently sits in, or `null` when it has no status.",
            "anyOf": [
              {
                "$ref": "#/components/schemas/TaskStatus"
              },
              {
                "type": "null"
              }
            ]
          },
          "company_tags": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/CompanyTag"
            },
            "description": "Tags applied to the task. Only `id` and `name` are returned here — list the tags endpoint for a tag's type and color."
          }
        },
        "required": [
          "uuid",
          "title",
          "number",
          "due_date",
          "start_date",
          "time_estimate_in_minutes",
          "priority",
          "completed_at",
          "archived_at",
          "assignee_id",
          "assignee_member_id",
          "creator_member_id",
          "project_id",
          "parent_task_uuid",
          "project_title",
          "assignee_member_name",
          "url",
          "task_status",
          "company_tags"
        ],
        "description": "A unit of work, optionally inside a project and optionally nested under a parent task."
      },
      "TaskDetailedTrait": {
        "type": "object",
        "properties": {
          "description_html": {
            "type": [
              "string",
              "null"
            ],
            "description": "Task description as an HTML fragment, sanitized to a safe set of tags. Returned on single-task responses only, never in a list.",
            "examples": [
              "<p>Draft the <strong>brief</strong></p>"
            ]
          },
          "description_plain_text": {
            "type": [
              "string",
              "null"
            ],
            "description": "Task description with all HTML removed. Returned on single-task responses only, never in a list.",
            "examples": [
              "Draft the brief"
            ]
          }
        },
        "required": [
          "description_html",
          "description_plain_text"
        ],
        "description": "The description fields returned when you retrieve a single task, but never in a list."
      },
      "TaskStatus": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid",
            "description": "Unique identifier for the status. Pass this value as `task_status_id` when creating or updating a task.",
            "examples": [
              "e1c4b06e-3a0f-4a52-9c8f-6a1d2b3c4d5e"
            ]
          },
          "status": {
            "type": "string",
            "description": "Status name as it appears on the board.",
            "examples": [
              "In Progress"
            ]
          },
          "state": {
            "type": "string",
            "enum": [
              "active",
              "complete"
            ],
            "description": "Whether tasks in this status count as open (`active`) or finished (`complete`).",
            "examples": [
              "active"
            ]
          },
          "color": {
            "type": [
              "string",
              "null"
            ],
            "description": "Hex color the status is drawn in, or `null` when it has none.",
            "examples": [
              "#520EB0"
            ]
          },
          "position": {
            "type": "integer",
            "description": "Position of the status on the board, ordered left to right.",
            "examples": [
              1
            ]
          }
        },
        "required": [
          "id",
          "status",
          "state",
          "color",
          "position"
        ],
        "description": "A column of your task board. Moving a task between statuses is how you track its progress."
      },
      "TeamMember": {
        "type": "object",
        "properties": {
          "user_id": {
            "type": [
              "integer",
              "null"
            ],
            "description": "Id of the Bonsai user behind the membership, or `null` for an invitation nobody has accepted yet.",
            "examples": [
              6012
            ]
          },
          "deactivated_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "When an administrator deactivated the member, or `null`. A member with a value here has no access, whatever `confirmed_at` says.",
            "examples": [
              "2026-04-14T09:12:04.000Z"
            ]
          },
          "confirmed_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "When the member accepted their invitation, or `null` while it is still outstanding.",
            "examples": [
              "2026-04-14T09:12:04.000Z"
            ]
          },
          "declined_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "When the member declined their invitation, or `null`. Never set alongside `confirmed_at`.",
            "examples": [
              "2026-04-14T09:12:04.000Z"
            ]
          },
          "company_member_id": {
            "type": "integer",
            "description": "Unique identifier for the membership. This is the value write endpoints accept as `assignee_member_id` or `owner_member_id`.",
            "examples": [
              314
            ]
          },
          "name": {
            "type": [
              "string",
              "null"
            ],
            "description": "The member's full name, or `null` before they accept their invitation.",
            "examples": [
              "Maria Santos"
            ]
          },
          "email": {
            "type": [
              "string",
              "null"
            ],
            "description": "The member's email address.",
            "examples": [
              "maria@example.com"
            ]
          },
          "role": {
            "type": [
              "string",
              "null"
            ],
            "description": "The member's job title, for example `Software Engineer`. Free text set by your team, `null` when unset. It carries no authorization meaning — see `permission_profile` for that.",
            "examples": [
              "Software Engineer"
            ]
          },
          "permission_profile": {
            "type": [
              "string",
              "null"
            ],
            "description": "Name of the permission profile that decides what the member is allowed to do, for example `Owner` or `Collaborator`. `null` when unset.",
            "examples": [
              "Project Manager"
            ]
          }
        },
        "required": [
          "user_id",
          "deactivated_at",
          "confirmed_at",
          "declined_at",
          "company_member_id",
          "name",
          "email",
          "role",
          "permission_profile"
        ],
        "description": "Somebody's membership of your Bonsai account, and the ids you use to assign work to them."
      },
      "TimeEntry": {
        "type": "object",
        "properties": {
          "key": {
            "type": "string",
            "description": "Unique identifier for the time entry. This is the id the update endpoint takes in its path.",
            "examples": [
              "01JTR8Z9K5QW3M7VX4YB2NC6HD"
            ]
          },
          "seconds": {
            "type": "integer",
            "description": "Duration of the entry in seconds.",
            "examples": [
              3600
            ]
          },
          "date": {
            "type": "string",
            "format": "date",
            "description": "Date the work was done.",
            "examples": [
              "2026-06-17"
            ]
          },
          "notes": {
            "type": [
              "string",
              "null"
            ],
            "description": "What the time was spent on, or `null`.",
            "examples": [
              "Homepage build"
            ]
          },
          "rate": {
            "type": [
              "string",
              "null"
            ],
            "description": "Hourly rate applied to the entry, as a decimal string in `currency`. `null` when no rate applies. Absent unless the token is allowed to see billing.",
            "examples": [
              "150.0"
            ]
          },
          "non_billable": {
            "type": "boolean",
            "description": "Whether the entry is excluded from billing. Absent unless the token is allowed to see billing.",
            "examples": [
              false
            ]
          },
          "billable_amount": {
            "type": "string",
            "description": "What the entry bills for, as a decimal string in `currency`. Absent unless the token is allowed to see billing.",
            "examples": [
              "150.0"
            ]
          },
          "status": {
            "type": "string",
            "description": "Whether the entry has been placed on an invoice. `billing_status` is the fuller picture, and is what the list filter matches on.",
            "enum": [
              "unbilled",
              "billed"
            ],
            "examples": [
              "unbilled"
            ]
          },
          "project_id": {
            "type": [
              "integer",
              "null"
            ],
            "description": "Id of the project the time is logged against, or `null` when the entry is not attached to one.",
            "examples": [
              51
            ]
          },
          "owner_member_id": {
            "type": [
              "integer",
              "null"
            ],
            "description": "Team member id of whoever the time is logged for, or `null`.",
            "examples": [
              314
            ]
          },
          "created_at": {
            "type": "string",
            "format": "date-time",
            "description": "When the entry was logged.",
            "examples": [
              "2026-04-14T09:12:04.000Z"
            ]
          },
          "billing_status": {
            "type": "string",
            "description": "Effective billing state: `non_billable`, `unbilled` (billable, not on an invoice yet) or `billed` (on an invoice that has been sent, is overdue, is awaiting payment or has been paid, or marked billed by hand). Accepted by `filter[billing_status]` on the list endpoint, and writable as `billed` or `unbilled` on the update endpoint. Absent unless the token is allowed to see billing.",
            "enum": [
              "non_billable",
              "unbilled",
              "billed"
            ],
            "examples": [
              "unbilled"
            ]
          },
          "currency": {
            "type": "string",
            "description": "Three-letter ISO 4217 currency code for `rate` and `billable_amount`.",
            "examples": [
              "USD"
            ]
          },
          "formatted_time": {
            "type": "string",
            "description": "`seconds` rendered as `HH:MM:SS`.",
            "examples": [
              "01:00:00"
            ]
          },
          "task_uuid": {
            "type": [
              "string",
              "null"
            ],
            "description": "`uuid` of the task the entry is linked to, or `null` when it is logged against a project only.",
            "examples": [
              "ad3fdad8-5072-46df-8800-bb57299179df"
            ]
          }
        },
        "required": [
          "key",
          "seconds",
          "date",
          "notes",
          "status",
          "project_id",
          "owner_member_id",
          "created_at",
          "currency",
          "formatted_time",
          "task_uuid"
        ],
        "description": "Time logged against a project or task, and the basis for billing hourly work."
      }
    }
  },
  "paths": {
    "/public-api/v1/board_groups": {
      "get": {
        "summary": "List board groups",
        "tags": [
          "Board Groups"
        ],
        "operationId": "listBoardGroups",
        "description": "Lists the stages of your deal and project boards, a page at a time.\n\nDeals move through these stages, so this is where you turn a stage name\nsuch as `Won` into the `board_group_id` the deal endpoints take.\n\nYou only get the boards you are allowed to list: deal stages need\npermission to see deals, project stages need permission to see\nprojects. Without either, the list comes back empty.\n\n## Filtering\n\n- `filter[name]` — partial, case-insensitive match on the stage name.\n- `filter[resource_type]` — `Deal` or `Project`, to return one board's\n  stages only. Omit it to get both.",
        "security": [
          {
            "oauth2": []
          },
          {
            "api_key": []
          }
        ],
        "parameters": [
          {
            "name": "page[number]",
            "in": "query",
            "required": false,
            "description": "Page to return, starting at 1.",
            "schema": {
              "type": "integer",
              "minimum": 1
            }
          },
          {
            "name": "page[size]",
            "in": "query",
            "required": false,
            "description": "Records per page. Defaults to 25; anything above 100 is reduced to 100.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100
            }
          },
          {
            "name": "filter[name]",
            "in": "query",
            "required": false,
            "description": "Partial, case-insensitive match on the stage name.",
            "schema": {
              "type": "string"
            },
            "examples": {
              "default": {
                "value": "Won"
              }
            }
          },
          {
            "name": "filter[resource_type]",
            "in": "query",
            "required": false,
            "description": "Return one board's stages only. Omit to get both boards.",
            "schema": {
              "type": "string",
              "enum": [
                "Deal",
                "Project"
              ]
            },
            "examples": {
              "default": {
                "value": "Deal"
              }
            }
          }
        ],
        "responses": {
          "200": {
            "description": "A page of pipeline stages, grouped by board and ordered by their position on it.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/BoardGroup"
                      }
                    },
                    "meta": {
                      "$ref": "#/components/schemas/PaginatedMeta"
                    }
                  },
                  "required": [
                    "data",
                    "meta"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "A filter key is unknown, or `filter[resource_type]` is not `Deal` or `Project`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "401": {
            "description": "The access token is missing, expired or invalid.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded. Wait at least `Retry-After` seconds before trying again.",
            "headers": {
              "Retry-After": {
                "schema": {
                  "type": "integer"
                },
                "description": "Seconds to wait before sending another request."
              },
              "RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "How many requests the exhausted window allows."
              },
              "RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left in the window. Always `0` on a throttled response."
              },
              "RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Seconds until the exhausted window resets."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        }
      }
    },
    "/public-api/v1/tasks/{task_id}/comments": {
      "get": {
        "summary": "List comments on a task",
        "tags": [
          "Comments"
        ],
        "operationId": "listTaskComments",
        "description": "Lists the comments on one task, newest first.\n\nComments always belong to a task or a\ndeal, so there is no\nendpoint that lists them all — you read them per record. A\ntask you cannot see returns `404`.\n\nTwo kinds of entry live in this thread: comments people wrote\n(`user_created`) and the activity Bonsai records on its own\n(`event`) — status changes, reassignments and the like. Only the\nwritten comments come back by default; use `filter[kind]` to get the\nactivity entries instead, or both together.",
        "security": [
          {
            "oauth2": []
          },
          {
            "api_key": []
          }
        ],
        "parameters": [
          {
            "name": "task_id",
            "in": "path",
            "required": true,
            "description": "The task to read comments from — the task's `uuid`.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "filter[kind]",
            "in": "query",
            "required": false,
            "description": "Which entries to return: `user_created` for written comments (the default), `events` for the activity Bonsai records automatically, or `all` for both.",
            "schema": {
              "type": "string",
              "enum": [
                "user_created",
                "events",
                "all"
              ]
            }
          },
          {
            "name": "page[number]",
            "in": "query",
            "required": false,
            "description": "Page to return, starting at 1.",
            "schema": {
              "type": "integer",
              "minimum": 1
            }
          },
          {
            "name": "page[size]",
            "in": "query",
            "required": false,
            "description": "Records per page. Defaults to 25; anything above 100 is reduced to 100.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100
            }
          }
        ],
        "responses": {
          "200": {
            "description": "A page of comments, newest first.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Comment"
                      }
                    },
                    "meta": {
                      "$ref": "#/components/schemas/PaginatedMeta"
                    }
                  },
                  "required": [
                    "data",
                    "meta"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "`filter[kind]` is not `user_created`, `events` or `all`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "401": {
            "description": "The access token is missing, expired or invalid.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "403": {
            "description": "This token is not allowed to read this record's comments.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "404": {
            "description": "No such task in your account, or this token cannot see it.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded. Wait at least `Retry-After` seconds before trying again.",
            "headers": {
              "Retry-After": {
                "schema": {
                  "type": "integer"
                },
                "description": "Seconds to wait before sending another request."
              },
              "RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "How many requests the exhausted window allows."
              },
              "RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left in the window. Always `0` on a throttled response."
              },
              "RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Seconds until the exhausted window resets."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        }
      },
      "post": {
        "summary": "Comment on a task",
        "tags": [
          "Comments"
        ],
        "operationId": "createTaskComment",
        "description": "Posts a comment on one task, attributed to whoever the\ntoken belongs to. `body` is the only field, and unsafe HTML is stripped\nbefore it is stored. A task you cannot see returns\n`404`.\n\nPosting a comment notifies the people already following that task — its assignee and anyone subscribed to it. Who gets notified\nis decided entirely by the task, never by what the\ncomment says.\n\n**@-mentions do not work here.** Writing `@Alex` in `body` stores those\ncharacters and nothing more: it does not link to Alex, does not notify\nAlex, and there is no parameter that would. If you need to reach a\nspecific person, this is not the endpoint for it.\n\nAttachments are not supported either.",
        "security": [
          {
            "oauth2": []
          },
          {
            "api_key": []
          }
        ],
        "parameters": [
          {
            "name": "task_id",
            "in": "path",
            "required": true,
            "description": "The task to comment on — the task's `uuid`.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "201": {
            "description": "The comment was posted.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/Comment"
                    },
                    "meta": {
                      "$ref": "#/components/schemas/Meta"
                    }
                  },
                  "required": [
                    "data",
                    "meta"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "The request body is missing its `comment` wrapper.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "401": {
            "description": "The access token is missing, expired or invalid.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "403": {
            "description": "This token is not allowed to comment on this task.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "404": {
            "description": "No such task in your account, or this token cannot see it.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "422": {
            "description": "The comment could not be saved — usually an empty `body`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded. Wait at least `Retry-After` seconds before trying again.",
            "headers": {
              "Retry-After": {
                "schema": {
                  "type": "integer"
                },
                "description": "Seconds to wait before sending another request."
              },
              "RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "How many requests the exhausted window allows."
              },
              "RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left in the window. Always `0` on a throttled response."
              },
              "RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Seconds until the exhausted window resets."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        },
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "comment": {
                    "type": "object",
                    "properties": {
                      "body": {
                        "type": "string",
                        "description": "What the comment says. Required. Unsafe HTML is stripped, and `@Name` is stored as plain text without mentioning or notifying anyone.",
                        "examples": [
                          "Looks good to me!"
                        ]
                      }
                    },
                    "required": [
                      "body"
                    ]
                  }
                },
                "required": [
                  "comment"
                ]
              }
            }
          },
          "required": true,
          "description": "The comment, wrapped in a `comment` key."
        }
      }
    },
    "/public-api/v1/deals/{deal_id}/comments": {
      "get": {
        "summary": "List comments on a deal",
        "tags": [
          "Comments"
        ],
        "operationId": "listDealComments",
        "description": "Lists the comments on one deal, newest first.\n\nComments always belong to a deal or a\ntask, so there is no\nendpoint that lists them all — you read them per record. A\ndeal you cannot see returns `404`.\n\nTwo kinds of entry live in this thread: comments people wrote\n(`user_created`) and the activity Bonsai records on its own\n(`event`) — status changes, reassignments and the like. Only the\nwritten comments come back by default; use `filter[kind]` to get the\nactivity entries instead, or both together.",
        "security": [
          {
            "oauth2": []
          },
          {
            "api_key": []
          }
        ],
        "parameters": [
          {
            "name": "deal_id",
            "in": "path",
            "required": true,
            "description": "The deal to read comments from — the deal's numeric id.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "filter[kind]",
            "in": "query",
            "required": false,
            "description": "Which entries to return: `user_created` for written comments (the default), `events` for the activity Bonsai records automatically, or `all` for both.",
            "schema": {
              "type": "string",
              "enum": [
                "user_created",
                "events",
                "all"
              ]
            }
          },
          {
            "name": "page[number]",
            "in": "query",
            "required": false,
            "description": "Page to return, starting at 1.",
            "schema": {
              "type": "integer",
              "minimum": 1
            }
          },
          {
            "name": "page[size]",
            "in": "query",
            "required": false,
            "description": "Records per page. Defaults to 25; anything above 100 is reduced to 100.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100
            }
          }
        ],
        "responses": {
          "200": {
            "description": "A page of comments, newest first.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Comment"
                      }
                    },
                    "meta": {
                      "$ref": "#/components/schemas/PaginatedMeta"
                    }
                  },
                  "required": [
                    "data",
                    "meta"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "`filter[kind]` is not `user_created`, `events` or `all`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "401": {
            "description": "The access token is missing, expired or invalid.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "403": {
            "description": "This token is not allowed to read this record's comments.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "404": {
            "description": "No such deal in your account, or this token cannot see it.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded. Wait at least `Retry-After` seconds before trying again.",
            "headers": {
              "Retry-After": {
                "schema": {
                  "type": "integer"
                },
                "description": "Seconds to wait before sending another request."
              },
              "RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "How many requests the exhausted window allows."
              },
              "RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left in the window. Always `0` on a throttled response."
              },
              "RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Seconds until the exhausted window resets."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        }
      },
      "post": {
        "summary": "Comment on a deal",
        "tags": [
          "Comments"
        ],
        "operationId": "createDealComment",
        "description": "Posts a comment on one deal, attributed to whoever the\ntoken belongs to. `body` is the only field, and unsafe HTML is stripped\nbefore it is stored. A deal you cannot see returns\n`404`.\n\nPosting a comment notifies nobody — deal comments do not send notifications today. Who gets notified\nis decided entirely by the deal, never by what the\ncomment says.\n\n**@-mentions do not work here.** Writing `@Alex` in `body` stores those\ncharacters and nothing more: it does not link to Alex, does not notify\nAlex, and there is no parameter that would. If you need to reach a\nspecific person, this is not the endpoint for it.\n\nAttachments are not supported either.",
        "security": [
          {
            "oauth2": []
          },
          {
            "api_key": []
          }
        ],
        "parameters": [
          {
            "name": "deal_id",
            "in": "path",
            "required": true,
            "description": "The deal to comment on — the deal's numeric id.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "201": {
            "description": "The comment was posted.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/Comment"
                    },
                    "meta": {
                      "$ref": "#/components/schemas/Meta"
                    }
                  },
                  "required": [
                    "data",
                    "meta"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "The request body is missing its `comment` wrapper.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "401": {
            "description": "The access token is missing, expired or invalid.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "403": {
            "description": "This token is not allowed to comment on this deal.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "404": {
            "description": "No such deal in your account, or this token cannot see it.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "422": {
            "description": "The comment could not be saved — usually an empty `body`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded. Wait at least `Retry-After` seconds before trying again.",
            "headers": {
              "Retry-After": {
                "schema": {
                  "type": "integer"
                },
                "description": "Seconds to wait before sending another request."
              },
              "RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "How many requests the exhausted window allows."
              },
              "RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left in the window. Always `0` on a throttled response."
              },
              "RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Seconds until the exhausted window resets."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        },
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "comment": {
                    "type": "object",
                    "properties": {
                      "body": {
                        "type": "string",
                        "description": "What the comment says. Required. Unsafe HTML is stripped, and `@Name` is stored as plain text without mentioning or notifying anyone.",
                        "examples": [
                          "Looks good to me!"
                        ]
                      }
                    },
                    "required": [
                      "body"
                    ]
                  }
                },
                "required": [
                  "comment"
                ]
              }
            }
          },
          "required": true,
          "description": "The comment, wrapped in a `comment` key."
        }
      }
    },
    "/public-api/v1/company_tags": {
      "get": {
        "summary": "List company tags",
        "tags": [
          "Company Tags"
        ],
        "operationId": "listCompanyTags",
        "description": "Lists the tags your account has defined, a page at a time. Every tag\ntype comes back in one flat collection; filter by `tag_type` to narrow\nit.\n\nA tag belongs to exactly one type of record, so a tag you intend to put\non a task must be a `task` tag. Pass the ids you find here in `tag_ids`\nwhen creating or updating a task.\n\nYou only get the tag types you are allowed to list the underlying\nrecords for: `task` tags need permission to see tasks, `deal` tags need\npermission to see deals, and so on. A token that cannot list any of\nthem gets a `403`.\n\n## Filtering\n\n- `filter[name]` — partial, case-insensitive match on the tag name.\n- `filter[tag_type]` — return one type of tag only. Omit it to get all\n  types.",
        "security": [
          {
            "oauth2": []
          },
          {
            "api_key": []
          }
        ],
        "parameters": [
          {
            "name": "page[number]",
            "in": "query",
            "required": false,
            "description": "Page to return, starting at 1.",
            "schema": {
              "type": "integer",
              "minimum": 1
            }
          },
          {
            "name": "page[size]",
            "in": "query",
            "required": false,
            "description": "Records per page. Defaults to 25; anything above 100 is reduced to 100.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100
            }
          },
          {
            "name": "filter[name]",
            "in": "query",
            "required": false,
            "description": "Partial, case-insensitive match on the tag name.",
            "schema": {
              "type": "string"
            },
            "examples": {
              "default": {
                "value": "VIP"
              }
            }
          },
          {
            "name": "filter[tag_type]",
            "in": "query",
            "required": false,
            "description": "Return one type of tag only. Omit to get every type.",
            "schema": {
              "type": "string",
              "enum": [
                "client",
                "deal",
                "project",
                "vendor",
                "task",
                "time_entry",
                "expense",
                "supplier"
              ]
            },
            "examples": {
              "default": {
                "value": "client"
              }
            }
          }
        ],
        "responses": {
          "200": {
            "description": "A page of tags, grouped by type and then sorted by name.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "allOf": [
                          {
                            "$ref": "#/components/schemas/CompanyTag"
                          },
                          {
                            "$ref": "#/components/schemas/CompanyTagDetailedTrait"
                          }
                        ]
                      }
                    },
                    "meta": {
                      "$ref": "#/components/schemas/PaginatedMeta"
                    }
                  },
                  "required": [
                    "data",
                    "meta"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "A filter key is unknown, or `filter[tag_type]` is not one of the listed types.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "401": {
            "description": "The access token is missing, expired or invalid.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "403": {
            "description": "This token is not allowed to list any of the records tags can be put on.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded. Wait at least `Retry-After` seconds before trying again.",
            "headers": {
              "Retry-After": {
                "schema": {
                  "type": "integer"
                },
                "description": "Seconds to wait before sending another request."
              },
              "RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "How many requests the exhausted window allows."
              },
              "RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left in the window. Always `0` on a throttled response."
              },
              "RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Seconds until the exhausted window resets."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        }
      }
    },
    "/public-api/v1/companies": {
      "get": {
        "summary": "List companies",
        "tags": [
          "Companies"
        ],
        "operationId": "listCompanies",
        "description": "Lists the companies in your CRM, a page at a time. Archived companies\nare left out.\n\nSearch by name before creating a company, so you update the existing\nrecord instead of adding a duplicate.\n\n## Filtering\n\n- `filter[name]` — searches company names, matching from the start of\n  any word and ignoring case. `acme` finds \"Acme Corp\"; so does `corp`.",
        "security": [
          {
            "oauth2": []
          },
          {
            "api_key": []
          }
        ],
        "parameters": [
          {
            "name": "page[number]",
            "in": "query",
            "required": false,
            "description": "Page to return, starting at 1.",
            "schema": {
              "type": "integer",
              "minimum": 1
            }
          },
          {
            "name": "page[size]",
            "in": "query",
            "required": false,
            "description": "Records per page. Defaults to 25; anything above 100 is reduced to 100.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100
            }
          },
          {
            "name": "filter[name]",
            "in": "query",
            "required": false,
            "description": "Searches company names, matching from the start of any word and ignoring case.",
            "schema": {
              "type": "string"
            },
            "examples": {
              "default": {
                "value": "Acme"
              }
            }
          }
        ],
        "responses": {
          "200": {
            "description": "A page of companies.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Company"
                      }
                    },
                    "meta": {
                      "$ref": "#/components/schemas/PaginatedMeta"
                    }
                  },
                  "required": [
                    "data",
                    "meta"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "A filter key is unknown, or a pagination parameter is not an integer.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "401": {
            "description": "The access token is missing, expired or invalid.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "403": {
            "description": "This token is not allowed to read or write companies in your CRM.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded. Wait at least `Retry-After` seconds before trying again.",
            "headers": {
              "Retry-After": {
                "schema": {
                  "type": "integer"
                },
                "description": "Seconds to wait before sending another request."
              },
              "RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "How many requests the exhausted window allows."
              },
              "RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left in the window. Always `0` on a throttled response."
              },
              "RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Seconds until the exhausted window resets."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        }
      },
      "post": {
        "summary": "Create a company",
        "tags": [
          "Companies"
        ],
        "operationId": "createCompany",
        "description": "Adds a company to your CRM. Only `name` is required.\n\nSearch the list endpoint by name first — nothing here deduplicates for\nyou, so creating a company you already have leaves you with two.\n\nEvery company created through the API is a client. Vendors cannot be\ncreated here.",
        "security": [
          {
            "oauth2": []
          },
          {
            "api_key": []
          }
        ],
        "parameters": [],
        "responses": {
          "201": {
            "description": "The company was created.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/Company"
                    },
                    "meta": {
                      "$ref": "#/components/schemas/Meta"
                    }
                  },
                  "required": [
                    "data",
                    "meta"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "The request body is missing its `company` wrapper.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "401": {
            "description": "The access token is missing, expired or invalid.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "403": {
            "description": "This token is not allowed to read or write companies in your CRM.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "422": {
            "description": "The company could not be saved — most often a blank name, a value outside one of the fixed lists, or a domain already claimed by another company.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded. Wait at least `Retry-After` seconds before trying again.",
            "headers": {
              "Retry-After": {
                "schema": {
                  "type": "integer"
                },
                "description": "Seconds to wait before sending another request."
              },
              "RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "How many requests the exhausted window allows."
              },
              "RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left in the window. Always `0` on a throttled response."
              },
              "RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Seconds until the exhausted window resets."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        },
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "company": {
                    "type": "object",
                    "properties": {
                      "name": {
                        "type": "string",
                        "description": "Company name. Required.",
                        "examples": [
                          "Acme Corp"
                        ]
                      },
                      "default_contact_id": {
                        "type": [
                          "integer",
                          "null"
                        ],
                        "description": "Id of the contact to make this company's primary point of contact. Must be a contact in your account — find one with `GET /public-api/v1/contacts`.",
                        "examples": [
                          7
                        ]
                      },
                      "description": {
                        "type": [
                          "string",
                          "null"
                        ],
                        "description": "Free text notes about the company.",
                        "examples": [
                          "Long standing retainer client."
                        ]
                      },
                      "industry": {
                        "type": [
                          "string",
                          "null"
                        ],
                        "enum": [
                          "arts_and_entertainment",
                          "business_and_consumer_services",
                          "community_and_society",
                          "computers_electronics_and_technology",
                          "e_commerce_and_shopping",
                          "finance",
                          "food_and_drink",
                          "gambling",
                          "games",
                          "health",
                          "heavy_industry_and_engineering",
                          "hobbies_and_leisure",
                          "home_and_garden",
                          "jobs_and_career",
                          "law_and_government",
                          "lifestyle",
                          "luxury",
                          "news_and_media",
                          "pets_and_animals",
                          "reference_materials",
                          "science_and_education",
                          "sports",
                          "travel_and_tourism",
                          "vehicles",
                          null
                        ],
                        "description": "The industry the company operates in. Must be one of the listed values.",
                        "examples": [
                          "computers_electronics_and_technology"
                        ]
                      },
                      "company_size": {
                        "type": [
                          "string",
                          "null"
                        ],
                        "enum": [
                          "size_1_10",
                          "size_11_50",
                          "size_51_200",
                          "size_201_1000",
                          "size_1000_plus",
                          null
                        ],
                        "description": "Headcount band for the company. Must be one of the listed values.",
                        "examples": [
                          "size_201_1000"
                        ]
                      },
                      "source": {
                        "type": [
                          "string",
                          "null"
                        ],
                        "enum": [
                          "organic_search",
                          "paid_search",
                          "email_marketing",
                          "organic_social",
                          "referrals",
                          "other_campaigns",
                          "direct_traffic",
                          "offline_sources",
                          "paid_social",
                          "ai_referrals",
                          null
                        ],
                        "description": "How the client was acquired. Must be one of the listed values.",
                        "examples": [
                          "organic_search"
                        ]
                      },
                      "linkedin": {
                        "type": [
                          "string",
                          "null"
                        ],
                        "description": "Link to the company's LinkedIn profile.",
                        "examples": [
                          "https://linkedin.com/company/acme"
                        ]
                      },
                      "instagram": {
                        "type": [
                          "string",
                          "null"
                        ],
                        "description": "Link to the company's Instagram profile.",
                        "examples": [
                          "https://instagram.com/acme"
                        ]
                      },
                      "twitter": {
                        "type": [
                          "string",
                          "null"
                        ],
                        "description": "Link to the company's X profile.",
                        "examples": [
                          "https://x.com/acme"
                        ]
                      },
                      "facebook": {
                        "type": [
                          "string",
                          "null"
                        ],
                        "description": "Link to the company's Facebook page.",
                        "examples": [
                          "https://facebook.com/acme"
                        ]
                      },
                      "domains": {
                        "type": [
                          "array",
                          "null"
                        ],
                        "items": {
                          "type": "string"
                        },
                        "description": "Domains to associate with the company. Each value is lowercased and stripped of any protocol or `www.` prefix before it is stored. A domain can belong to only one company in your account; claiming one that is already taken fails with `422`.",
                        "examples": [
                          [
                            "example.com",
                            "example.net"
                          ]
                        ]
                      }
                    },
                    "required": [
                      "name"
                    ]
                  }
                },
                "required": [
                  "company"
                ]
              }
            }
          },
          "required": true,
          "description": "Company attributes, wrapped in a `company` key."
        }
      }
    },
    "/public-api/v1/companies/{id}": {
      "patch": {
        "summary": "Update a company",
        "tags": [
          "Companies"
        ],
        "operationId": "updateCompany",
        "description": "Updates a company. Every field is optional, and anything you leave out\nkeeps its current value.\n\nNote that `domains` adds rather than replaces — see the field for how to\nclear it.",
        "security": [
          {
            "oauth2": []
          },
          {
            "api_key": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Id of the company to update.",
            "schema": {
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The company was updated.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/Company"
                    },
                    "meta": {
                      "$ref": "#/components/schemas/Meta"
                    }
                  },
                  "required": [
                    "data",
                    "meta"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "The request body is missing its `company` wrapper.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "401": {
            "description": "The access token is missing, expired or invalid.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "403": {
            "description": "This token is not allowed to read or write companies in your CRM.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "404": {
            "description": "No company with that id in your account.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "422": {
            "description": "A value is outside one of the fixed lists, a domain is malformed or already claimed by another company, or `default_contact_id` is not a contact in your account.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded. Wait at least `Retry-After` seconds before trying again.",
            "headers": {
              "Retry-After": {
                "schema": {
                  "type": "integer"
                },
                "description": "Seconds to wait before sending another request."
              },
              "RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "How many requests the exhausted window allows."
              },
              "RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left in the window. Always `0` on a throttled response."
              },
              "RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Seconds until the exhausted window resets."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        },
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "company": {
                    "type": "object",
                    "properties": {
                      "name": {
                        "type": "string",
                        "description": "New company name. A company must have a name, so `null` or an empty string is ignored rather than clearing it.",
                        "examples": [
                          "Acme Corp"
                        ]
                      },
                      "default_contact_id": {
                        "type": [
                          "integer",
                          "null"
                        ],
                        "description": "Id of the contact to make this company's primary point of contact. Must be a contact in your account. A company keeps whichever primary contact it already has, so `null` is ignored rather than clearing it.",
                        "examples": [
                          7
                        ]
                      },
                      "description": {
                        "type": [
                          "string",
                          "null"
                        ],
                        "description": "Free text notes about the company. Send `null` to clear it.",
                        "examples": [
                          "Long standing retainer client."
                        ]
                      },
                      "industry": {
                        "type": [
                          "string",
                          "null"
                        ],
                        "enum": [
                          "arts_and_entertainment",
                          "business_and_consumer_services",
                          "community_and_society",
                          "computers_electronics_and_technology",
                          "e_commerce_and_shopping",
                          "finance",
                          "food_and_drink",
                          "gambling",
                          "games",
                          "health",
                          "heavy_industry_and_engineering",
                          "hobbies_and_leisure",
                          "home_and_garden",
                          "jobs_and_career",
                          "law_and_government",
                          "lifestyle",
                          "luxury",
                          "news_and_media",
                          "pets_and_animals",
                          "reference_materials",
                          "science_and_education",
                          "sports",
                          "travel_and_tourism",
                          "vehicles",
                          null
                        ],
                        "description": "The industry the company operates in. Must be one of the listed values. Send `null` to clear it.",
                        "examples": [
                          "computers_electronics_and_technology"
                        ]
                      },
                      "company_size": {
                        "type": [
                          "string",
                          "null"
                        ],
                        "enum": [
                          "size_1_10",
                          "size_11_50",
                          "size_51_200",
                          "size_201_1000",
                          "size_1000_plus",
                          null
                        ],
                        "description": "Headcount band for the company. Must be one of the listed values. Send `null` to clear it.",
                        "examples": [
                          "size_201_1000"
                        ]
                      },
                      "source": {
                        "type": [
                          "string",
                          "null"
                        ],
                        "enum": [
                          "organic_search",
                          "paid_search",
                          "email_marketing",
                          "organic_social",
                          "referrals",
                          "other_campaigns",
                          "direct_traffic",
                          "offline_sources",
                          "paid_social",
                          "ai_referrals",
                          null
                        ],
                        "description": "How the client was acquired. Must be one of the listed values. Send `null` to clear it.",
                        "examples": [
                          "organic_search"
                        ]
                      },
                      "linkedin": {
                        "type": [
                          "string",
                          "null"
                        ],
                        "description": "Link to the company's LinkedIn profile. Send `null` to clear it.",
                        "examples": [
                          "https://linkedin.com/company/acme"
                        ]
                      },
                      "instagram": {
                        "type": [
                          "string",
                          "null"
                        ],
                        "description": "Link to the company's Instagram profile. Send `null` to clear it.",
                        "examples": [
                          "https://instagram.com/acme"
                        ]
                      },
                      "twitter": {
                        "type": [
                          "string",
                          "null"
                        ],
                        "description": "Link to the company's X profile. Send `null` to clear it.",
                        "examples": [
                          "https://x.com/acme"
                        ]
                      },
                      "facebook": {
                        "type": [
                          "string",
                          "null"
                        ],
                        "description": "Link to the company's Facebook page. Send `null` to clear it.",
                        "examples": [
                          "https://facebook.com/acme"
                        ]
                      },
                      "domains": {
                        "type": [
                          "array",
                          "null"
                        ],
                        "items": {
                          "type": "string"
                        },
                        "description": "Domains to add to the company. This is additive, not a replacement: the values you send are added, and re-sending one the company already has changes nothing. To remove every domain, send an empty array. Leave the field out to keep the current domains.",
                        "examples": [
                          [
                            "example.net"
                          ]
                        ]
                      }
                    }
                  }
                },
                "required": [
                  "company"
                ]
              }
            }
          },
          "required": true,
          "description": "The attributes to change, wrapped in a `company` key. All of them are optional."
        }
      }
    },
    "/public-api/v1/contacts": {
      "get": {
        "summary": "List contacts",
        "tags": [
          "Contacts"
        ],
        "operationId": "listContacts",
        "description": "Lists the contacts in your CRM, a page at a time. Archived contacts are\nleft out.\n\nSearch by name or email to find someone before you add them, and filter\nby `company_id` to list everyone you know at one company.\n\n## Filtering\n\n- `filter[name]` — searches contact names, matching anywhere in the name\n  and ignoring case.\n- `filter[email]` — searches email addresses the same way.\n- `filter[company_id]` — one or more company ids. Returns the contacts\n  linked to those companies.",
        "security": [
          {
            "oauth2": []
          },
          {
            "api_key": []
          }
        ],
        "parameters": [
          {
            "name": "page[number]",
            "in": "query",
            "required": false,
            "description": "Page to return, starting at 1.",
            "schema": {
              "type": "integer",
              "minimum": 1
            }
          },
          {
            "name": "page[size]",
            "in": "query",
            "required": false,
            "description": "Records per page. Defaults to 25; anything above 100 is reduced to 100.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100
            }
          },
          {
            "name": "filter[name]",
            "in": "query",
            "required": false,
            "description": "Searches contact names, matching anywhere in the name and ignoring case.",
            "schema": {
              "type": "string"
            },
            "examples": {
              "default": {
                "value": "Alex"
              }
            }
          },
          {
            "name": "filter[email]",
            "in": "query",
            "required": false,
            "description": "Searches email addresses, matching anywhere in the address and ignoring case.",
            "schema": {
              "type": "string"
            },
            "examples": {
              "default": {
                "value": "alex@example.com"
              }
            }
          },
          {
            "name": "filter[company_id]",
            "in": "query",
            "required": false,
            "description": "One or more company ids, comma-separated or repeated. Returns the contacts linked to those companies.",
            "schema": {
              "type": "string"
            },
            "examples": {
              "default": {
                "value": "42"
              }
            }
          }
        ],
        "responses": {
          "200": {
            "description": "A page of contacts.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Contact"
                      }
                    },
                    "meta": {
                      "$ref": "#/components/schemas/PaginatedMeta"
                    }
                  },
                  "required": [
                    "data",
                    "meta"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "A filter key is unknown, or `filter[company_id]` is not an integer.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "401": {
            "description": "The access token is missing, expired or invalid.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "403": {
            "description": "This token is not allowed to read or write contacts in your CRM.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded. Wait at least `Retry-After` seconds before trying again.",
            "headers": {
              "Retry-After": {
                "schema": {
                  "type": "integer"
                },
                "description": "Seconds to wait before sending another request."
              },
              "RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "How many requests the exhausted window allows."
              },
              "RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left in the window. Always `0` on a throttled response."
              },
              "RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Seconds until the exhausted window resets."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        }
      },
      "post": {
        "summary": "Create a contact",
        "tags": [
          "Contacts"
        ],
        "operationId": "createContact",
        "description": "Adds a person to your CRM. `name` and `email` are required.\n\nPass `company_id` to link them to a company you already have, or leave it\nout for a contact who does not belong to one.",
        "security": [
          {
            "oauth2": []
          },
          {
            "api_key": []
          }
        ],
        "parameters": [],
        "responses": {
          "201": {
            "description": "The contact was created.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/Contact"
                    },
                    "meta": {
                      "$ref": "#/components/schemas/Meta"
                    }
                  },
                  "required": [
                    "data",
                    "meta"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "The request body is missing its `contact` wrapper.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "401": {
            "description": "The access token is missing, expired or invalid.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "403": {
            "description": "This token is not allowed to read or write contacts in your CRM.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "422": {
            "description": "The contact could not be saved — a required field is blank, the email is malformed or already in use, or `company_id` is not a company in your account.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded. Wait at least `Retry-After` seconds before trying again.",
            "headers": {
              "Retry-After": {
                "schema": {
                  "type": "integer"
                },
                "description": "Seconds to wait before sending another request."
              },
              "RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "How many requests the exhausted window allows."
              },
              "RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left in the window. Always `0` on a throttled response."
              },
              "RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Seconds until the exhausted window resets."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        },
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "contact": {
                    "type": "object",
                    "properties": {
                      "name": {
                        "type": "string",
                        "description": "Full name. Required.",
                        "examples": [
                          "Alex Johnson"
                        ]
                      },
                      "email": {
                        "type": "string",
                        "description": "Email address. Required, and must be unique among your contacts.",
                        "examples": [
                          "alex@example.com"
                        ]
                      },
                      "job_title": {
                        "type": [
                          "string",
                          "null"
                        ],
                        "description": "Job title.",
                        "examples": [
                          "CTO"
                        ]
                      },
                      "phone_number": {
                        "type": [
                          "string",
                          "null"
                        ],
                        "description": "Phone number, stored as given.",
                        "examples": [
                          "+1-415-555-0100"
                        ]
                      },
                      "company_id": {
                        "type": [
                          "integer",
                          "null"
                        ],
                        "description": "Id of the company to link the contact to. Find one with `GET /public-api/v1/companies`.",
                        "examples": [
                          42
                        ]
                      }
                    },
                    "required": [
                      "name",
                      "email"
                    ]
                  }
                },
                "required": [
                  "contact"
                ]
              }
            }
          },
          "required": true,
          "description": "Contact attributes, wrapped in a `contact` key."
        }
      }
    },
    "/public-api/v1/contacts/{id}": {
      "get": {
        "summary": "Retrieve a contact",
        "tags": [
          "Contacts"
        ],
        "operationId": "getContact",
        "description": "Retrieves one contact by id.",
        "security": [
          {
            "oauth2": []
          },
          {
            "api_key": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Id of the contact to retrieve.",
            "schema": {
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The contact.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/Contact"
                    },
                    "meta": {
                      "$ref": "#/components/schemas/Meta"
                    }
                  },
                  "required": [
                    "data",
                    "meta"
                  ]
                }
              }
            }
          },
          "401": {
            "description": "The access token is missing, expired or invalid.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "403": {
            "description": "This token is not allowed to read or write contacts in your CRM.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "404": {
            "description": "No contact with that id in your account. Archived and deleted contacts return `404` too.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded. Wait at least `Retry-After` seconds before trying again.",
            "headers": {
              "Retry-After": {
                "schema": {
                  "type": "integer"
                },
                "description": "Seconds to wait before sending another request."
              },
              "RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "How many requests the exhausted window allows."
              },
              "RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left in the window. Always `0` on a throttled response."
              },
              "RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Seconds until the exhausted window resets."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        }
      },
      "patch": {
        "summary": "Update a contact",
        "tags": [
          "Contacts"
        ],
        "operationId": "updateContact",
        "description": "Updates a contact. Every field is optional, and anything you leave out\nkeeps its current value.\n\nThis endpoint does not change which companies a contact is linked to.",
        "security": [
          {
            "oauth2": []
          },
          {
            "api_key": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Id of the contact to update.",
            "schema": {
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The contact was updated.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/Contact"
                    },
                    "meta": {
                      "$ref": "#/components/schemas/Meta"
                    }
                  },
                  "required": [
                    "data",
                    "meta"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "The request body is missing its `contact` wrapper.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "401": {
            "description": "The access token is missing, expired or invalid.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "403": {
            "description": "This token is not allowed to read or write contacts in your CRM.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "404": {
            "description": "No contact with that id in your account.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "422": {
            "description": "The email is malformed, or already belongs to another contact.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded. Wait at least `Retry-After` seconds before trying again.",
            "headers": {
              "Retry-After": {
                "schema": {
                  "type": "integer"
                },
                "description": "Seconds to wait before sending another request."
              },
              "RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "How many requests the exhausted window allows."
              },
              "RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left in the window. Always `0` on a throttled response."
              },
              "RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Seconds until the exhausted window resets."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        },
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "contact": {
                    "type": "object",
                    "properties": {
                      "name": {
                        "type": "string",
                        "description": "New full name. A contact must have a name, so `null` or an empty string is ignored rather than clearing it.",
                        "examples": [
                          "Alex Johnson"
                        ]
                      },
                      "email": {
                        "type": "string",
                        "description": "New email address. A contact must have an email, so `null` or an empty string is ignored rather than clearing it. Must stay unique among your contacts.",
                        "examples": [
                          "alex@example.com"
                        ]
                      },
                      "job_title": {
                        "type": [
                          "string",
                          "null"
                        ],
                        "description": "New job title. Send `null` or an empty string to clear it; leave the field out to keep it.",
                        "examples": [
                          "CTO"
                        ]
                      },
                      "phone_number": {
                        "type": [
                          "string",
                          "null"
                        ],
                        "description": "New phone number. Send `null` or an empty string to clear it; leave the field out to keep it.",
                        "examples": [
                          "+1-415-555-0100"
                        ]
                      }
                    }
                  }
                },
                "required": [
                  "contact"
                ]
              }
            }
          },
          "required": true,
          "description": "The attributes to change, wrapped in a `contact` key. All of them are optional."
        }
      }
    },
    "/public-api/v1/deals": {
      "get": {
        "summary": "List deals",
        "tags": [
          "Deals"
        ],
        "operationId": "listDeals",
        "description": "Lists the deals in your pipeline, a page at a time.\n\n## Filtering\n\n- `filter[title]` — searches deal titles, matching from the start of any\n  word and ignoring case.\n- `filter[deal_number]` — searches deal references the same way.\n- `filter[assignee_member_id]` — one or more team member ids, or `me` for\n  whoever the token belongs to.\n- `filter[board_group_id]` — one or more pipeline stage ids. Returns the\n  deals sitting in those stages.",
        "security": [
          {
            "oauth2": []
          },
          {
            "api_key": []
          }
        ],
        "parameters": [
          {
            "name": "page[number]",
            "in": "query",
            "required": false,
            "description": "Page to return, starting at 1.",
            "schema": {
              "type": "integer",
              "minimum": 1
            }
          },
          {
            "name": "page[size]",
            "in": "query",
            "required": false,
            "description": "Records per page. Defaults to 25; anything above 100 is reduced to 100.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100
            }
          },
          {
            "name": "filter[title]",
            "in": "query",
            "required": false,
            "description": "Searches deal titles, matching from the start of any word and ignoring case.",
            "schema": {
              "type": "string"
            },
            "examples": {
              "default": {
                "value": "Acme"
              }
            }
          },
          {
            "name": "filter[deal_number]",
            "in": "query",
            "required": false,
            "description": "Searches deal references, matching from the start of any word.",
            "schema": {
              "type": "string"
            },
            "examples": {
              "default": {
                "value": "DL-42"
              }
            }
          },
          {
            "name": "filter[assignee_member_id]",
            "in": "query",
            "required": false,
            "description": "One or more team member ids, comma-separated or repeated. Pass `me` for whoever the token belongs to.",
            "schema": {
              "type": "string"
            },
            "examples": {
              "default": {
                "value": "me"
              }
            }
          },
          {
            "name": "filter[board_group_id]",
            "in": "query",
            "required": false,
            "description": "One or more pipeline stage ids, comma-separated or repeated. Returns the deals sitting in those stages.",
            "schema": {
              "type": "string"
            },
            "examples": {
              "default": {
                "value": "8feddf83-f0dc-4587-ac77-3236d509a5c4"
              }
            }
          }
        ],
        "responses": {
          "200": {
            "description": "A page of deals.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Deal"
                      }
                    },
                    "meta": {
                      "$ref": "#/components/schemas/PaginatedMeta"
                    }
                  },
                  "required": [
                    "data",
                    "meta"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "A filter key is unknown, a filter value is not allowed, or a pagination parameter is not an integer.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "401": {
            "description": "The access token is missing, expired or invalid.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "403": {
            "description": "This token is not allowed to see deals.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded. Wait at least `Retry-After` seconds before trying again.",
            "headers": {
              "Retry-After": {
                "schema": {
                  "type": "integer"
                },
                "description": "Seconds to wait before sending another request."
              },
              "RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "How many requests the exhausted window allows."
              },
              "RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left in the window. Always `0` on a throttled response."
              },
              "RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Seconds until the exhausted window resets."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        }
      },
      "post": {
        "summary": "Create a deal",
        "tags": [
          "Deals"
        ],
        "operationId": "createDeal",
        "description": "Creates a deal. `title` and `company_id` are required — every deal is\nwith a client.\n\nA few things are filled in for you when you leave them out: the deal\nlands in the first stage of your deal board, and the client's primary\ncontact is attached to it. Pass `board_group_id` or `contact_id` to\nchoose instead.\n\nThe ids this endpoint takes come from other endpoints:\n`GET /public-api/v1/companies` for `company_id` and `contact_id`,\n`GET /public-api/v1/board_groups` for `board_group_id`, and\n`GET /public-api/v1/team_members` for `assignee_member_id`.",
        "security": [
          {
            "oauth2": []
          },
          {
            "api_key": []
          }
        ],
        "parameters": [],
        "responses": {
          "201": {
            "description": "The deal was created.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/Deal"
                    },
                    "meta": {
                      "$ref": "#/components/schemas/Meta"
                    }
                  },
                  "required": [
                    "data",
                    "meta"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "The request body is missing its `deal` wrapper.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "401": {
            "description": "The access token is missing, expired or invalid.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "403": {
            "description": "This token is not allowed to create deals.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "422": {
            "description": "The deal could not be saved. `blank` or `invalid_value` means a field was empty or the wrong shape; `not_accessible` means `company_id`, `contact_id`, `assignee_member_id` or `board_group_id` names something that is not in your account.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded. Wait at least `Retry-After` seconds before trying again.",
            "headers": {
              "Retry-After": {
                "schema": {
                  "type": "integer"
                },
                "description": "Seconds to wait before sending another request."
              },
              "RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "How many requests the exhausted window allows."
              },
              "RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left in the window. Always `0` on a throttled response."
              },
              "RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Seconds until the exhausted window resets."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        },
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "deal": {
                    "type": "object",
                    "properties": {
                      "title": {
                        "type": "string",
                        "description": "Deal title. Required.",
                        "examples": [
                          "Acme Website Redesign"
                        ]
                      },
                      "company_id": {
                        "type": "integer",
                        "description": "Id of the client company the deal is with. Required.",
                        "examples": [
                          42
                        ]
                      },
                      "contact_id": {
                        "type": [
                          "integer",
                          "null"
                        ],
                        "description": "Id of a contact at that company to attach to the deal. Defaults to the company's primary contact.",
                        "examples": [
                          7
                        ]
                      },
                      "assignee_member_id": {
                        "oneOf": [
                          {
                            "type": [
                              "integer",
                              "null"
                            ],
                            "description": "Team member id of the owner."
                          },
                          {
                            "type": "string",
                            "enum": [
                              "me"
                            ],
                            "description": "Assigns the deal to whoever the token belongs to."
                          }
                        ],
                        "description": "Who owns the deal: a team member id, or `me` for whoever the token belongs to. Send `null` or leave it out for an unassigned deal.",
                        "examples": [
                          "me"
                        ]
                      },
                      "board_group_id": {
                        "type": [
                          "string",
                          "null"
                        ],
                        "format": "uuid",
                        "description": "Id of the pipeline stage to put the deal in. Defaults to the first stage of your deal board.",
                        "examples": [
                          "8feddf83-f0dc-4587-ac77-3236d509a5c4"
                        ]
                      },
                      "deal_value": {
                        "type": [
                          "string",
                          "null"
                        ],
                        "description": "What the deal is worth, as a decimal string. Must be zero or greater.",
                        "examples": [
                          "5000.00"
                        ]
                      }
                    },
                    "required": [
                      "title",
                      "company_id"
                    ]
                  }
                },
                "required": [
                  "deal"
                ]
              }
            }
          },
          "required": true,
          "description": "Deal attributes, wrapped in a `deal` key."
        }
      }
    },
    "/public-api/v1/deals/{id}": {
      "patch": {
        "summary": "Update a deal",
        "tags": [
          "Deals"
        ],
        "operationId": "updateDeal",
        "description": "Updates a deal. Every field is optional, and anything you leave out keeps\nits current value.\n\nSend `board_group_id` to move the deal to another stage of your pipeline —\nthat is how you advance a deal, mark it won, or mark it lost.\n\nChanging `company_id` moves the deal to a different client and re-points\nits contact to that client's primary contact. To choose the contacts\nyourself, send `contact_ids` as well: it replaces the deal's contacts\nwith exactly the ids you give, and wins over the automatic re-point.",
        "security": [
          {
            "oauth2": []
          },
          {
            "api_key": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Id of the deal to update.",
            "schema": {
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The deal was updated.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/Deal"
                    },
                    "meta": {
                      "$ref": "#/components/schemas/Meta"
                    }
                  },
                  "required": [
                    "data",
                    "meta"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "The request body is missing its `deal` wrapper.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "401": {
            "description": "The access token is missing, expired or invalid.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "403": {
            "description": "This token can see the deal but is not allowed to change it.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "404": {
            "description": "No deal with that id in your account.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "422": {
            "description": "The deal could not be saved. `invalid_value` means a field was the wrong shape or out of range; `not_accessible` means `company_id`, `contact_ids`, `assignee_member_id` or `board_group_id` names something that is not in your account.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded. Wait at least `Retry-After` seconds before trying again.",
            "headers": {
              "Retry-After": {
                "schema": {
                  "type": "integer"
                },
                "description": "Seconds to wait before sending another request."
              },
              "RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "How many requests the exhausted window allows."
              },
              "RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left in the window. Always `0` on a throttled response."
              },
              "RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Seconds until the exhausted window resets."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        },
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "deal": {
                    "type": "object",
                    "properties": {
                      "title": {
                        "type": "string",
                        "description": "New deal title.",
                        "examples": [
                          "Acme Website Redesign"
                        ]
                      },
                      "company_id": {
                        "type": [
                          "integer",
                          "null"
                        ],
                        "description": "Id of the client company the deal is with. Find one with `GET /public-api/v1/companies`.",
                        "examples": [
                          42
                        ]
                      },
                      "contact_ids": {
                        "type": "array",
                        "items": {
                          "type": "integer"
                        },
                        "description": "The contacts on the deal, replacing whatever is there now. Send an empty array to remove them all. Leave the field out to keep the deal's current contacts.",
                        "examples": [
                          [
                            7,
                            12
                          ]
                        ]
                      },
                      "assignee_member_id": {
                        "oneOf": [
                          {
                            "type": [
                              "integer",
                              "null"
                            ],
                            "description": "Team member id of the owner."
                          },
                          {
                            "type": "string",
                            "enum": [
                              "me"
                            ],
                            "description": "Assigns the deal to whoever the token belongs to."
                          }
                        ],
                        "description": "Who owns the deal: a team member id, or `me` for whoever the token belongs to. Send `null` to leave it unassigned.",
                        "examples": [
                          "me"
                        ]
                      },
                      "board_group_id": {
                        "type": [
                          "string",
                          "null"
                        ],
                        "format": "uuid",
                        "description": "Id of the pipeline stage to move the deal to. Find one with `GET /public-api/v1/board_groups`.",
                        "examples": [
                          "8feddf83-f0dc-4587-ac77-3236d509a5c4"
                        ]
                      },
                      "deal_value": {
                        "type": [
                          "string",
                          "null"
                        ],
                        "description": "What the deal is worth, as a decimal string. Must be zero or greater. `null` is ignored rather than clearing the value.",
                        "examples": [
                          "7500.00"
                        ]
                      },
                      "probability": {
                        "type": [
                          "integer",
                          "null"
                        ],
                        "minimum": 0,
                        "maximum": 100,
                        "description": "Chance of winning the deal, as a whole percentage from 0 to 100. `null` is ignored rather than clearing the value.",
                        "examples": [
                          60
                        ]
                      },
                      "close_date": {
                        "type": [
                          "string",
                          "null"
                        ],
                        "format": "date",
                        "description": "Date the deal is expected to close. Send `null` to clear it.",
                        "examples": [
                          "2026-09-30"
                        ]
                      }
                    }
                  }
                },
                "required": [
                  "deal"
                ]
              }
            }
          },
          "required": true,
          "description": "The attributes to change, wrapped in a `deal` key. All of them are optional."
        }
      }
    },
    "/public-api/v1/invoices/{invoice_id}/invoice_items": {
      "parameters": [
        {
          "name": "invoice_id",
          "in": "path",
          "required": true,
          "description": "Id of the invoice the line item belongs to.",
          "schema": {
            "type": "integer"
          }
        }
      ],
      "post": {
        "summary": "Add a line item to an invoice",
        "tags": [
          "Invoices"
        ],
        "operationId": "createInvoiceItem",
        "description": "Adds one line item to an invoice and recalculates its total.\n\nOnly invoices that have not been paid for work here. An invoice that is\npaid, partially paid, or has a payment in progress is closed to changes\nand returns `403`.",
        "security": [
          {
            "oauth2": []
          },
          {
            "api_key": []
          }
        ],
        "parameters": [],
        "responses": {
          "201": {
            "description": "The line item was added and the invoice total recalculated.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/InvoiceItem"
                    },
                    "meta": {
                      "$ref": "#/components/schemas/Meta"
                    }
                  },
                  "required": [
                    "data",
                    "meta"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "The request body is missing its `invoice_item` wrapper.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "401": {
            "description": "The access token is missing, expired or invalid.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "403": {
            "description": "This token is not allowed to edit invoices, or the invoice is paid, partially paid, or has a payment in progress and can no longer be changed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "404": {
            "description": "No invoice with that id that this token can see.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "422": {
            "description": "The line item could not be saved — `name`, `amount` or `rate` is missing, `unit_type` is not one of the listed values, or `amount` or `rate` is negative.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded. Wait at least `Retry-After` seconds before trying again.",
            "headers": {
              "Retry-After": {
                "schema": {
                  "type": "integer"
                },
                "description": "Seconds to wait before sending another request."
              },
              "RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "How many requests the exhausted window allows."
              },
              "RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left in the window. Always `0` on a throttled response."
              },
              "RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Seconds until the exhausted window resets."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        },
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "invoice_item": {
                    "type": "object",
                    "required": [
                      "name",
                      "amount",
                      "rate"
                    ],
                    "properties": {
                      "name": {
                        "type": "string",
                        "description": "Line item name, as the client will see it. Required.",
                        "examples": [
                          "Consulting"
                        ]
                      },
                      "description": {
                        "type": "string",
                        "description": "Longer description shown beneath the name.",
                        "examples": [
                          "Discovery workshops"
                        ]
                      },
                      "amount": {
                        "type": "string",
                        "description": "Quantity billed, as a decimal string. Required, and cannot be negative.",
                        "examples": [
                          "3"
                        ]
                      },
                      "rate": {
                        "type": "string",
                        "description": "Price per unit, as a decimal string. Required, and cannot be negative.",
                        "examples": [
                          "100"
                        ]
                      },
                      "unit_type": {
                        "type": "string",
                        "enum": [
                          "flat",
                          "per_hour",
                          "per_day",
                          "per_item",
                          "per_word",
                          "per_week",
                          "per_month",
                          "per_year",
                          "per_quarter"
                        ],
                        "description": "What the rate is charged per. Defaults to `flat`."
                      }
                    }
                  }
                },
                "required": [
                  "invoice_item"
                ]
              }
            }
          },
          "required": true,
          "description": "The line item, wrapped in an `invoice_item` key."
        }
      }
    },
    "/public-api/v1/invoices/{invoice_id}/invoice_items/{id}": {
      "parameters": [
        {
          "name": "invoice_id",
          "in": "path",
          "required": true,
          "description": "Id of the invoice the line item belongs to.",
          "schema": {
            "type": "integer"
          }
        },
        {
          "name": "id",
          "in": "path",
          "required": true,
          "description": "Id of the line item.",
          "schema": {
            "type": "integer"
          }
        }
      ],
      "patch": {
        "summary": "Update a line item on an invoice",
        "tags": [
          "Invoices"
        ],
        "operationId": "updateInvoiceItem",
        "description": "Updates one line item and recalculates the invoice total. Every field is\noptional, and anything you leave out keeps its current value.\n\nOnly invoices that have not been paid for work here. An invoice that is\npaid, partially paid, or has a payment in progress is closed to changes\nand returns `403`.\n\nA line item built from a timesheet takes its `amount`, `rate` and\n`unit_type` from the time entries behind it. Sending a different value\nfor one of those returns `422`. Sending the stored value is accepted, so\na full-representation update still works.",
        "security": [
          {
            "oauth2": []
          },
          {
            "api_key": []
          }
        ],
        "parameters": [],
        "responses": {
          "200": {
            "description": "The line item was updated and the invoice total recalculated.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/InvoiceItem"
                    },
                    "meta": {
                      "$ref": "#/components/schemas/Meta"
                    }
                  },
                  "required": [
                    "data",
                    "meta"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "The request body is missing its `invoice_item` wrapper.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "401": {
            "description": "The access token is missing, expired or invalid.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "403": {
            "description": "This token is not allowed to edit invoices, or the invoice is paid, partially paid, or has a payment in progress and can no longer be changed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "404": {
            "description": "No such invoice or line item that this token can see.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "422": {
            "description": "The line item could not be saved — `name` is blank, `unit_type` is not one of the listed values, or `amount` or `rate` is negative.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded. Wait at least `Retry-After` seconds before trying again.",
            "headers": {
              "Retry-After": {
                "schema": {
                  "type": "integer"
                },
                "description": "Seconds to wait before sending another request."
              },
              "RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "How many requests the exhausted window allows."
              },
              "RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left in the window. Always `0` on a throttled response."
              },
              "RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Seconds until the exhausted window resets."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        },
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "invoice_item": {
                    "type": "object",
                    "properties": {
                      "name": {
                        "type": "string",
                        "description": "Line item name, as the client will see it. Cannot be blank.",
                        "examples": [
                          "Consulting"
                        ]
                      },
                      "description": {
                        "type": "string",
                        "description": "Longer description shown beneath the name.",
                        "examples": [
                          "Discovery workshops"
                        ]
                      },
                      "amount": {
                        "type": "string",
                        "description": "Quantity billed, as a decimal string. Cannot be negative.",
                        "examples": [
                          "3"
                        ]
                      },
                      "rate": {
                        "type": "string",
                        "description": "Price per unit, as a decimal string. Cannot be negative.",
                        "examples": [
                          "100"
                        ]
                      },
                      "unit_type": {
                        "type": "string",
                        "enum": [
                          "flat",
                          "per_hour",
                          "per_day",
                          "per_item",
                          "per_word",
                          "per_week",
                          "per_month",
                          "per_year",
                          "per_quarter"
                        ],
                        "description": "What the rate is charged per."
                      }
                    }
                  }
                },
                "required": [
                  "invoice_item"
                ]
              }
            }
          },
          "required": true,
          "description": "The fields to change, wrapped in an `invoice_item` key. All of them are optional."
        }
      },
      "delete": {
        "summary": "Remove a line item from an invoice",
        "tags": [
          "Invoices"
        ],
        "operationId": "deleteInvoiceItem",
        "description": "Removes one line item and recalculates the invoice total. The response\nhands back the item as it was, so you can log or reinstate it.\n\nAny time entries that were billed through this line item go back to\nunbilled and can be invoiced again.\n\nOnly invoices that have not been paid for work here. An invoice that is\npaid, partially paid, or has a payment in progress is closed to changes\nand returns `403`.",
        "security": [
          {
            "oauth2": []
          },
          {
            "api_key": []
          }
        ],
        "responses": {
          "200": {
            "description": "The line item was removed. Its final state is in `data`.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/InvoiceItem"
                    },
                    "meta": {
                      "$ref": "#/components/schemas/Meta"
                    }
                  },
                  "required": [
                    "data",
                    "meta"
                  ]
                }
              }
            }
          },
          "401": {
            "description": "The access token is missing, expired or invalid.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "403": {
            "description": "This token is not allowed to edit invoices, or the invoice is paid, partially paid, or has a payment in progress and can no longer be changed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "404": {
            "description": "No such invoice or line item that this token can see.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded. Wait at least `Retry-After` seconds before trying again.",
            "headers": {
              "Retry-After": {
                "schema": {
                  "type": "integer"
                },
                "description": "Seconds to wait before sending another request."
              },
              "RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "How many requests the exhausted window allows."
              },
              "RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left in the window. Always `0` on a throttled response."
              },
              "RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Seconds until the exhausted window resets."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        }
      }
    },
    "/public-api/v1/invoices": {
      "get": {
        "summary": "List invoices",
        "tags": [
          "Invoices"
        ],
        "operationId": "listInvoices",
        "description": "Lists your invoices, newest first, a page at a time. Each one arrives with\nits line items.\n\n## Filtering\n\n- `filter[invoice_number]` — one or more invoice numbers. This is how you\n  turn a number your client quoted you into an invoice id.\n- `filter[project_id]` — one or more project ids.\n- `filter[company_id]` — one or more client company ids.",
        "security": [
          {
            "oauth2": []
          },
          {
            "api_key": []
          }
        ],
        "parameters": [
          {
            "name": "page[number]",
            "in": "query",
            "required": false,
            "description": "Page to return, starting at 1.",
            "schema": {
              "type": "integer",
              "minimum": 1
            }
          },
          {
            "name": "page[size]",
            "in": "query",
            "required": false,
            "description": "Records per page. Defaults to 25; anything above 100 is reduced to 100.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100
            }
          },
          {
            "name": "filter[invoice_number]",
            "in": "query",
            "required": false,
            "description": "One or more exact invoice numbers, comma-separated or repeated.",
            "schema": {
              "type": "string"
            },
            "examples": {
              "default": {
                "value": "INV-001"
              }
            }
          },
          {
            "name": "filter[project_id]",
            "in": "query",
            "required": false,
            "description": "One or more project ids, comma-separated or repeated.",
            "schema": {
              "type": "string"
            },
            "examples": {
              "default": {
                "value": "51"
              }
            }
          },
          {
            "name": "filter[company_id]",
            "in": "query",
            "required": false,
            "description": "One or more client company ids, comma-separated or repeated. Find them with `GET /public-api/v1/companies`.",
            "schema": {
              "type": "string"
            },
            "examples": {
              "default": {
                "value": "42"
              }
            }
          }
        ],
        "responses": {
          "200": {
            "description": "A page of invoices, newest first, each with its line items.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Invoice"
                      }
                    },
                    "meta": {
                      "$ref": "#/components/schemas/PaginatedMeta"
                    }
                  },
                  "required": [
                    "data",
                    "meta"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "A filter key is unknown, an id filter is not an integer, or a pagination parameter is not an integer.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "401": {
            "description": "The access token is missing, expired or invalid.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "403": {
            "description": "This token is not allowed to see invoices.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded. Wait at least `Retry-After` seconds before trying again.",
            "headers": {
              "Retry-After": {
                "schema": {
                  "type": "integer"
                },
                "description": "Seconds to wait before sending another request."
              },
              "RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "How many requests the exhausted window allows."
              },
              "RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left in the window. Always `0` on a throttled response."
              },
              "RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Seconds until the exhausted window resets."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        }
      },
      "post": {
        "summary": "Create an invoice",
        "tags": [
          "Invoices"
        ],
        "operationId": "createInvoice",
        "description": "Creates an invoice, with its line items inline if you have them.\n`company_id`, `contact_id` and `project_id` are all required — an invoice\nis always for one client, addressed to one person, against one project.\n\n`kind` decides whether this is a one-off, a recurring invoice or a\nsubscription. A recurring invoice starts reissuing itself shortly after\nit is created, so send `repeat_finish_date` if the series has an end.\n\nThe project has to exist already and belong to the same client as\n`company_id`. This endpoint never creates one; use\n`POST /public-api/v1/projects` first if you need to.\n\nThe invoice is created as a draft. Nothing is sent to your client here —\nsend it from Bonsai when you are ready.\n\nReferencing a company, contact or project that is not in your account\nfails with `422 not_accessible` rather than `404`: the invoice is what you\naddressed, and the ids inside it are attributes of that request.",
        "security": [
          {
            "oauth2": []
          },
          {
            "api_key": []
          }
        ],
        "parameters": [],
        "responses": {
          "201": {
            "description": "The invoice was created as a draft.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/Invoice"
                    },
                    "meta": {
                      "$ref": "#/components/schemas/Meta"
                    }
                  },
                  "required": [
                    "data",
                    "meta"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "The request body is missing its `invoice` wrapper.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "401": {
            "description": "The access token is missing, expired or invalid.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "403": {
            "description": "This token is not allowed to create invoices, or it can only invoice projects it is assigned to and `project_id` is not one of them.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "422": {
            "description": "The invoice could not be created. `blank` means `company_id`, `contact_id` or `project_id` was missing, or `repeat_interval` was missing on a recurring invoice; `invalid_value` means `due` and `due_date` disagree, `kind` is not one you can create, a recurrence field was sent on a `single` invoice, `repeat_finish_date` falls before the first repeat, or a line item is malformed; `not_accessible` means one of those ids is not in your account, or the project belongs to a different client.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded. Wait at least `Retry-After` seconds before trying again.",
            "headers": {
              "Retry-After": {
                "schema": {
                  "type": "integer"
                },
                "description": "Seconds to wait before sending another request."
              },
              "RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "How many requests the exhausted window allows."
              },
              "RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left in the window. Always `0` on a throttled response."
              },
              "RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Seconds until the exhausted window resets."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        },
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "invoice": {
                    "type": "object",
                    "required": [
                      "company_id",
                      "contact_id",
                      "project_id"
                    ],
                    "properties": {
                      "company_id": {
                        "type": "integer",
                        "description": "Id of the client company to invoice. Required. Must be a company in your account.",
                        "examples": [
                          42
                        ]
                      },
                      "contact_id": {
                        "type": "integer",
                        "description": "Id of the contact at that company to address the invoice to. Required. Must be a contact in your account.",
                        "examples": [
                          7
                        ]
                      },
                      "project_id": {
                        "type": "integer",
                        "description": "Id of the project to invoice. Required, and must already exist and belong to the same client as `company_id`.",
                        "examples": [
                          51
                        ]
                      },
                      "currency": {
                        "type": "string",
                        "description": "Three-letter ISO 4217 currency code. Defaults to the project's currency; send it to override.",
                        "examples": [
                          "USD"
                        ]
                      },
                      "title": {
                        "type": "string",
                        "description": "Invoice title shown to the client. Defaults to the client name followed by \"Invoice\".",
                        "examples": [
                          "Acme Corp Invoice"
                        ]
                      },
                      "due": {
                        "type": "string",
                        "enum": [
                          "upon_receipt",
                          "custom"
                        ],
                        "description": "When payment is due. Leave it out to use your account's default payment terms, which may work out to a number of days after issue."
                      },
                      "due_date": {
                        "type": "string",
                        "format": "date",
                        "description": "The exact date payment is due. Required when `due` is `custom`, and must be left out when `due` is `upon_receipt`.",
                        "examples": [
                          "2026-04-28"
                        ]
                      },
                      "kind": {
                        "type": "string",
                        "enum": [
                          "single",
                          "recurring",
                          "subscription"
                        ],
                        "description": "What kind of invoice to create. Defaults to `single`. `recurring` reissues the invoice on `repeat_interval`; `subscription` does the same and charges the card your client has on file. Bundled invoices are assembled in the Bonsai app from invoices that already exist, so `bundled` is not accepted here."
                      },
                      "repeat_interval": {
                        "type": "string",
                        "enum": [
                          "weekly",
                          "every two weeks",
                          "every four weeks",
                          "monthly",
                          "every two months",
                          "quarterly",
                          "every six months",
                          "annually"
                        ],
                        "description": "How often to reissue the invoice. Required for `recurring` and `subscription`, and must be left out for `single`."
                      },
                      "repeat_finish_date": {
                        "type": "string",
                        "format": "date",
                        "description": "Date to stop reissuing the invoice. Leave it out to keep the series running until you stop it. Must be left out for `single`, and must be no earlier than the first repeat — one `repeat_interval` after the issue date — since a series that ends before then would never reissue at all.",
                        "examples": [
                          "2027-04-28"
                        ]
                      },
                      "invoice_items": {
                        "type": "array",
                        "description": "Line items to put on the invoice. Leave it out for an empty invoice you fill in later, and add lines with the line-item endpoint.",
                        "items": {
                          "type": "object",
                          "required": [
                            "name",
                            "amount",
                            "rate"
                          ],
                          "properties": {
                            "name": {
                              "type": "string",
                              "description": "Line item name, as the client will see it. Required.",
                              "examples": [
                                "Design"
                              ]
                            },
                            "description": {
                              "type": "string",
                              "description": "Longer description shown beneath the name.",
                              "examples": [
                                "Homepage and pricing page"
                              ]
                            },
                            "amount": {
                              "type": "string",
                              "description": "Quantity billed, as a decimal string. Required, and cannot be negative.",
                              "examples": [
                                "2"
                              ]
                            },
                            "rate": {
                              "type": "string",
                              "description": "Price per unit, as a decimal string. Required, and cannot be negative.",
                              "examples": [
                                "100"
                              ]
                            },
                            "unit_type": {
                              "type": "string",
                              "enum": [
                                "flat",
                                "per_hour",
                                "per_day",
                                "per_item",
                                "per_word",
                                "per_week",
                                "per_month",
                                "per_year",
                                "per_quarter"
                              ],
                              "description": "What the rate is charged per. Defaults to `flat`."
                            }
                          }
                        }
                      }
                    }
                  }
                },
                "required": [
                  "invoice"
                ]
              }
            }
          },
          "required": true,
          "description": "Invoice attributes, wrapped in an `invoice` key."
        }
      }
    },
    "/public-api/v1/invoices/{id}": {
      "patch": {
        "summary": "Update an invoice",
        "tags": [
          "Invoices"
        ],
        "operationId": "updateInvoice",
        "description": "Updates an invoice. Every field is optional, and anything you leave out\nkeeps its current value.\n\nOnly four things can change here: who the invoice is addressed to, its\ntitle, its currency and when it is due. The client company and the project\nare fixed once the invoice exists, and line items have their own\nendpoints.\n\nAn invoice that is `paid` or has a payment in progress cannot be edited at\nall, and returns `403`.",
        "security": [
          {
            "oauth2": []
          },
          {
            "api_key": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Id of the invoice to update.",
            "schema": {
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The invoice was updated.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/Invoice"
                    },
                    "meta": {
                      "$ref": "#/components/schemas/Meta"
                    }
                  },
                  "required": [
                    "data",
                    "meta"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "The request body is missing its `invoice` wrapper.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "401": {
            "description": "The access token is missing, expired or invalid.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "403": {
            "description": "This token is not allowed to edit invoices, or the invoice is paid or has a payment in progress and can no longer be changed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "404": {
            "description": "No invoice with that id that this token can see.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "422": {
            "description": "`due` and `due_date` disagree, or `contact_id` is not a contact at the invoice's client company.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded. Wait at least `Retry-After` seconds before trying again.",
            "headers": {
              "Retry-After": {
                "schema": {
                  "type": "integer"
                },
                "description": "Seconds to wait before sending another request."
              },
              "RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "How many requests the exhausted window allows."
              },
              "RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left in the window. Always `0` on a throttled response."
              },
              "RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Seconds until the exhausted window resets."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        },
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "invoice": {
                    "type": "object",
                    "properties": {
                      "contact_id": {
                        "type": "integer",
                        "description": "Id of the contact to address the invoice to, which also refreshes the client name and email printed on it. Must be a contact at the invoice's existing client company.",
                        "examples": [
                          7
                        ]
                      },
                      "currency": {
                        "type": "string",
                        "description": "Three-letter ISO 4217 currency code.",
                        "examples": [
                          "USD"
                        ]
                      },
                      "title": {
                        "type": "string",
                        "description": "Invoice title shown to the client.",
                        "examples": [
                          "Acme Corp Invoice"
                        ]
                      },
                      "due": {
                        "type": "string",
                        "enum": [
                          "upon_receipt",
                          "custom"
                        ],
                        "description": "When payment is due."
                      },
                      "due_date": {
                        "type": "string",
                        "format": "date",
                        "description": "The exact date payment is due. Required when `due` is `custom`, and must be left out when `due` is `upon_receipt`. Sending it on its own is treated as `custom`.",
                        "examples": [
                          "2026-04-28"
                        ]
                      }
                    }
                  }
                },
                "required": [
                  "invoice"
                ]
              }
            }
          },
          "required": true,
          "description": "The attributes to change, wrapped in an `invoice` key. All of them are optional."
        }
      }
    },
    "/public-api/v1/notes/{note_id}/links": {
      "parameters": [
        {
          "name": "note_id",
          "in": "path",
          "required": true,
          "description": "The note's `id`.",
          "schema": {
            "type": "string",
            "format": "uuid"
          }
        }
      ],
      "post": {
        "summary": "Attach a record to a note",
        "tags": [
          "Notes"
        ],
        "operationId": "createNoteLink",
        "description": "Attaches a note you have already written to a client company, a project\nor a contact. Find the id with `GET /public-api/v1/companies`,\n`GET /public-api/v1/projects` or `GET /public-api/v1/contacts`.\n\nAttaching is idempotent. Posting a pair the note already carries returns\n`201` with the attachment that was there, and writes nothing — so a\nretry after a timeout is safe and there is no duplicate to handle.\n\nA note holds at most 10 records. Once it is full\nany further record returns `422` pointed at the body rather than at\n`record_id`, because a different id would fail the same way.\n\nAttaching a record takes the same permission as editing the note, so\nthe person who wrote a note can always attach to it. `data.id` is what\naddresses the attachment if you later want to remove it.",
        "security": [
          {
            "oauth2": []
          },
          {
            "api_key": []
          }
        ],
        "parameters": [],
        "responses": {
          "201": {
            "description": "The record is attached. `data` carries the attachment, whether this call created it or it was already there.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/NoteLink"
                    },
                    "meta": {
                      "$ref": "#/components/schemas/Meta"
                    }
                  },
                  "required": [
                    "data",
                    "meta"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "The request body is missing its `link` wrapper.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "401": {
            "description": "The access token is missing, expired or invalid.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "403": {
            "description": "This token can see the note but is not allowed to edit it.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "404": {
            "description": "No note with that `note_id` that this token can see.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "422": {
            "description": "The attachment could not be made. `blank` means `record_type` or `record_id` was missing; `invalid_value` means `record_type` is not one of the published names, `record_id` is not a whole number, or the note is already full; `not_accessible` means `record_id` is not a record you can reach.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded. Wait at least `Retry-After` seconds before trying again.",
            "headers": {
              "Retry-After": {
                "schema": {
                  "type": "integer"
                },
                "description": "Seconds to wait before sending another request."
              },
              "RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "How many requests the exhausted window allows."
              },
              "RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left in the window. Always `0` on a throttled response."
              },
              "RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Seconds until the exhausted window resets."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        },
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "link": {
                    "type": "object",
                    "properties": {
                      "record_type": {
                        "type": "string",
                        "enum": [
                          "company",
                          "project",
                          "contact"
                        ],
                        "description": "Which kind of record to attach the note to.",
                        "examples": [
                          "project"
                        ]
                      },
                      "record_id": {
                        "type": "integer",
                        "description": "Id of the record to attach, in the resource named by `record_type`.",
                        "examples": [
                          42
                        ]
                      }
                    },
                    "required": [
                      "record_type",
                      "record_id"
                    ]
                  }
                },
                "required": [
                  "link"
                ]
              }
            }
          },
          "required": true,
          "description": "The record to attach, wrapped in a `link` key."
        }
      }
    },
    "/public-api/v1/notes/{note_id}/links/{id}": {
      "parameters": [
        {
          "name": "note_id",
          "in": "path",
          "required": true,
          "description": "The note's `id`.",
          "schema": {
            "type": "string",
            "format": "uuid"
          }
        },
        {
          "name": "id",
          "in": "path",
          "required": true,
          "description": "The attachment's `id`, as returned in `note_links` on any note response.",
          "schema": {
            "type": "integer"
          }
        }
      ],
      "delete": {
        "summary": "Detach a record from a note",
        "tags": [
          "Notes"
        ],
        "operationId": "deleteNoteLink",
        "description": "Detaches one record from a note. The response hands back the attachment\nas it was, so you can log exactly what was removed.\n\nUnlike attaching, this is not idempotent: the attachment is gone for\ngood, so a repeat call returns `404`. A retry after a timeout will hit\nthat rather than succeeding twice.\n\nThe note itself is untouched. Detaching its last record leaves it in the\nworkspace as a note without records, rather than deleting it.\n\nDetaching takes the same permission as editing the note, so the person\nwho wrote a note can always detach from it.",
        "security": [
          {
            "oauth2": []
          },
          {
            "api_key": []
          }
        ],
        "responses": {
          "200": {
            "description": "The record was detached. The attachment as it was is in `data`.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/NoteLink"
                    },
                    "meta": {
                      "$ref": "#/components/schemas/Meta"
                    }
                  },
                  "required": [
                    "data",
                    "meta"
                  ]
                }
              }
            }
          },
          "401": {
            "description": "The access token is missing, expired or invalid.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "403": {
            "description": "This token can see the note but is not allowed to edit it.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "404": {
            "description": "No such note or attachment that this token can see. An attachment already detached returns `404` too.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded. Wait at least `Retry-After` seconds before trying again.",
            "headers": {
              "Retry-After": {
                "schema": {
                  "type": "integer"
                },
                "description": "Seconds to wait before sending another request."
              },
              "RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "How many requests the exhausted window allows."
              },
              "RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left in the window. Always `0` on a throttled response."
              },
              "RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Seconds until the exhausted window resets."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        }
      }
    },
    "/public-api/v1/notes": {
      "get": {
        "summary": "List notes",
        "tags": [
          "Notes"
        ],
        "operationId": "listNotes",
        "description": "Lists notes, newest first, a page at a time. The note body is not part\nof a list response.\n\nEach note carries the records it is attached to in `note_links`, leaving\nout any record the token cannot reach — so a note can come back with an\nempty `note_links` even though somebody else can see what it is about.\n\n## Filtering\n\n- `filter[record_type]` and `filter[record_id]` — the record the notes\n  are attached to. Both are required together; neither narrows anything\n  on its own.\n- `filter[created_by_member_id]` — one or more team member ids, or `me`\n  for whoever the token belongs to.\n- `filter[date_from]` and `filter[date_to]` — a date range over the day\n  each note is about. Both bounds are inclusive, and either works alone.",
        "security": [
          {
            "oauth2": []
          },
          {
            "api_key": []
          }
        ],
        "parameters": [
          {
            "name": "page[number]",
            "in": "query",
            "required": false,
            "description": "Page to return, starting at 1.",
            "schema": {
              "type": "integer",
              "minimum": 1
            }
          },
          {
            "name": "page[size]",
            "in": "query",
            "required": false,
            "description": "Records per page. Defaults to 25; anything above 100 is reduced to 100.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100
            }
          },
          {
            "name": "filter[record_type]",
            "in": "query",
            "required": false,
            "description": "Return notes attached to this kind of record. Requires `filter[record_id]`.",
            "schema": {
              "type": "string",
              "enum": [
                "company",
                "project",
                "contact"
              ]
            },
            "examples": {
              "default": {
                "value": "company"
              }
            }
          },
          {
            "name": "filter[record_id]",
            "in": "query",
            "required": false,
            "description": "Return notes attached to this record, in the resource named by `filter[record_type]`. Requires `filter[record_type]`.",
            "schema": {
              "type": "string"
            },
            "examples": {
              "default": {
                "value": "42"
              }
            }
          },
          {
            "name": "filter[created_by_member_id]",
            "in": "query",
            "required": false,
            "description": "One or more team member ids, comma-separated or repeated. Pass `me` for whoever the token belongs to.",
            "schema": {
              "type": "string"
            },
            "examples": {
              "default": {
                "value": "me"
              }
            }
          },
          {
            "name": "filter[date_from]",
            "in": "query",
            "required": false,
            "description": "Return notes dated on or after this day.",
            "schema": {
              "type": "string",
              "format": "date"
            },
            "examples": {
              "default": {
                "value": "2026-05-01"
              }
            }
          },
          {
            "name": "filter[date_to]",
            "in": "query",
            "required": false,
            "description": "Return notes dated on or before this day.",
            "schema": {
              "type": "string",
              "format": "date"
            },
            "examples": {
              "default": {
                "value": "2026-05-31"
              }
            }
          }
        ],
        "responses": {
          "200": {
            "description": "A page of notes, newest first.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Note"
                      }
                    },
                    "meta": {
                      "$ref": "#/components/schemas/PaginatedMeta"
                    }
                  },
                  "required": [
                    "data",
                    "meta"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "A filter key is unknown, a filter value is not allowed, or a pagination parameter is not an integer.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "401": {
            "description": "The access token is missing, expired or invalid.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "403": {
            "description": "This token is not allowed to read notes.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded. Wait at least `Retry-After` seconds before trying again.",
            "headers": {
              "Retry-After": {
                "schema": {
                  "type": "integer"
                },
                "description": "Seconds to wait before sending another request."
              },
              "RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "How many requests the exhausted window allows."
              },
              "RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left in the window. Always `0` on a throttled response."
              },
              "RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Seconds until the exhausted window resets."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        }
      },
      "post": {
        "summary": "Create a note",
        "tags": [
          "Notes"
        ],
        "operationId": "createNote",
        "description": "Writes a note. `content` is the only required field.\n\nPass `record_type` and `record_id` together to attach the note to a\nclient company, a project or a contact — find the id with\n`GET /public-api/v1/companies`, `GET /public-api/v1/projects` or\n`GET /public-api/v1/contacts`. Leave both out and the note stands on\nits own in the workspace.\n\n## Formatting\n\n`content` is Markdown. Bonsai converts it to the rich text the notes\neditor renders, so the constructs below all survive the conversion:\n\n- headings, `**bold**`, `*italic*`, `~~strikethrough~~`\n- bullet, numbered and `- [ ]` task lists\n- links, `> quotes`, `---` dividers, tables\n- `inline code` and fenced code blocks\n\nA single newline is a soft break and joins lines into one paragraph, as\nin standard Markdown — leave a blank line between paragraphs. Anything\noutside the list above, including raw HTML and images, is reduced to\nits text.\n\nThe response returns the stored body as `content_html`, not as the\nMarkdown you sent. To edit this note afterwards, read `content_html`\nand send the modified HTML back to\n`PATCH /public-api/v1/notes/{id}` — that endpoint takes HTML, not\nMarkdown, so nothing is lost on the way through.\n\nThat endpoint accepts a wider set of markup than this one can produce:\nimages and mention spans survive an update but cannot be written here,\nsince the Markdown converter reduces them to their text.",
        "security": [
          {
            "oauth2": []
          },
          {
            "api_key": []
          }
        ],
        "parameters": [],
        "responses": {
          "201": {
            "description": "The note was created.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "allOf": [
                        {
                          "$ref": "#/components/schemas/Note"
                        },
                        {
                          "$ref": "#/components/schemas/NoteDetailedTrait"
                        }
                      ]
                    },
                    "meta": {
                      "$ref": "#/components/schemas/Meta"
                    }
                  },
                  "required": [
                    "data",
                    "meta"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "The request body is missing its `note` wrapper.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "401": {
            "description": "The access token is missing, expired or invalid.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "403": {
            "description": "This token is not allowed to write notes.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "422": {
            "description": "The note could not be saved. `blank` means `content` was empty, or `record_type` and `record_id` were not supplied together; `invalid_value` means a field was the wrong shape; `not_accessible` means `record_id` is not a record you can reach.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded. Wait at least `Retry-After` seconds before trying again.",
            "headers": {
              "Retry-After": {
                "schema": {
                  "type": "integer"
                },
                "description": "Seconds to wait before sending another request."
              },
              "RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "How many requests the exhausted window allows."
              },
              "RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left in the window. Always `0` on a throttled response."
              },
              "RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Seconds until the exhausted window resets."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        },
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "note": {
                    "type": "object",
                    "properties": {
                      "content": {
                        "type": "string",
                        "description": "The note body, in Markdown. Required.",
                        "examples": [
                          "## Kickoff call\n\n- Agreed on a May launch\n- **Next:** send the revised quote"
                        ]
                      },
                      "title": {
                        "type": [
                          "string",
                          "null"
                        ],
                        "description": "Note title. Defaults to empty.",
                        "examples": [
                          "Kickoff call"
                        ]
                      },
                      "date": {
                        "type": [
                          "string",
                          "null"
                        ],
                        "format": "date",
                        "description": "The day the note is about. Defaults to today in your account timezone.",
                        "examples": [
                          "2026-05-01"
                        ]
                      },
                      "visibility": {
                        "type": [
                          "string",
                          "null"
                        ],
                        "enum": [
                          "everyone",
                          "author_only",
                          null
                        ],
                        "description": "Who can read the note: `everyone` for the whole account, `author_only` for nobody but you. Defaults to `everyone`.",
                        "examples": [
                          "everyone"
                        ]
                      },
                      "record_type": {
                        "type": [
                          "string",
                          "null"
                        ],
                        "enum": [
                          "company",
                          "project",
                          "contact",
                          null
                        ],
                        "description": "What to attach the note to. Requires `record_id`. Omit both for a workspace note.",
                        "examples": [
                          "company"
                        ]
                      },
                      "record_id": {
                        "type": [
                          "integer",
                          "null"
                        ],
                        "description": "Id of the record to attach the note to. Required when `record_type` is set.",
                        "examples": [
                          42
                        ]
                      }
                    },
                    "required": [
                      "content"
                    ]
                  }
                },
                "required": [
                  "note"
                ]
              }
            }
          },
          "required": true,
          "description": "Note attributes, wrapped in a `note` key."
        }
      }
    },
    "/public-api/v1/notes/{id}": {
      "get": {
        "summary": "Retrieve a note",
        "tags": [
          "Notes"
        ],
        "operationId": "getNote",
        "description": "Retrieves one note. Unlike the list endpoint, this returns the body:\n`content_html` is the note in the HTML the Bonsai editor renders,\nscrubbed of script-execution vectors, and the field to send back to\n`PATCH /public-api/v1/notes/{id}` when editing it.\n`content_plain_text` is the same body with the markup removed, for when\nyou only need to read what it says.\n\n`note_links` leaves out any record the token cannot reach, so a note can\ncome back with an empty `note_links` even though somebody else can see\nwhat it is about.\n\nAnyone can read a note they wrote; reading someone else's needs\npermission to read notes. A note the token cannot see returns `404`, and\none it can see but may not read returns `403`.",
        "security": [
          {
            "oauth2": []
          },
          {
            "api_key": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The note's `id`.",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The note, including what it says.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "allOf": [
                        {
                          "$ref": "#/components/schemas/Note"
                        },
                        {
                          "$ref": "#/components/schemas/NoteDetailedTrait"
                        }
                      ]
                    },
                    "meta": {
                      "$ref": "#/components/schemas/Meta"
                    }
                  },
                  "required": [
                    "data",
                    "meta"
                  ]
                }
              }
            }
          },
          "401": {
            "description": "The access token is missing, expired or invalid.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "403": {
            "description": "This token can see the note but is not allowed to read notes it did not write.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "404": {
            "description": "No note with that `id` that this token can see. A deleted note returns `404` too.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded. Wait at least `Retry-After` seconds before trying again.",
            "headers": {
              "Retry-After": {
                "schema": {
                  "type": "integer"
                },
                "description": "Seconds to wait before sending another request."
              },
              "RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "How many requests the exhausted window allows."
              },
              "RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left in the window. Always `0` on a throttled response."
              },
              "RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Seconds until the exhausted window resets."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        }
      },
      "patch": {
        "summary": "Update a note",
        "tags": [
          "Notes"
        ],
        "operationId": "updateNote",
        "description": "Rewrites what a note says. Every field is optional, and anything you\nleave out keeps its current value.\n\nAnyone can edit a note they wrote; editing someone else's needs\npermission to write notes. A note the token cannot see returns `404`,\nand one it can see but may not change returns `403`.\n\n`visibility` is the author's to set: an edit from anybody else that\nchanges it returns `403` and writes nothing at all, not even the rest\nof the payload. Sending back the value the note already has is\naccepted, so reading a note, changing one field and returning the\nwhole thing works whoever you are.\n\nThe records a note is attached to are fixed once it is written, so\n`record_type` and `record_id` are not accepted here.\n\n## Formatting\n\n`content_html` is HTML and replaces the whole body. It is the same\nshape `GET /public-api/v1/notes/{id}` hands back, so read that field,\nchange the part you need and send the result — whatever you leave\nalone survives the edit untouched.\n\nMarkdown is not accepted here; sending the `content` that\n`POST /public-api/v1/notes` takes returns `422` naming `content_html`,\nrather than leaving the edit to vanish. The create endpoint takes\nMarkdown because a new note has nothing to preserve, while an edit\ndoes, and Markdown cannot express mentions, images or merged table\ncells.\n\nTask list items carry the state the editor reads back, so keep their\nattributes as they came:\n\n    <ul data-type=\"taskList\">\n      <li data-type=\"taskItem\" data-checked=\"true\"><p>Confirm budget</p></li>\n      <li data-type=\"taskItem\" data-checked=\"false\"><p>Send the quote</p></li>\n    </ul>",
        "security": [
          {
            "oauth2": []
          },
          {
            "api_key": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The note's `id`.",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The note was updated.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "allOf": [
                        {
                          "$ref": "#/components/schemas/Note"
                        },
                        {
                          "$ref": "#/components/schemas/NoteDetailedTrait"
                        }
                      ]
                    },
                    "meta": {
                      "$ref": "#/components/schemas/Meta"
                    }
                  },
                  "required": [
                    "data",
                    "meta"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "The request body is missing its `note` wrapper.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "401": {
            "description": "The access token is missing, expired or invalid.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "403": {
            "description": "This token can see the note but is not allowed to change it, or tried to change the `visibility` of a note it did not write.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "404": {
            "description": "No note with that `id` that this token can see. A deleted note returns `404` too.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "422": {
            "description": "The note could not be saved. `blank` means `content_html` was emptied, or held only markup that is stripped before storing; `invalid_value` means a field was the wrong shape, or `content` was sent where `content_html` belongs.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded. Wait at least `Retry-After` seconds before trying again.",
            "headers": {
              "Retry-After": {
                "schema": {
                  "type": "integer"
                },
                "description": "Seconds to wait before sending another request."
              },
              "RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "How many requests the exhausted window allows."
              },
              "RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left in the window. Always `0` on a throttled response."
              },
              "RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Seconds until the exhausted window resets."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        },
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "note": {
                    "type": "object",
                    "properties": {
                      "content_html": {
                        "type": "string",
                        "description": "The new note body, as HTML, replacing the current one. Read the current HTML from `GET /public-api/v1/notes/{id}`. Cannot be emptied.",
                        "examples": [
                          "<h2>Renewal call</h2><p>Signed off on the May launch.</p>"
                        ]
                      },
                      "title": {
                        "type": [
                          "string",
                          "null"
                        ],
                        "description": "New note title. Send an empty string to remove it.",
                        "examples": [
                          "Renewal call"
                        ]
                      },
                      "date": {
                        "type": [
                          "string",
                          "null"
                        ],
                        "format": "date",
                        "description": "The day the note is about. `null` is ignored rather than clearing it.",
                        "examples": [
                          "2026-06-15"
                        ]
                      },
                      "visibility": {
                        "type": [
                          "string",
                          "null"
                        ],
                        "enum": [
                          "everyone",
                          "author_only",
                          null
                        ],
                        "description": "Who can read the note: `everyone` for the whole account, `author_only` for nobody but its author. Only the member who wrote the note can change this. `null` is ignored rather than resetting it to `everyone`.",
                        "examples": [
                          "author_only"
                        ]
                      }
                    }
                  }
                },
                "required": [
                  "note"
                ]
              }
            }
          },
          "required": true,
          "description": "The attributes to change, wrapped in a `note` key. All of them are optional."
        }
      },
      "delete": {
        "summary": "Delete a note",
        "tags": [
          "Notes"
        ],
        "operationId": "deleteNote",
        "description": "Deletes a note. It stops appearing in every subsequent read, and the\nresponse hands back its final state — body included, along with the\nrecords it was attached to — so you can log what went.\n\nAnyone can delete a note they wrote; deleting someone else's needs\npermission to delete notes. A note the token cannot see returns `404`,\nand one it can see but may not delete returns `403`.",
        "security": [
          {
            "oauth2": []
          },
          {
            "api_key": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The note's `id`.",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The note was deleted. Its final state is in `data`.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "allOf": [
                        {
                          "$ref": "#/components/schemas/Note"
                        },
                        {
                          "$ref": "#/components/schemas/NoteDetailedTrait"
                        }
                      ]
                    },
                    "meta": {
                      "$ref": "#/components/schemas/Meta"
                    }
                  },
                  "required": [
                    "data",
                    "meta"
                  ]
                }
              }
            }
          },
          "401": {
            "description": "The access token is missing, expired or invalid.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "403": {
            "description": "This token can see the note but is not allowed to delete it.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "404": {
            "description": "No note with that `id` that this token can see. An already deleted note returns `404` too.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded. Wait at least `Retry-After` seconds before trying again.",
            "headers": {
              "Retry-After": {
                "schema": {
                  "type": "integer"
                },
                "description": "Seconds to wait before sending another request."
              },
              "RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "How many requests the exhausted window allows."
              },
              "RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left in the window. Always `0` on a throttled response."
              },
              "RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Seconds until the exhausted window resets."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        }
      }
    },
    "/public-api/v1/projects": {
      "get": {
        "summary": "List projects",
        "tags": [
          "Projects"
        ],
        "operationId": "listProjects",
        "description": "Lists your projects, a page at a time. Only active projects come back\nunless you ask for others with `filter[status]`.\n\n## Filtering\n\n- `filter[title]` — searches project titles, matching from the start of\n  any word and ignoring case.\n- `filter[public_url_token]` — one or more URL tokens, for looking up\n  projects you already hold a client link for.\n- `filter[board_group_id]` — one or more pipeline stage ids. Returns the\n  projects whose board card sits in those stages.\n- `filter[status]` — which projects to include:\n  - `active` (the default) — neither archived nor completed.\n  - `completed` — finished but not archived.\n  - `archived` — archived, whether or not they were completed.\n  - `not_archived` — everything except archived projects.\n  - `all` — every project, whatever its state.",
        "security": [
          {
            "oauth2": []
          },
          {
            "api_key": []
          }
        ],
        "parameters": [
          {
            "name": "page[number]",
            "in": "query",
            "required": false,
            "description": "Page to return, starting at 1.",
            "schema": {
              "type": "integer",
              "minimum": 1
            }
          },
          {
            "name": "page[size]",
            "in": "query",
            "required": false,
            "description": "Records per page. Defaults to 25; anything above 100 is reduced to 100.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100
            }
          },
          {
            "name": "filter[title]",
            "in": "query",
            "required": false,
            "description": "Searches project titles, matching from the start of any word and ignoring case.",
            "schema": {
              "type": "string"
            },
            "examples": {
              "default": {
                "value": "Acme"
              }
            }
          },
          {
            "name": "filter[public_url_token]",
            "in": "query",
            "required": false,
            "description": "One or more URL tokens, comma-separated or repeated.",
            "schema": {
              "type": "string"
            },
            "examples": {
              "default": {
                "value": "acme-website-x4n2"
              }
            }
          },
          {
            "name": "filter[board_group_id]",
            "in": "query",
            "required": false,
            "description": "One or more pipeline stage ids, comma-separated or repeated. Returns the projects whose board card sits in those stages.",
            "schema": {
              "type": "string"
            },
            "examples": {
              "default": {
                "value": "0c8f1e2a-3b4c-4d5e-6f70-8192a3b4c5d6"
              }
            }
          },
          {
            "name": "filter[status]",
            "in": "query",
            "required": false,
            "description": "Which projects to include. Defaults to `active`.",
            "schema": {
              "type": "string",
              "enum": [
                "active",
                "archived",
                "not_archived",
                "completed",
                "all"
              ]
            },
            "examples": {
              "default": {
                "value": "all"
              }
            }
          }
        ],
        "responses": {
          "200": {
            "description": "A page of projects.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Project"
                      }
                    },
                    "meta": {
                      "$ref": "#/components/schemas/PaginatedMeta"
                    }
                  },
                  "required": [
                    "data",
                    "meta"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "A filter key is unknown, `filter[status]` is not one of the listed values, or a pagination parameter is not an integer.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "401": {
            "description": "The access token is missing, expired or invalid.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "403": {
            "description": "This token is not allowed to read or create projects.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded. Wait at least `Retry-After` seconds before trying again.",
            "headers": {
              "Retry-After": {
                "schema": {
                  "type": "integer"
                },
                "description": "Seconds to wait before sending another request."
              },
              "RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "How many requests the exhausted window allows."
              },
              "RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left in the window. Always `0` on a throttled response."
              },
              "RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Seconds until the exhausted window resets."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        }
      },
      "post": {
        "summary": "Create a project",
        "tags": [
          "Projects"
        ],
        "operationId": "createProject",
        "description": "Creates a project for a client you already have. `title`, `company_id`\nand `billing_type` are required; find the company id with\n`GET /public-api/v1/companies`.\n\n`billing_fee` and `billing_cycle` have to agree with `billing_type`, and\na mismatch is rejected rather than ignored:\n\n| `billing_type` | `billing_fee` | `billing_cycle` |\n| --- | --- | --- |\n| `fixed_fee` | required | must be omitted |\n| `retainer` | required | required |\n| `time` | must be omitted | must be omitted |\n| `not_billable` | must be omitted | must be omitted |\n\nBonsai fills in the rest: the project number, its client-facing URL\ntoken, its color, and its starting members. `currency` follows the\nclient's currency and `start_date` defaults to today.",
        "security": [
          {
            "oauth2": []
          },
          {
            "api_key": []
          }
        ],
        "parameters": [],
        "responses": {
          "201": {
            "description": "The project was created.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/Project"
                    },
                    "meta": {
                      "$ref": "#/components/schemas/Meta"
                    }
                  },
                  "required": [
                    "data",
                    "meta"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "The request body is missing its `project` wrapper.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "401": {
            "description": "The access token is missing, expired or invalid.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "403": {
            "description": "This token is not allowed to read or create projects.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "422": {
            "description": "The project could not be saved. `blank` or `invalid_value` means a required field was empty or the billing fields disagree with `billing_type`; `not_accessible` means `company_id` is not a company in your account.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded. Wait at least `Retry-After` seconds before trying again.",
            "headers": {
              "Retry-After": {
                "schema": {
                  "type": "integer"
                },
                "description": "Seconds to wait before sending another request."
              },
              "RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "How many requests the exhausted window allows."
              },
              "RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left in the window. Always `0` on a throttled response."
              },
              "RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Seconds until the exhausted window resets."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        },
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "project": {
                    "type": "object",
                    "properties": {
                      "title": {
                        "type": "string",
                        "description": "Project title. Required.",
                        "examples": [
                          "Acme Website"
                        ]
                      },
                      "company_id": {
                        "type": "integer",
                        "description": "Id of the client company the project is for. Required.",
                        "examples": [
                          42
                        ]
                      },
                      "currency": {
                        "type": [
                          "string",
                          "null"
                        ],
                        "description": "Three-letter ISO 4217 currency code the project bills in. Defaults to the client's currency.",
                        "examples": [
                          "USD"
                        ]
                      },
                      "start_date": {
                        "type": [
                          "string",
                          "null"
                        ],
                        "format": "date",
                        "description": "Date work starts. Defaults to today.",
                        "examples": [
                          "2026-05-01"
                        ]
                      },
                      "billing_type": {
                        "type": "string",
                        "enum": [
                          "time",
                          "fixed_fee",
                          "retainer",
                          "not_billable"
                        ],
                        "description": "How the project bills. Required. See the table above for which fee fields each type takes.",
                        "examples": [
                          "fixed_fee"
                        ]
                      },
                      "billing_fee": {
                        "type": [
                          "string",
                          "null"
                        ],
                        "description": "The agreed fee, as a decimal string. Required for `fixed_fee` and `retainer`, rejected for the other types.",
                        "examples": [
                          "5000"
                        ]
                      },
                      "billing_cycle": {
                        "type": [
                          "string",
                          "null"
                        ],
                        "enum": [
                          "weekly",
                          "monthly",
                          "quarterly",
                          "biannually",
                          "yearly",
                          null
                        ],
                        "description": "How often a retainer bills. Required for `retainer`, rejected for the other types.",
                        "examples": [
                          "monthly"
                        ]
                      }
                    },
                    "required": [
                      "title",
                      "company_id",
                      "billing_type"
                    ]
                  }
                },
                "required": [
                  "project"
                ]
              }
            }
          },
          "required": true,
          "description": "Project attributes, wrapped in a `project` key."
        }
      }
    },
    "/public-api/v1/tasks/{task_id}/subtasks": {
      "get": {
        "summary": "List subtasks",
        "tags": [
          "Tasks"
        ],
        "operationId": "listSubtasks",
        "description": "Lists the tasks nested under a parent task, in the order they appear\nbeneath it. Each one carries the same fields as a task in the list\nendpoint.\n\nArchived and deleted subtasks are left out; completed ones are not.",
        "security": [
          {
            "oauth2": []
          },
          {
            "api_key": []
          }
        ],
        "parameters": [
          {
            "name": "task_id",
            "in": "path",
            "required": true,
            "description": "The parent task's `uuid`.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "page[number]",
            "in": "query",
            "required": false,
            "description": "Page to return, starting at 1.",
            "schema": {
              "type": "integer",
              "minimum": 1
            }
          },
          {
            "name": "page[size]",
            "in": "query",
            "required": false,
            "description": "Records per page. Defaults to 25; anything above 100 is reduced to 100.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100
            }
          }
        ],
        "responses": {
          "200": {
            "description": "A page of the parent's subtasks.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Task"
                      }
                    },
                    "meta": {
                      "$ref": "#/components/schemas/PaginatedMeta"
                    }
                  },
                  "required": [
                    "data",
                    "meta"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "A pagination parameter is not an integer.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "401": {
            "description": "The access token is missing, expired or invalid.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "403": {
            "description": "This token is not allowed to see tasks.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "404": {
            "description": "No task with that `uuid` in your account. Archived, deleted and template parents return `404` too.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded. Wait at least `Retry-After` seconds before trying again.",
            "headers": {
              "Retry-After": {
                "schema": {
                  "type": "integer"
                },
                "description": "Seconds to wait before sending another request."
              },
              "RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "How many requests the exhausted window allows."
              },
              "RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left in the window. Always `0` on a throttled response."
              },
              "RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Seconds until the exhausted window resets."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        }
      }
    },
    "/public-api/v1/task_statuses": {
      "get": {
        "summary": "List task statuses",
        "tags": [
          "Task Statuses"
        ],
        "operationId": "listTaskStatuses",
        "description": "Lists the columns of your task board — `To Do`, `In Progress` and `Done`,\nplus any columns your account has added.\n\nThis is where you turn a status name into the `task_status_id` the task\nendpoints take, whether you are placing a new task in a column, moving\nan existing one, or filtering a list by column.",
        "security": [
          {
            "oauth2": []
          },
          {
            "api_key": []
          }
        ],
        "parameters": [
          {
            "name": "page[number]",
            "in": "query",
            "required": false,
            "description": "Page to return, starting at 1.",
            "schema": {
              "type": "integer",
              "minimum": 1
            }
          },
          {
            "name": "page[size]",
            "in": "query",
            "required": false,
            "description": "Records per page. Defaults to 25; anything above 100 is reduced to 100.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100
            }
          }
        ],
        "responses": {
          "200": {
            "description": "A page of task statuses, ordered by their position on the board.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/TaskStatus"
                      }
                    },
                    "meta": {
                      "$ref": "#/components/schemas/PaginatedMeta"
                    }
                  },
                  "required": [
                    "data",
                    "meta"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "A pagination parameter is not an integer.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "401": {
            "description": "The access token is missing, expired or invalid.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "403": {
            "description": "This token is not allowed to see tasks, so it cannot read the board columns either.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded. Wait at least `Retry-After` seconds before trying again.",
            "headers": {
              "Retry-After": {
                "schema": {
                  "type": "integer"
                },
                "description": "Seconds to wait before sending another request."
              },
              "RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "How many requests the exhausted window allows."
              },
              "RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left in the window. Always `0` on a throttled response."
              },
              "RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Seconds until the exhausted window resets."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        }
      }
    },
    "/public-api/v1/tasks": {
      "get": {
        "summary": "List tasks",
        "tags": [
          "Tasks"
        ],
        "operationId": "listTasks",
        "description": "Lists tasks, a page at a time. Only active tasks come back unless you ask\nfor others with `filter[scope]`.\n\nDescriptions are left out of list responses — retrieve a task on its own\nto read `description_html` or `description_plain_text`.\n\nTwo keys depend on permissions and are left out entirely for a token\nwithout them: `billable` for a token not allowed to see billing, and\n`deal_id` for one not allowed to see deals. Treat both as optional keys\nrather than assuming they are always there.\n\n## Filtering\n\n- `filter[assignee_id]` — one or more user ids, or `me` for whoever the\n  token belongs to. Note this takes a user id, not a team member id.\n- `filter[scope]` — which tasks to include: `active` (the default),\n  `completed`, `archived`, `all`, `my_tasks`, `planned` (has a due date),\n  `unplanned` (has none) or `upcoming_and_overdue`.\n- `filter[due]` — a named date window: `today`, `overdue`,\n  `today_or_overdue`, `this_week`, `this_month` or `upcoming`. Cannot be\n  combined with `filter[due_from]` or `filter[due_to]`.\n- `filter[due_from]` and `filter[due_to]` — an explicit date range.\n  Either bound works on its own.\n- `filter[deal_id]` — one or more deal ids. Needs permission to see\n  deals; without it this filter returns `403` rather than quietly\n  returning every task.\n- `filter[priority]` — one or more of `urgent`, `high`, `medium`, `low`.\n- `filter[project_id]` — one or more project ids.\n- `filter[tag_id]` — one or more tag ids.\n- `filter[task_status_id]` — one or more task status ids. This one also\n  widens the default scope: tasks in the given columns come back whether\n  they are active, completed or archived.\n- `filter[title]` — searches task titles, ignoring case.",
        "security": [
          {
            "oauth2": []
          },
          {
            "api_key": []
          }
        ],
        "parameters": [
          {
            "name": "page[number]",
            "in": "query",
            "required": false,
            "description": "Page to return, starting at 1.",
            "schema": {
              "type": "integer",
              "minimum": 1
            }
          },
          {
            "name": "page[size]",
            "in": "query",
            "required": false,
            "description": "Records per page. Defaults to 25; anything above 100 is reduced to 100.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100
            }
          },
          {
            "name": "filter[assignee_id]",
            "in": "query",
            "required": false,
            "description": "One or more user ids, comma-separated or repeated. Pass `me` for whoever the token belongs to.",
            "schema": {
              "type": "string"
            },
            "examples": {
              "default": {
                "value": "me"
              }
            }
          },
          {
            "name": "filter[scope]",
            "in": "query",
            "required": false,
            "description": "Which tasks to include. Defaults to `active`.",
            "schema": {
              "type": "string",
              "enum": [
                "active",
                "completed",
                "archived",
                "all",
                "upcoming_and_overdue",
                "unplanned",
                "planned",
                "my_tasks"
              ]
            }
          },
          {
            "name": "filter[due]",
            "in": "query",
            "required": false,
            "description": "A named due-date window. Cannot be combined with `filter[due_from]` or `filter[due_to]`.",
            "schema": {
              "type": "string",
              "enum": [
                "today",
                "overdue",
                "today_or_overdue",
                "this_week",
                "this_month",
                "upcoming"
              ]
            }
          },
          {
            "name": "filter[due_from]",
            "in": "query",
            "required": false,
            "description": "Return tasks due on or after this date.",
            "schema": {
              "type": "string",
              "format": "date"
            },
            "examples": {
              "default": {
                "value": "2026-05-01"
              }
            }
          },
          {
            "name": "filter[due_to]",
            "in": "query",
            "required": false,
            "description": "Return tasks due on or before this date.",
            "schema": {
              "type": "string",
              "format": "date"
            },
            "examples": {
              "default": {
                "value": "2026-05-31"
              }
            }
          },
          {
            "name": "filter[deal_id]",
            "in": "query",
            "required": false,
            "description": "One or more deal ids, comma-separated or repeated. Needs permission to see deals; without it this filter returns 403.",
            "schema": {
              "type": "string"
            },
            "examples": {
              "default": {
                "value": "17"
              }
            }
          },
          {
            "name": "filter[priority]",
            "in": "query",
            "required": false,
            "description": "One or more priorities, comma-separated or repeated.",
            "schema": {
              "type": "string"
            },
            "examples": {
              "default": {
                "value": "high,medium"
              }
            }
          },
          {
            "name": "filter[project_id]",
            "in": "query",
            "required": false,
            "description": "One or more project ids, comma-separated or repeated.",
            "schema": {
              "type": "string"
            },
            "examples": {
              "default": {
                "value": "51"
              }
            }
          },
          {
            "name": "filter[tag_id]",
            "in": "query",
            "required": false,
            "description": "One or more tag ids, comma-separated or repeated.",
            "schema": {
              "type": "string"
            },
            "examples": {
              "default": {
                "value": "7"
              }
            }
          },
          {
            "name": "filter[task_status_id]",
            "in": "query",
            "required": false,
            "description": "One or more task status ids, comma-separated or repeated. Returns tasks in those columns whether they are active, completed or archived.",
            "schema": {
              "type": "string"
            },
            "examples": {
              "default": {
                "value": "e1c4b06e-3a0f-4a52-9c8f-6a1d2b3c4d5e"
              }
            }
          },
          {
            "name": "filter[title]",
            "in": "query",
            "required": false,
            "description": "Searches task titles, ignoring case.",
            "schema": {
              "type": "string"
            },
            "examples": {
              "default": {
                "value": "Homepage"
              }
            }
          }
        ],
        "responses": {
          "200": {
            "description": "A page of tasks, without their descriptions.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Task"
                      }
                    },
                    "meta": {
                      "$ref": "#/components/schemas/PaginatedMeta"
                    }
                  },
                  "required": [
                    "data",
                    "meta"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "A filter key is unknown, a filter value is not allowed, two filters conflict, or a pagination parameter is not an integer.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "401": {
            "description": "The access token is missing, expired or invalid.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "403": {
            "description": "This token is not allowed to see tasks, or used `filter[deal_id]` without permission to see deals.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded. Wait at least `Retry-After` seconds before trying again.",
            "headers": {
              "Retry-After": {
                "schema": {
                  "type": "integer"
                },
                "description": "Seconds to wait before sending another request."
              },
              "RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "How many requests the exhausted window allows."
              },
              "RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left in the window. Always `0` on a throttled response."
              },
              "RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Seconds until the exhausted window resets."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        }
      },
      "post": {
        "summary": "Create a task",
        "tags": [
          "Tasks"
        ],
        "operationId": "createTask",
        "description": "Creates a task. Only `title` is required — a task does not need a project,\nan assignee or a date.\n\nAssign the task with `assignee_member_id`: a team member id from\n`GET /public-api/v1/team_members`, or `me` for whoever the token belongs\nto. Responses report the assignee twice, as `assignee_member_id` and as\nthe user's `assignee_id`, but only `assignee_member_id` is writable.\n\n## Subtasks\n\nSend `parent_task_uuid` to create the task underneath an existing one.\nA subtask takes its project and billable setting from its parent, so\n`project_id` cannot be sent alongside `parent_task_uuid`.\n\nTasks nest two levels deep at most — a top-level task, a subtask, and a\nsubtask of that. Pointing at a parent that is already at the bottom\nfails with `422 invalid_value`. Read a task's children with\n`GET /public-api/v1/tasks/{uuid}/subtasks`.",
        "security": [
          {
            "oauth2": []
          },
          {
            "api_key": []
          }
        ],
        "parameters": [],
        "responses": {
          "201": {
            "description": "The task was created.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "allOf": [
                        {
                          "$ref": "#/components/schemas/Task"
                        },
                        {
                          "$ref": "#/components/schemas/TaskDetailedTrait"
                        }
                      ]
                    },
                    "meta": {
                      "$ref": "#/components/schemas/Meta"
                    }
                  },
                  "required": [
                    "data",
                    "meta"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "The request body is missing its `task` wrapper.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "401": {
            "description": "The access token is missing, expired or invalid.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "403": {
            "description": "This token is not allowed to create tasks.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "422": {
            "description": "The task could not be saved. `blank` or `invalid_value` means a field was empty or the wrong shape; `not_accessible` means a referenced record is not in your account, or that this token is not allowed to set that particular field — `priority`, `due_date`, `start_date`, `time_estimate_in_minutes` and `assignee_member_id` all need permission to edit task details, and `deal_id` needs permission to create deals on top of that.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded. Wait at least `Retry-After` seconds before trying again.",
            "headers": {
              "Retry-After": {
                "schema": {
                  "type": "integer"
                },
                "description": "Seconds to wait before sending another request."
              },
              "RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "How many requests the exhausted window allows."
              },
              "RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left in the window. Always `0` on a throttled response."
              },
              "RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Seconds until the exhausted window resets."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        },
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "task": {
                    "type": "object",
                    "properties": {
                      "title": {
                        "type": "string",
                        "description": "Task title. Required.",
                        "examples": [
                          "Finalize homepage mockups"
                        ]
                      },
                      "project_id": {
                        "type": [
                          "integer",
                          "null"
                        ],
                        "description": "Id of the project the task belongs to. Leave it out for a task that stands on its own. Cannot be combined with `parent_task_uuid` — a subtask takes its project from its parent. Find one with `GET /public-api/v1/projects`.",
                        "examples": [
                          51
                        ]
                      },
                      "deal_id": {
                        "type": [
                          "integer",
                          "null"
                        ],
                        "description": "Id of the deal to put the task on. Independent of `project_id` — a task can sit on a deal, a project, both or neither. Needs permission to create deals; without it the request fails with `422 not_accessible`. A deal that is not in your account fails the same way. Find one with `GET /public-api/v1/deals`.",
                        "examples": [
                          17
                        ]
                      },
                      "parent_task_uuid": {
                        "type": [
                          "string",
                          "null"
                        ],
                        "description": "`uuid` of the task to nest this one under. The subtask takes its project and billable setting from the parent, so `project_id` cannot be sent with it. A parent that is not in your account fails with `422 not_accessible`; a parent already two levels deep fails with `422 invalid_value`.",
                        "examples": [
                          "ad3fdad8-5072-46df-8800-bb57299179df"
                        ]
                      },
                      "assignee_member_id": {
                        "oneOf": [
                          {
                            "type": [
                              "integer",
                              "null"
                            ],
                            "description": "Team member id of the assignee."
                          },
                          {
                            "type": "string",
                            "enum": [
                              "me"
                            ],
                            "description": "Assigns the task to whoever the token belongs to."
                          }
                        ],
                        "description": "Who the task is for: a team member id, or `me` for whoever the token belongs to. Send `null` or leave it out for an unassigned task. Find ids with `GET /public-api/v1/team_members` — use the `company_member_id` field.",
                        "examples": [
                          "me"
                        ]
                      },
                      "priority": {
                        "type": [
                          "string",
                          "null"
                        ],
                        "enum": [
                          "urgent",
                          "high",
                          "medium",
                          "low",
                          null
                        ],
                        "description": "How urgent the task is.",
                        "examples": [
                          "high"
                        ]
                      },
                      "due_date": {
                        "type": [
                          "string",
                          "null"
                        ],
                        "format": "date",
                        "description": "Date the task is due. Leave it out for an unscheduled task. Cannot be earlier than `start_date` when you send both.",
                        "examples": [
                          "2026-05-15"
                        ]
                      },
                      "start_date": {
                        "type": [
                          "string",
                          "null"
                        ],
                        "format": "date",
                        "description": "Date work on the task starts. Cannot be later than `due_date` when you send both.",
                        "examples": [
                          "2026-05-01"
                        ]
                      },
                      "description": {
                        "type": [
                          "string",
                          "null"
                        ],
                        "description": "What the task involves. Accepts an HTML fragment for formatting — `<p>`, `<strong>`, `<ul>`/`<li>` and `<a>` among others. Read it back as `description_html` (sanitized) or `description_plain_text` (tags removed) on single-task responses; neither appears in a list.",
                        "examples": [
                          "<p>Draft the <strong>brief</strong></p>"
                        ]
                      },
                      "time_estimate_in_minutes": {
                        "type": [
                          "integer",
                          "null"
                        ],
                        "description": "Estimated effort in whole minutes — 90 for an hour and a half. Must be positive.",
                        "examples": [
                          90
                        ]
                      },
                      "task_status_id": {
                        "type": [
                          "string",
                          "null"
                        ],
                        "description": "Id of the board column to put the task in. Defaults to your `To Do` column. Find one with `GET /public-api/v1/task_statuses`.",
                        "examples": [
                          "e1c4b06e-3a0f-4a52-9c8f-6a1d2b3c4d5e"
                        ]
                      },
                      "tag_ids": {
                        "type": "array",
                        "items": {
                          "type": "integer"
                        },
                        "description": "Tags to put on the task. Must be `task` tags from your account — find them with `GET /public-api/v1/company_tags`. Read them back under `company_tags`.",
                        "examples": [
                          [
                            7,
                            12
                          ]
                        ]
                      }
                    },
                    "required": [
                      "title"
                    ]
                  }
                },
                "required": [
                  "task"
                ]
              }
            }
          },
          "required": true,
          "description": "Task attributes, wrapped in a `task` key."
        }
      }
    },
    "/public-api/v1/tasks/{uuid}": {
      "get": {
        "summary": "Retrieve a task",
        "tags": [
          "Tasks"
        ],
        "operationId": "getTask",
        "description": "Retrieves one task by `uuid`. Unlike the list endpoint, this returns the\ntask's description, as both `description_html` and\n`description_plain_text`.",
        "security": [
          {
            "oauth2": []
          },
          {
            "api_key": []
          }
        ],
        "parameters": [
          {
            "name": "uuid",
            "in": "path",
            "required": true,
            "description": "The task's `uuid`.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The task, including its description.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "allOf": [
                        {
                          "$ref": "#/components/schemas/Task"
                        },
                        {
                          "$ref": "#/components/schemas/TaskDetailedTrait"
                        }
                      ]
                    },
                    "meta": {
                      "$ref": "#/components/schemas/Meta"
                    }
                  },
                  "required": [
                    "data",
                    "meta"
                  ]
                }
              }
            }
          },
          "401": {
            "description": "The access token is missing, expired or invalid.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "404": {
            "description": "No task with that `uuid` in your account. Archived, deleted and template tasks return `404` too.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded. Wait at least `Retry-After` seconds before trying again.",
            "headers": {
              "Retry-After": {
                "schema": {
                  "type": "integer"
                },
                "description": "Seconds to wait before sending another request."
              },
              "RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "How many requests the exhausted window allows."
              },
              "RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left in the window. Always `0` on a throttled response."
              },
              "RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Seconds until the exhausted window resets."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        }
      },
      "delete": {
        "summary": "Delete a task",
        "tags": [
          "Tasks"
        ],
        "operationId": "deleteTask",
        "description": "Deletes a task. It stops appearing in every subsequent read, and the\nresponse hands back its final state so you can log or undo from it.\nArchived tasks can be deleted without unarchiving them first.\n\nAnyone can delete a task they created; deleting someone else's needs\npermission to delete tasks. A task the token cannot see returns `404`,\nand one it can see but may not delete returns `403`.",
        "security": [
          {
            "oauth2": []
          },
          {
            "api_key": []
          }
        ],
        "parameters": [
          {
            "name": "uuid",
            "in": "path",
            "required": true,
            "description": "The task's `uuid`.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The task was deleted. Its final state is in `data`.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "allOf": [
                        {
                          "$ref": "#/components/schemas/Task"
                        },
                        {
                          "$ref": "#/components/schemas/TaskDetailedTrait"
                        }
                      ]
                    },
                    "meta": {
                      "$ref": "#/components/schemas/Meta"
                    }
                  },
                  "required": [
                    "data",
                    "meta"
                  ]
                }
              }
            }
          },
          "401": {
            "description": "The access token is missing, expired or invalid.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "403": {
            "description": "This token can see the task but is not allowed to delete it.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "404": {
            "description": "No task with that `uuid` that this token can see. Deleted and template tasks return `404` too.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded. Wait at least `Retry-After` seconds before trying again.",
            "headers": {
              "Retry-After": {
                "schema": {
                  "type": "integer"
                },
                "description": "Seconds to wait before sending another request."
              },
              "RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "How many requests the exhausted window allows."
              },
              "RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left in the window. Always `0` on a throttled response."
              },
              "RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Seconds until the exhausted window resets."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        }
      },
      "patch": {
        "summary": "Update a task",
        "tags": [
          "Tasks"
        ],
        "operationId": "updateTask",
        "description": "Updates a task. Every field is optional, and anything you leave out keeps\nits current value.\n\nSend `task_status_id` to move the task to another column of your board —\nthat is how you mark it done. Sending `null` clears `due_date`,\n`start_date`, `description`, `time_estimate_in_minutes`, `project_id`,\n`deal_id` and `assignee_member_id`; `title` and `priority` cannot be\ncleared this way.\n\nA task the token cannot see returns `404`, and one it can see but may not\nchange returns `403`.",
        "security": [
          {
            "oauth2": []
          },
          {
            "api_key": []
          }
        ],
        "parameters": [
          {
            "name": "uuid",
            "in": "path",
            "required": true,
            "description": "The task's `uuid`.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The task was updated.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "allOf": [
                        {
                          "$ref": "#/components/schemas/Task"
                        },
                        {
                          "$ref": "#/components/schemas/TaskDetailedTrait"
                        }
                      ]
                    },
                    "meta": {
                      "$ref": "#/components/schemas/Meta"
                    }
                  },
                  "required": [
                    "data",
                    "meta"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "The request body is missing its `task` wrapper.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "401": {
            "description": "The access token is missing, expired or invalid.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "403": {
            "description": "This token can see the task but is not allowed to change it.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "404": {
            "description": "No task with that `uuid` that this token can see. Deleted and template tasks return `404` too.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "422": {
            "description": "The task could not be saved. `blank` or `invalid_value` means a field was empty or the wrong shape; `not_accessible` means a referenced record is not in your account, or that this token is not allowed to set that particular field — `priority`, `due_date`, `start_date`, `time_estimate_in_minutes` and `assignee_member_id` all need permission to edit task details, and `deal_id` needs permission to create deals on top of that.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded. Wait at least `Retry-After` seconds before trying again.",
            "headers": {
              "Retry-After": {
                "schema": {
                  "type": "integer"
                },
                "description": "Seconds to wait before sending another request."
              },
              "RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "How many requests the exhausted window allows."
              },
              "RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left in the window. Always `0` on a throttled response."
              },
              "RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Seconds until the exhausted window resets."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        },
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "task": {
                    "type": "object",
                    "properties": {
                      "title": {
                        "type": "string",
                        "description": "New task title. A task must have one, so it cannot be blank.",
                        "examples": [
                          "Finalize homepage mockups"
                        ]
                      },
                      "project_id": {
                        "type": [
                          "integer",
                          "null"
                        ],
                        "description": "Move the task to another project, or send `null` to detach it from the one it is on. Find one with `GET /public-api/v1/projects`.",
                        "examples": [
                          51
                        ]
                      },
                      "deal_id": {
                        "type": [
                          "integer",
                          "null"
                        ],
                        "description": "Move the task to another deal, or send `null` to take it off the one it is on. Independent of `project_id`. Needs permission to create deals; without it the request fails with `422 not_accessible`. A deal that is not in your account fails the same way. Find one with `GET /public-api/v1/deals`.",
                        "examples": [
                          17
                        ]
                      },
                      "assignee_member_id": {
                        "oneOf": [
                          {
                            "type": [
                              "integer",
                              "null"
                            ],
                            "description": "Team member id of the assignee."
                          },
                          {
                            "type": "string",
                            "enum": [
                              "me"
                            ],
                            "description": "Assigns the task to whoever the token belongs to."
                          }
                        ],
                        "description": "Who the task is for: a team member id, or `me` for whoever the token belongs to. Send `null` to unassign it. Find ids with `GET /public-api/v1/team_members` — use the `company_member_id` field.",
                        "examples": [
                          "me"
                        ]
                      },
                      "priority": {
                        "type": "string",
                        "enum": [
                          "urgent",
                          "high",
                          "medium",
                          "low"
                        ],
                        "description": "How urgent the task is. Can be changed but not cleared once set.",
                        "examples": [
                          "high"
                        ]
                      },
                      "due_date": {
                        "type": [
                          "string",
                          "null"
                        ],
                        "format": "date",
                        "description": "Date the task is due. Send `null` to clear it.",
                        "examples": [
                          "2026-05-15"
                        ]
                      },
                      "start_date": {
                        "type": [
                          "string",
                          "null"
                        ],
                        "format": "date",
                        "description": "Date work on the task starts. Send `null` to clear it.",
                        "examples": [
                          "2026-05-01"
                        ]
                      },
                      "description": {
                        "type": [
                          "string",
                          "null"
                        ],
                        "description": "What the task involves, as an HTML fragment. Send `null` to clear it. Read it back as `description_html` or `description_plain_text`.",
                        "examples": [
                          "<p>Draft the <strong>brief</strong></p>"
                        ]
                      },
                      "time_estimate_in_minutes": {
                        "type": [
                          "integer",
                          "null"
                        ],
                        "description": "Estimated effort in whole minutes. Must be positive, or `null` to clear it.",
                        "examples": [
                          90
                        ]
                      },
                      "task_status_id": {
                        "type": "string",
                        "description": "Id of the board column to move the task to. Find one with `GET /public-api/v1/task_statuses`.",
                        "examples": [
                          "e1c4b06e-3a0f-4a52-9c8f-6a1d2b3c4d5e"
                        ]
                      },
                      "tag_ids": {
                        "type": "array",
                        "items": {
                          "type": "integer"
                        },
                        "description": "The tags on the task, replacing whatever is there now. Send an empty array to remove them all. Must be `task` tags from your account — find them with `GET /public-api/v1/company_tags`.",
                        "examples": [
                          [
                            7,
                            12
                          ]
                        ]
                      }
                    }
                  }
                },
                "required": [
                  "task"
                ]
              }
            }
          },
          "required": true,
          "description": "The attributes to change, wrapped in a `task` key. All of them are optional."
        }
      }
    },
    "/public-api/v1/team_members": {
      "get": {
        "summary": "List team members",
        "tags": [
          "Team Members"
        ],
        "operationId": "listTeamMembers",
        "description": "Lists the people in your Bonsai account, a page at a time. This is where\nyou turn a name into an id before assigning work: use\n`company_member_id` for `assignee_member_id` and `owner_member_id`, and\n`user_id` for `filter[assignee_id]` on the tasks list.\n\nTwo fields that sound alike mean different things. `role` is a job title\nyour team typed in, such as `Software Engineer`, and carries no authority.\n`permission_profile` is the profile that decides what the member can do,\nsuch as `Owner` or `Collaborator`.\n\nYour clients are not team members and never appear here.\n\n## Membership status\n\nThere is no single status field. Work it out from three timestamps,\nchecking them in this order:\n\n| Condition | Status |\n| --- | --- |\n| `deactivated_at` is set | Deactivated — no access, whatever else is set |\n| `declined_at` is set | Declined the invitation |\n| `confirmed_at` is set | Active |\n| all three are `null` | Invited, waiting for a response |\n\n## Filtering\n\n- `filter[name]` — searches member names, ignoring case.\n- `filter[id]` — one or more team member ids.\n- `filter[user_id]` — one or more user ids.\n- `filter[project_id]` — one or more project ids. Returns only the\n  members assigned to those projects.",
        "security": [
          {
            "oauth2": []
          },
          {
            "api_key": []
          }
        ],
        "parameters": [
          {
            "name": "page[number]",
            "in": "query",
            "required": false,
            "description": "Page to return, starting at 1.",
            "schema": {
              "type": "integer",
              "minimum": 1
            }
          },
          {
            "name": "page[size]",
            "in": "query",
            "required": false,
            "description": "Records per page. Defaults to 25; anything above 100 is reduced to 100.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100
            }
          },
          {
            "name": "filter[id]",
            "in": "query",
            "required": false,
            "description": "One or more team member ids, comma-separated or repeated.",
            "schema": {
              "type": "string"
            },
            "examples": {
              "default": {
                "value": "314"
              }
            }
          },
          {
            "name": "filter[user_id]",
            "in": "query",
            "required": false,
            "description": "One or more user ids, comma-separated or repeated.",
            "schema": {
              "type": "string"
            },
            "examples": {
              "default": {
                "value": "6012"
              }
            }
          },
          {
            "name": "filter[project_id]",
            "in": "query",
            "required": false,
            "description": "One or more project ids, comma-separated or repeated. Returns only the members assigned to those projects.",
            "schema": {
              "type": "string"
            },
            "examples": {
              "default": {
                "value": "51"
              }
            }
          },
          {
            "name": "filter[name]",
            "in": "query",
            "required": false,
            "description": "Searches member names, ignoring case.",
            "schema": {
              "type": "string"
            },
            "examples": {
              "default": {
                "value": "Maria"
              }
            }
          }
        ],
        "responses": {
          "200": {
            "description": "A page of team members.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/TeamMember"
                      }
                    },
                    "meta": {
                      "$ref": "#/components/schemas/PaginatedMeta"
                    }
                  },
                  "required": [
                    "data",
                    "meta"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "A filter key is unknown, an id filter is not an integer, or a pagination parameter is not an integer.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "401": {
            "description": "The access token is missing, expired or invalid.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "403": {
            "description": "This token is not allowed to see your team.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded. Wait at least `Retry-After` seconds before trying again.",
            "headers": {
              "Retry-After": {
                "schema": {
                  "type": "integer"
                },
                "description": "Seconds to wait before sending another request."
              },
              "RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "How many requests the exhausted window allows."
              },
              "RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left in the window. Always `0` on a throttled response."
              },
              "RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Seconds until the exhausted window resets."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        }
      }
    },
    "/public-api/v1/time_entries": {
      "get": {
        "summary": "List time entries",
        "tags": [
          "Time Entries"
        ],
        "operationId": "listTimeEntries",
        "description": "Lists logged time, newest first, a page at a time.\n\nWhat comes back depends on the token: without permission to see the whole\nteam's time, you only get your own entries, and\n`filter[owner_member_id]` cannot reach anyone else's.\n\nThe money fields — `rate`, `billable_amount`, `non_billable` and\n`billing_status` — are left out entirely for a token that is not allowed\nto see billing. Treat them as optional keys rather than assuming they\nare always there. The same token cannot filter on\n`filter[billing_status]` either: that would hand back the value the\nfield is hiding, so it returns `403` instead.\n\n## Filtering\n\n- `filter[date_from]` and `filter[date_to]` — a date range over the day\n  the work was done. Both bounds are inclusive, and either works alone.\n- `filter[owner_member_id]` — one or more team member ids, or `me` for\n  whoever the token belongs to.\n- `filter[project_id]` — one or more project ids.\n- `filter[company_id]` — one or more client company ids, matched through\n  each entry's project.\n- `filter[billing_status]` — `billed`, `unbilled` or `non_billable`, or\n  several at once. Needs permission to see billing.\n- `filter[currency]` — one or more three-letter ISO 4217 currency codes.",
        "security": [
          {
            "oauth2": []
          },
          {
            "api_key": []
          }
        ],
        "parameters": [
          {
            "name": "page[number]",
            "in": "query",
            "required": false,
            "description": "Page to return, starting at 1.",
            "schema": {
              "type": "integer",
              "minimum": 1
            }
          },
          {
            "name": "page[size]",
            "in": "query",
            "required": false,
            "description": "Records per page. Defaults to 25; anything above 100 is reduced to 100.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100
            }
          },
          {
            "name": "filter[date_from]",
            "in": "query",
            "required": false,
            "description": "Return entries dated on or after this day.",
            "schema": {
              "type": "string",
              "format": "date"
            },
            "examples": {
              "default": {
                "value": "2026-01-01"
              }
            }
          },
          {
            "name": "filter[date_to]",
            "in": "query",
            "required": false,
            "description": "Return entries dated on or before this day.",
            "schema": {
              "type": "string",
              "format": "date"
            },
            "examples": {
              "default": {
                "value": "2026-01-31"
              }
            }
          },
          {
            "name": "filter[owner_member_id]",
            "in": "query",
            "required": false,
            "description": "One or more team member ids, comma-separated or repeated. Pass `me` for whoever the token belongs to. Reaching another member's time needs permission to see the whole team's time.",
            "schema": {
              "type": "string"
            },
            "examples": {
              "default": {
                "value": "me"
              }
            }
          },
          {
            "name": "filter[project_id]",
            "in": "query",
            "required": false,
            "description": "One or more project ids, comma-separated or repeated.",
            "schema": {
              "type": "string"
            },
            "examples": {
              "default": {
                "value": "51"
              }
            }
          },
          {
            "name": "filter[company_id]",
            "in": "query",
            "required": false,
            "description": "One or more client company ids, comma-separated or repeated. Matched through each entry's project.",
            "schema": {
              "type": "string"
            },
            "examples": {
              "default": {
                "value": "42"
              }
            }
          },
          {
            "name": "filter[billing_status]",
            "in": "query",
            "required": false,
            "description": "Return entries in one or more billing states, comma-separated or repeated. Needs permission to see billing; without it this filter returns 403.",
            "schema": {
              "type": "string",
              "enum": [
                "billed",
                "unbilled",
                "non_billable"
              ]
            },
            "examples": {
              "default": {
                "value": "unbilled"
              }
            }
          },
          {
            "name": "filter[currency]",
            "in": "query",
            "required": false,
            "description": "One or more three-letter ISO 4217 currency codes, comma-separated or repeated.",
            "schema": {
              "type": "string"
            },
            "examples": {
              "default": {
                "value": "USD"
              }
            }
          }
        ],
        "responses": {
          "200": {
            "description": "A page of time entries, newest first.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/TimeEntry"
                      }
                    },
                    "meta": {
                      "$ref": "#/components/schemas/PaginatedMeta"
                    }
                  },
                  "required": [
                    "data",
                    "meta"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "A filter key is unknown, a filter value is not allowed, or a pagination parameter is not an integer.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "401": {
            "description": "The access token is missing, expired or invalid.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "403": {
            "description": "This token is not allowed to see logged time, or used `filter[billing_status]` without permission to see billing.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded. Wait at least `Retry-After` seconds before trying again.",
            "headers": {
              "Retry-After": {
                "schema": {
                  "type": "integer"
                },
                "description": "Seconds to wait before sending another request."
              },
              "RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "How many requests the exhausted window allows."
              },
              "RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left in the window. Always `0` on a throttled response."
              },
              "RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Seconds until the exhausted window resets."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        }
      },
      "post": {
        "summary": "Log a time entry",
        "tags": [
          "Time Entries"
        ],
        "operationId": "createTimeEntry",
        "description": "Logs time. Only `seconds` and `date` are required: the account comes from\nthe token, and the client is taken from the project, so neither is\nsomething you send.\n\nAttach the entry to work with `project_id` or `task_uuid`. Sending\n`task_uuid` also settles the project — it is taken from the task, and any\n`project_id` you send is ignored.\n\nTwo fields depend on permissions rather than on what you send. `rate` and\n`non_billable` are only honoured for a token allowed to set billing; for\nanyone else Bonsai derives them from the project or member rate and the\nproject's billing type. Logging time against someone else's name with\n`owner_member_id` needs permission to manage the whole team's time; it\ndefaults to whoever the token belongs to.",
        "security": [
          {
            "oauth2": []
          },
          {
            "api_key": []
          }
        ],
        "parameters": [],
        "responses": {
          "201": {
            "description": "The time entry was logged.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/TimeEntry"
                    },
                    "meta": {
                      "$ref": "#/components/schemas/Meta"
                    }
                  },
                  "required": [
                    "data",
                    "meta"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "The request body is missing its `time_entry` wrapper.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "401": {
            "description": "The access token is missing, expired or invalid.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "403": {
            "description": "This token is not allowed to log time.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "422": {
            "description": "The entry could not be saved. `blank` or `invalid_value` means a required field was empty or the wrong shape; `not_accessible` means `project_id`, `task_uuid` or `owner_member_id` names something this token cannot use.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded. Wait at least `Retry-After` seconds before trying again.",
            "headers": {
              "Retry-After": {
                "schema": {
                  "type": "integer"
                },
                "description": "Seconds to wait before sending another request."
              },
              "RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "How many requests the exhausted window allows."
              },
              "RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left in the window. Always `0` on a throttled response."
              },
              "RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Seconds until the exhausted window resets."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        },
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "time_entry": {
                    "type": "object",
                    "properties": {
                      "seconds": {
                        "type": "integer",
                        "minimum": 0,
                        "description": "How long the work took, in seconds. Required.",
                        "examples": [
                          3600
                        ]
                      },
                      "date": {
                        "type": "string",
                        "format": "date",
                        "description": "The day the work was done. Required.",
                        "examples": [
                          "2026-06-17"
                        ]
                      },
                      "project_id": {
                        "type": [
                          "integer",
                          "null"
                        ],
                        "description": "Id of the project to log against. Ignored when you also send `task_uuid`. Sample projects are not accepted. Find one with `GET /public-api/v1/projects`.",
                        "examples": [
                          51
                        ]
                      },
                      "task_uuid": {
                        "type": [
                          "string",
                          "null"
                        ],
                        "format": "uuid",
                        "description": "`uuid` of the task to log against. The project is taken from the task. Tasks on sample projects, and template tasks, are not accepted. Find one with `GET /public-api/v1/tasks`.",
                        "examples": [
                          "ad3fdad8-5072-46df-8800-bb57299179df"
                        ]
                      },
                      "notes": {
                        "type": [
                          "string",
                          "null"
                        ],
                        "description": "What the time was spent on.",
                        "examples": [
                          "Homepage build"
                        ]
                      },
                      "rate": {
                        "type": [
                          "string",
                          "null"
                        ],
                        "description": "Hourly rate to bill at, as a decimal string. Must be zero or greater. Only honoured for a token allowed to set billing; otherwise it comes from the project or member rate.",
                        "examples": [
                          "150.00"
                        ]
                      },
                      "non_billable": {
                        "type": [
                          "boolean",
                          "null"
                        ],
                        "description": "Whether to exclude the entry from billing. Only honoured for a token allowed to set billing; otherwise it comes from the task or project's billing type.",
                        "examples": [
                          false
                        ]
                      },
                      "owner_member_id": {
                        "oneOf": [
                          {
                            "type": [
                              "integer",
                              "null"
                            ],
                            "description": "Team member id of the person the time is for."
                          },
                          {
                            "type": "string",
                            "enum": [
                              "me"
                            ],
                            "description": "Logs the time for whoever the token belongs to."
                          }
                        ],
                        "description": "Who the time is for: a team member id, or `me` for whoever the token belongs to. Defaults to the token's own member. Logging for someone else needs permission to manage the whole team's time. Find ids with `GET /public-api/v1/team_members`.",
                        "examples": [
                          "me"
                        ]
                      }
                    },
                    "required": [
                      "seconds",
                      "date"
                    ]
                  }
                },
                "required": [
                  "time_entry"
                ]
              }
            }
          },
          "required": true,
          "description": "Time entry attributes, wrapped in a `time_entry` key."
        }
      }
    },
    "/public-api/v1/time_entries/{key}": {
      "patch": {
        "summary": "Update a time entry",
        "tags": [
          "Time Entries"
        ],
        "operationId": "updateTimeEntry",
        "description": "Updates a logged time entry. Every field is optional, and anything you\nleave out keeps its current value.\n\nMove the entry between projects or tasks with `project_id` and\n`task_uuid`. Sending `task_uuid` also settles the project — it is taken\nfrom the task, and any `project_id` you send is ignored. Send either as\n`null` to detach.\n\nMoving the entry to a different project recalculates `rate` and\n`non_billable` from the new project unless you set them yourself.\n\nAs on creation, `rate` and `non_billable` are only honoured for a token\nallowed to set billing, and reassigning `owner_member_id` to somebody\nelse needs permission to manage the whole team's time.\n\nAn entry already billed on a paid or in-progress invoice is frozen:\nediting it fails with `422 not_accessible`. Remove the invoice line item\nfirst if you need to change it.\n\nSet `billing_status` to `billed` or `unbilled` to move the entry's\nbilling state. It is only honoured for a token allowed to set billing,\nand a token without that permission gets `422 not_accessible` rather\nthan a silent no-op. `non_billable` is not a writable value here — use\nthe `non_billable` flag instead.\n\nMarking an entry `billed` requires it to be on a project and billable,\nand fails with `422 invalid_value` otherwise. Moving a billed entry out\nof `billed` indirectly — by clearing its project or task, or by moving it\nto a non-billable project — needs the same billing permission and fails\nwith `422 not_accessible` without it. An entry attached to an\ninvoice cannot be moved in either direction and fails with\n`422 not_accessible`. Repeating `billed` re-stamps the entry with the\ncurrent time, matching the bulk action in the app; repeating `unbilled`\non an already-unbilled entry changes nothing. A request that only sets\n`billing_status` is accepted on a locked timesheet, matching the app's\nmark-as-billed action.\n\n`status` and `billing_status` answer different questions. `status`\ntracks whether the entry sits on an invoice, so an entry you mark billed\nby hand keeps `status: unbilled` while `billing_status` reads `billed`.",
        "security": [
          {
            "oauth2": []
          },
          {
            "api_key": []
          }
        ],
        "parameters": [
          {
            "name": "key",
            "in": "path",
            "required": true,
            "description": "The entry's `key`, as returned in a time entry response.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The time entry was updated.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/TimeEntry"
                    },
                    "meta": {
                      "$ref": "#/components/schemas/Meta"
                    }
                  },
                  "required": [
                    "data",
                    "meta"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "The request body is missing its `time_entry` wrapper.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "401": {
            "description": "The access token is missing, expired or invalid.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "403": {
            "description": "This token can see the entry but is not allowed to change it.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "404": {
            "description": "No time entry with that key that this token can see.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "422": {
            "description": "The entry could not be saved. `invalid_value` means a field was the wrong shape; `not_accessible` means a referenced record is out of reach, or that the entry is already billed on a paid or in-progress invoice and is frozen.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded. Wait at least `Retry-After` seconds before trying again.",
            "headers": {
              "Retry-After": {
                "schema": {
                  "type": "integer"
                },
                "description": "Seconds to wait before sending another request."
              },
              "RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "How many requests the exhausted window allows."
              },
              "RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left in the window. Always `0` on a throttled response."
              },
              "RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Seconds until the exhausted window resets."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        },
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "time_entry": {
                    "type": "object",
                    "properties": {
                      "seconds": {
                        "type": "integer",
                        "minimum": 0,
                        "description": "How long the work took, in seconds.",
                        "examples": [
                          5400
                        ]
                      },
                      "date": {
                        "type": "string",
                        "format": "date",
                        "description": "The day the work was done.",
                        "examples": [
                          "2026-06-17"
                        ]
                      },
                      "project_id": {
                        "type": [
                          "integer",
                          "null"
                        ],
                        "description": "Id of the project to log against, or `null` to detach the entry from the one it is on. Ignored while the entry is linked to a task. Sample projects are not accepted.",
                        "examples": [
                          51
                        ]
                      },
                      "task_uuid": {
                        "type": [
                          "string",
                          "null"
                        ],
                        "format": "uuid",
                        "description": "`uuid` of the task to log against — the project comes with it. Send `null` to unlink the task, which also clears the project it brought.",
                        "examples": [
                          "ad3fdad8-5072-46df-8800-bb57299179df"
                        ]
                      },
                      "notes": {
                        "type": [
                          "string",
                          "null"
                        ],
                        "description": "What the time was spent on. Send an empty string to clear it.",
                        "examples": [
                          "Homepage build"
                        ]
                      },
                      "rate": {
                        "type": [
                          "string",
                          "null"
                        ],
                        "description": "Hourly rate to bill at, as a decimal string. Must be zero or greater. Only honoured for a token allowed to set billing.",
                        "examples": [
                          "150.00"
                        ]
                      },
                      "non_billable": {
                        "type": [
                          "boolean",
                          "null"
                        ],
                        "description": "Whether to exclude the entry from billing. Only honoured for a token allowed to set billing.",
                        "examples": [
                          false
                        ]
                      },
                      "billing_status": {
                        "type": "string",
                        "enum": [
                          "billed",
                          "unbilled"
                        ],
                        "description": "Mark the entry `billed`, or move it back to `unbilled`. Requires a token allowed to set billing — unlike `rate` and `non_billable`, a value here from a token without that permission fails the whole request with `422 not_accessible` rather than being ignored. `non_billable` is not accepted here — use the `non_billable` flag for that.",
                        "examples": [
                          "billed"
                        ]
                      },
                      "owner_member_id": {
                        "oneOf": [
                          {
                            "type": [
                              "integer",
                              "null"
                            ],
                            "description": "Team member id of the person the time is for."
                          },
                          {
                            "type": "string",
                            "enum": [
                              "me"
                            ],
                            "description": "Reassigns the time to whoever the token belongs to."
                          }
                        ],
                        "description": "Who the time is for: a team member id, or `me` for whoever the token belongs to. Reassigning to someone else needs permission to manage the whole team's time.",
                        "examples": [
                          "me"
                        ]
                      }
                    }
                  }
                },
                "required": [
                  "time_entry"
                ]
              }
            }
          },
          "required": true,
          "description": "The attributes to change, wrapped in a `time_entry` key. All of them are optional."
        }
      }
    }
  },
  "x-ext-urls": {}
}