{
  "openapi": "3.1.0",
  "info": {
    "title": "Hotorga API",
    "version": "1.0.0",
    "description": "REST API of the Hotorga CRM, for n8n, Make, Zapier or your own service.\n\n**Authentication**: create a key in Settings → Integrations → API, then send the header `Authorization: Bearer hot_sk_…`. A key has administrator rights: create one per tool so you can revoke it on its own. Keys, settings, team, billing and account management are not available with an API key (403).\n\n**Limits**: 300 requests per minute per key (429 with a `Retry-After` header beyond that); plan quotas (contacts, quotes, emails…) → 403.\n\n**Pagination**: pass `limit` (1 to 200); the response becomes `{items, nextCursor}`. Send `nextCursor` back as `cursor` until it is `null`. In n8n: HTTP Request → Pagination → “Update a Parameter in Each Request”, parameter `cursor` = `{{ $response.body.nextCursor }}`, stop when `{{ !$response.body.nextCursor }}`.\n\n**Errors**: JSON `{\"error\": \"message\"}` with the matching HTTP status, written to be shown to a user as-is. Some errors also carry a machine-readable `code` (see `VAT_STATUS_REQUIRED`).\n\n**VAT status**: quotes and invoices can only be issued once the business has said whether it is VAT-registered (Settings → Company). Until then `POST /invoices` and converting a quote into an invoice answer **409** with `{\"code\": \"VAT_STATUS_REQUIRED\"}`. A business that is not VAT-registered cannot charge VAT (400), and a registered business cannot show VAT on documents dated before its registration date, nor on an invoice before its VAT number is recorded.\n\n**Money and dates**: amounts in GBP, rounded to the penny; dates in ISO 8601; “issue date” = the date a document became an invoice (UK tax point), not the date its quote was drafted.\n\n**Receiving events**: use the “Outgoing webhook” action of an automation. Verify the header `X-Hotorga-Signature-V2: t=<timestamp>,v1=<signature>`, where signature = hex HMAC-SHA256 of `<timestamp>.<raw body>` with your secret; reject a timestamp older than 5 minutes."
  },
  "servers": [
    {
      "url": "https://api.hotorga.co.uk/api"
    }
  ],
  "security": [
    {
      "bearerAuth": []
    }
  ],
  "tags": [
    {
      "name": "Account"
    },
    {
      "name": "Contacts"
    },
    {
      "name": "Quotes and invoices"
    },
    {
      "name": "Catalogue"
    },
    {
      "name": "Reminders"
    },
    {
      "name": "Appointments"
    },
    {
      "name": "Statistics"
    },
    {
      "name": "Automations"
    },
    {
      "name": "Incoming webhook"
    },
    {
      "name": "Exports"
    },
    {
      "name": "E-commerce orders",
      "description": "Hotorga as the back office of your custom online shop: orders, payments, shipments (UK carriers), refunds (CRN credit notes), promo codes, abandoned baskets. Available from the Pro+ plan; test mode on every plan. Signed events to your website: Settings → Integrations → E-commerce website (header `X-Hotorga-Signature-V2`, events order.created, order.paid, order.shipped, order.status_changed, order.refunded, order.cancelled, stock.updated)."
    }
  ],
  "paths": {
    "/me": {
      "get": {
        "tags": [
          "Account"
        ],
        "summary": "Company linked to the key",
        "description": "Handy to test an API key from n8n.",
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "companyId": {
                      "type": "string"
                    },
                    "company": {
                      "type": "string",
                      "description": "Company name (or its email if no name)."
                    },
                    "email": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/contacts": {
      "get": {
        "tags": [
          "Contacts"
        ],
        "summary": "List contacts",
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 200
            },
            "description": "Page size. When set, the response becomes `{items, nextCursor}`."
          },
          {
            "name": "cursor",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "`nextCursor` from the previous page."
          },
          {
            "name": "status",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "lead",
                "contacted",
                "customer"
              ]
            }
          },
          {
            "name": "source",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "q",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "Text search (name, company, email)."
          },
          {
            "name": "email",
            "in": "query",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "oneOf": [
                    {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Contact"
                      }
                    },
                    {
                      "type": "object",
                      "properties": {
                        "items": {
                          "type": "array",
                          "items": {
                            "$ref": "#/components/schemas/Contact"
                          }
                        },
                        "nextCursor": {
                          "type": [
                            "string",
                            "null"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      },
      "post": {
        "tags": [
          "Contacts"
        ],
        "summary": "Create a contact",
        "description": "Fires contact.created. 409 with `duplicateOf` if the email already exists.",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "schema": {
              "type": "string",
              "maxLength": 200
            },
            "description": "Optional. The same key sent again within 24 h returns the first response instead of creating a duplicate (safe retries from n8n/Make/Zapier)."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/Contact"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Created",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          }
        }
      }
    },
    "/contacts/search": {
      "get": {
        "tags": [
          "Contacts"
        ],
        "summary": "Find a contact by email",
        "description": "Returns an array of 0 or 1 contact. Useful for a “find or create” step in n8n.",
        "parameters": [
          {
            "name": "email",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/Contact"
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/contacts/merge": {
      "post": {
        "tags": [
          "Contacts"
        ],
        "summary": "Merge two contacts",
        "description": "Keeps `keepId`, fills its empty email, phone, company and address from `mergeId`, adds up the deal values, appends `mergeId`'s notes (tagged with its name), then deletes `mergeId`. Its quotes, reminders and appointments are not moved.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "keepId",
                  "mergeId"
                ],
                "properties": {
                  "keepId": {
                    "type": "string"
                  },
                  "mergeId": {
                    "type": "string"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "merged": {
                      "type": "boolean"
                    },
                    "id": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/contacts/{id}": {
      "parameters": [
        {
          "name": "id",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string"
          }
        }
      ],
      "get": {
        "tags": [
          "Contacts"
        ],
        "summary": "Read a contact",
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Contact"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      },
      "put": {
        "tags": [
          "Contacts"
        ],
        "summary": "Update a contact",
        "description": "Send only the fields to change. Fires contact.updated (if a field changes) and status.changed (if the status changes).",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/Contact"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "OK"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      },
      "delete": {
        "tags": [
          "Contacts"
        ],
        "summary": "Delete a contact (administrator)",
        "description": "Deletes the contact, their reminders, appointments and files; their quotes are anonymised. Invoices and credit notes are kept for 6 years (HMRC record-keeping).",
        "responses": {
          "200": {
            "description": "OK"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/contacts/{id}/export": {
      "parameters": [
        {
          "name": "id",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string"
          }
        }
      ],
      "get": {
        "tags": [
          "Contacts"
        ],
        "summary": "Export one person's data (UK GDPR art. 15 and 20)",
        "description": "All the data held about this contact, as machine-readable JSON, to answer an access or portability request.",
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/invoices": {
      "get": {
        "tags": [
          "Quotes and invoices"
        ],
        "summary": "List quotes, invoices and credit notes",
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 200
            },
            "description": "Page size. When set, the response becomes `{items, nextCursor}`."
          },
          {
            "name": "cursor",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "`nextCursor` from the previous page."
          },
          {
            "name": "status",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "draft",
                "sent",
                "accepted",
                "invoiced",
                "paid",
                "overdue",
                "credit_note"
              ]
            }
          },
          {
            "name": "type",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "quote",
                "invoice",
                "credit_note"
              ]
            }
          },
          {
            "name": "contactId",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "q",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "Search by document number."
          },
          {
            "name": "toShip",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "1"
              ]
            },
            "description": "Paid orders with a physical product not yet shipped."
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "oneOf": [
                    {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Invoice"
                      }
                    },
                    {
                      "type": "object",
                      "properties": {
                        "items": {
                          "type": "array",
                          "items": {
                            "$ref": "#/components/schemas/Invoice"
                          }
                        },
                        "nextCursor": {
                          "type": [
                            "string",
                            "null"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      },
      "post": {
        "tags": [
          "Quotes and invoices"
        ],
        "summary": "Create a quote or an invoice",
        "description": "Fires quote.created (quote) or invoice.created (status `invoiced`). Credit notes are created with `/invoices/{id}/credit-note`.\n\n**409 `VAT_STATUS_REQUIRED`** while the business's VAT status is not set. **400** if VAT is charged by a business that is not VAT-registered, on a document dated before the VAT registration date, or on an invoice while no VAT number is recorded.",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "schema": {
              "type": "string",
              "maxLength": 200
            },
            "description": "Optional. The same key sent again within 24 h returns the first response instead of creating a duplicate (safe retries from n8n/Make/Zapier)."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/InvoiceCreate"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Created",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Invoice"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          }
        }
      }
    },
    "/invoices/{id}": {
      "parameters": [
        {
          "name": "id",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string"
          }
        }
      ],
      "delete": {
        "tags": [
          "Quotes and invoices"
        ],
        "summary": "Delete a quote (administrator)",
        "description": "Refused for an issued invoice or credit note (409): they are accounting records kept for 6 years — issue a credit note instead.",
        "responses": {
          "200": {
            "description": "OK"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          }
        }
      }
    },
    "/invoices/{id}/status": {
      "parameters": [
        {
          "name": "id",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string"
          }
        }
      ],
      "put": {
        "tags": [
          "Quotes and invoices"
        ],
        "summary": "Change the status (accept, invoice, mark paid…)",
        "description": "Fires quote.accepted, invoice.created or invoice.paid as relevant. An issued invoice cannot go back to a quote status (its status is ignored), a credit note's status cannot change (409).\n\nConverting a quote into an invoice answers **409 `VAT_STATUS_REQUIRED`** while the VAT status is not set.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/StatusUpdate"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Invoice"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          }
        }
      }
    },
    "/invoices/{id}/credit-note": {
      "parameters": [
        {
          "name": "id",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string"
          }
        }
      ],
      "post": {
        "tags": [
          "Quotes and invoices"
        ],
        "summary": "Issue a credit note for an invoice (administrator)",
        "description": "Cancels the original invoice with a CRN-… credit note that references it. Only one credit note per invoice (409). Fires credit_note.created.",
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "reason": {
                    "type": "string"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Created",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Invoice"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          }
        }
      }
    },
    "/invoices/{id}/send": {
      "parameters": [
        {
          "name": "id",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string"
          }
        }
      ],
      "post": {
        "tags": [
          "Quotes and invoices"
        ],
        "summary": "Send by email",
        "description": "Emails the document's link to the customer (uses the plan's email quota). A draft quote becomes `sent`.",
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "to": {
                    "type": "string"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "OK"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/invoices/{id}/payment-link": {
      "parameters": [
        {
          "name": "id",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string"
          }
        }
      ],
      "post": {
        "tags": [
          "Quotes and invoices"
        ],
        "summary": "Create a Stripe payment link (administrator)",
        "description": "Issued, unpaid invoices only. Requires the company's Stripe secret key (Settings → Integrations); the invoice is marked paid by the company's Stripe webhook.",
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "customerEmail": {
                    "type": "string"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "url": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          }
        }
      }
    },
    "/invoices/{id}/einvoice": {
      "parameters": [
        {
          "name": "id",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string"
          }
        }
      ],
      "get": {
        "tags": [
          "Quotes and invoices"
        ],
        "summary": "Structured e-invoice XML (administrator)",
        "description": "EN 16931 data model, CII syntax: one VAT breakdown per category and rate, CIS reverse charge as category AE, credit notes referencing the original invoice. Not required in the UK today.",
        "responses": {
          "200": {
            "description": "XML file",
            "content": {
              "application/xml": {
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          }
        }
      }
    },
    "/invoices/{id}/ship": {
      "parameters": [
        {
          "name": "id",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string"
          }
        }
      ],
      "post": {
        "tags": [
          "Quotes and invoices"
        ],
        "summary": "Mark an order shipped (administrator)",
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "shipped": {
                    "type": "boolean",
                    "default": true
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "OK"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/products": {
      "get": {
        "tags": [
          "Catalogue"
        ],
        "summary": "List products",
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 200
            },
            "description": "Page size. When set, the response becomes `{items, nextCursor}`."
          },
          {
            "name": "cursor",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "`nextCursor` from the previous page."
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "oneOf": [
                    {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Product"
                      }
                    },
                    {
                      "type": "object",
                      "properties": {
                        "items": {
                          "type": "array",
                          "items": {
                            "$ref": "#/components/schemas/Product"
                          }
                        },
                        "nextCursor": {
                          "type": [
                            "string",
                            "null"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      },
      "post": {
        "tags": [
          "Catalogue"
        ],
        "summary": "Create a product",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "schema": {
              "type": "string",
              "maxLength": 200
            },
            "description": "Optional. The same key sent again within 24 h returns the first response instead of creating a duplicate (safe retries from n8n/Make/Zapier)."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/Product"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Created",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/products/{id}": {
      "parameters": [
        {
          "name": "id",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string"
          }
        }
      ],
      "put": {
        "tags": [
          "Catalogue"
        ],
        "summary": "Update a product",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/Product"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "OK"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          }
        }
      },
      "delete": {
        "tags": [
          "Catalogue"
        ],
        "summary": "Delete a product (administrator)",
        "responses": {
          "200": {
            "description": "OK"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/reminders": {
      "get": {
        "tags": [
          "Reminders"
        ],
        "summary": "List reminders",
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 200
            },
            "description": "Page size. When set, the response becomes `{items, nextCursor}`."
          },
          {
            "name": "cursor",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "`nextCursor` from the previous page."
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "oneOf": [
                    {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Reminder"
                      }
                    },
                    {
                      "type": "object",
                      "properties": {
                        "items": {
                          "type": "array",
                          "items": {
                            "$ref": "#/components/schemas/Reminder"
                          }
                        },
                        "nextCursor": {
                          "type": [
                            "string",
                            "null"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      },
      "post": {
        "tags": [
          "Reminders"
        ],
        "summary": "Create a reminder",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "schema": {
              "type": "string",
              "maxLength": 200
            },
            "description": "Optional. The same key sent again within 24 h returns the first response instead of creating a duplicate (safe retries from n8n/Make/Zapier)."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/Reminder"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Created",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/reminders/{id}": {
      "parameters": [
        {
          "name": "id",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string"
          }
        }
      ],
      "put": {
        "tags": [
          "Reminders"
        ],
        "summary": "Update a reminder",
        "description": "title and date are required.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/Reminder"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "OK"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      },
      "delete": {
        "tags": [
          "Reminders"
        ],
        "summary": "Delete a reminder (administrator)",
        "responses": {
          "200": {
            "description": "OK"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/reminders/{id}/toggle": {
      "parameters": [
        {
          "name": "id",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string"
          }
        }
      ],
      "put": {
        "tags": [
          "Reminders"
        ],
        "summary": "Tick / untick a reminder",
        "responses": {
          "200": {
            "description": "OK"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/appointments": {
      "get": {
        "tags": [
          "Appointments"
        ],
        "summary": "List appointments",
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 200
            },
            "description": "Page size. When set, the response becomes `{items, nextCursor}`."
          },
          {
            "name": "cursor",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "`nextCursor` from the previous page."
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "oneOf": [
                    {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Appointment"
                      }
                    },
                    {
                      "type": "object",
                      "properties": {
                        "items": {
                          "type": "array",
                          "items": {
                            "$ref": "#/components/schemas/Appointment"
                          }
                        },
                        "nextCursor": {
                          "type": [
                            "string",
                            "null"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      },
      "post": {
        "tags": [
          "Appointments"
        ],
        "summary": "Create an appointment",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "schema": {
              "type": "string",
              "maxLength": 200
            },
            "description": "Optional. The same key sent again within 24 h returns the first response instead of creating a duplicate (safe retries from n8n/Make/Zapier)."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/Appointment"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Created",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/appointments/{id}": {
      "parameters": [
        {
          "name": "id",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string"
          }
        }
      ],
      "put": {
        "tags": [
          "Appointments"
        ],
        "summary": "Update an appointment",
        "description": "contactId, date and time are required.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/Appointment"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "OK"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      },
      "delete": {
        "tags": [
          "Appointments"
        ],
        "summary": "Delete an appointment (administrator)",
        "responses": {
          "200": {
            "description": "OK"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/stats/overview": {
      "get": {
        "tags": [
          "Statistics"
        ],
        "summary": "Dashboard figures",
        "description": "Contacts by status; revenue of the month and of the year, net of VAT and gross (paid invoices, refunds deducted, Margin Scheme sales included); rolling 12-month taxable turnover (VAT registration threshold); average sale; quotes in progress; overdue invoices; revenue by month… Invoicing figures are omitted for the Sales role.",
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/workflow/trigger": {
      "post": {
        "tags": [
          "Automations"
        ],
        "summary": "Fire the automations of an event",
        "description": "Runs the Hotorga rules set up on this event, with the data provided (e.g. data.contactId, data.contact, data.invoice).",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "event"
                ],
                "properties": {
                  "event": {
                    "type": "string",
                    "enum": [
                      "contact.created",
                      "contact.updated",
                      "status.changed",
                      "quote.created",
                      "quote.created.manual",
                      "quote.created.auto",
                      "quote.accepted",
                      "quote.viewed_not_signed",
                      "invoice.created",
                      "invoice.paid",
                      "invoice.overdue",
                      "credit_note.created",
                      "lead.inactive"
                    ]
                  },
                  "data": {
                    "type": "object"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/webhooks/in/{companyId}/{token}": {
      "post": {
        "tags": [
          "Incoming webhook"
        ],
        "summary": "Incoming webhook (no API key)",
        "description": "URL generated in Settings → Integrations (shown only once). 30 requests per minute. Two formats: `{name, email, phone, company, notes}` creates a contact (duplicate email → `duplicate: true`); `{event, data}` fires the automations of that event.",
        "security": [],
        "parameters": [
          {
            "name": "companyId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "token",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "oneOf": [
                  {
                    "type": "object",
                    "properties": {
                      "name": {
                        "type": "string"
                      },
                      "email": {
                        "type": "string"
                      },
                      "phone": {
                        "type": "string"
                      },
                      "company": {
                        "type": "string"
                      },
                      "notes": {
                        "type": "string"
                      }
                    }
                  },
                  {
                    "type": "object",
                    "required": [
                      "event"
                    ],
                    "properties": {
                      "event": {
                        "type": "string",
                        "enum": [
                          "contact.created",
                          "contact.updated",
                          "status.changed",
                          "quote.created",
                          "quote.created.manual",
                          "quote.created.auto",
                          "quote.accepted",
                          "quote.viewed_not_signed",
                          "invoice.created",
                          "invoice.paid",
                          "invoice.overdue",
                          "credit_note.created",
                          "lead.inactive"
                        ]
                      },
                      "data": {
                        "type": "object"
                      }
                    }
                  }
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Event processed or duplicate"
          },
          "201": {
            "description": "Contact created"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/export/accounting": {
      "get": {
        "tags": [
          "Exports"
        ],
        "summary": "Accounting export, CSV (administrator)",
        "description": "Invoices and credit notes (by issue date) and subcontractor expenses with CIS deducted; comma separator, decimal point, UTF-8. Columns: customer code, customer name, invoice no, date, description, net amount, VAT rate, VAT amount, gross amount, status, type, paid on, CIS deducted.",
        "responses": {
          "200": {
            "description": "CSV file",
            "content": {
              "text/csv": {
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/orders": {
      "post": {
        "tags": [
          "E-commerce orders"
        ],
        "summary": "Record a website order",
        "description": "Finds or creates the customer (by email), creates the order (QUO quote, then a paid INV invoice, legal numbering), applies UK VAT (standard rate in the UK, zero-rated exports of goods, services to overseas businesses outside the scope), reserves then decrements the stock and adds the order to “Orders to ship”. Idempotent on externalId. Counts in the monthly website orders quota (Pro+: 200, Team: 500, Team+: 1,200, Unlimited). **Test mode**: add `\"test\": true` — everything is checked and calculated but nothing is saved (no customer, invoice, stock or email), and order.created (test: true) goes to your webhook.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "externalId",
                  "customer",
                  "lines"
                ],
                "properties": {
                  "test": {
                    "type": "boolean"
                  },
                  "externalId": {
                    "type": "string",
                    "maxLength": 100
                  },
                  "customer": {
                    "type": "object",
                    "required": [
                      "email"
                    ],
                    "properties": {
                      "email": {
                        "type": "string"
                      },
                      "name": {
                        "type": "string"
                      },
                      "firstName": {
                        "type": "string"
                      },
                      "lastName": {
                        "type": "string"
                      },
                      "phone": {
                        "type": "string"
                      },
                      "company": {
                        "type": "string"
                      },
                      "type": {
                        "type": "string",
                        "enum": [
                          "consumer",
                          "business"
                        ]
                      },
                      "vatNumber": {
                        "type": "string",
                        "description": "Checked with HMRC (GB) or VIES (EU)."
                      },
                      "country": {
                        "type": "string",
                        "description": "2-letter ISO code, default GB."
                      },
                      "address": {
                        "oneOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "object",
                            "properties": {
                              "line1": {
                                "type": "string"
                              },
                              "line2": {
                                "type": "string"
                              },
                              "city": {
                                "type": "string"
                              },
                              "county": {
                                "type": "string"
                              },
                              "postcode": {
                                "type": "string"
                              },
                              "country": {
                                "type": "string"
                              }
                            }
                          }
                        ]
                      },
                      "birthday": {
                        "type": "string",
                        "description": "YYYY-MM-DD or MM-DD (birthday automations)."
                      }
                    }
                  },
                  "lines": {
                    "type": "array",
                    "maxItems": 200,
                    "items": {
                      "type": "object",
                      "properties": {
                        "ref": {
                          "type": "string",
                          "description": "SKU of a catalogue product (or `sku`)."
                        },
                        "productId": {
                          "type": "string"
                        },
                        "name": {
                          "type": "string",
                          "description": "Required when the line is not a catalogue product."
                        },
                        "qty": {
                          "type": "number"
                        },
                        "unitPriceHT": {
                          "type": "number",
                          "description": "Unit price excl. VAT (otherwise unitPriceTTC, otherwise the catalogue price)."
                        },
                        "unitPriceTTC": {
                          "type": "number",
                          "description": "Unit price incl. VAT."
                        },
                        "vatRate": {
                          "type": "number",
                          "description": "UK VAT rate of the line (20, 5, 0). Default: product's rate, then the company's."
                        },
                        "discountPercent": {
                          "type": "number"
                        },
                        "type": {
                          "type": "string",
                          "enum": [
                            "product",
                            "service"
                          ]
                        },
                        "shipping": {
                          "type": "boolean",
                          "description": "false for a digital product."
                        }
                      }
                    }
                  },
                  "shipping": {
                    "type": "object",
                    "properties": {
                      "priceHT": {
                        "type": "number"
                      },
                      "priceTTC": {
                        "type": "number"
                      },
                      "vatRate": {
                        "type": "number"
                      },
                      "label": {
                        "type": "string"
                      }
                    }
                  },
                  "shippingAddress": {
                    "oneOf": [
                      {
                        "type": "string"
                      },
                      {
                        "type": "object",
                        "properties": {
                          "line1": {
                            "type": "string"
                          },
                          "line2": {
                            "type": "string"
                          },
                          "city": {
                            "type": "string"
                          },
                          "county": {
                            "type": "string"
                          },
                          "postcode": {
                            "type": "string"
                          },
                          "country": {
                            "type": "string"
                          }
                        }
                      }
                    ]
                  },
                  "notes": {
                    "type": "string"
                  },
                  "discount": {
                    "type": "object",
                    "properties": {
                      "type": {
                        "type": "string",
                        "enum": [
                          "amount",
                          "percent"
                        ]
                      },
                      "value": {
                        "type": "number"
                      },
                      "amountTTC": {
                        "type": "number"
                      },
                      "label": {
                        "type": "string"
                      },
                      "includeShipping": {
                        "type": "boolean"
                      }
                    }
                  },
                  "promoCode": {
                    "type": "string",
                    "description": "Promo code managed in Hotorga (Catalogue → Promo codes)."
                  },
                  "expectedTotalTTC": {
                    "type": "number",
                    "description": "Your total incl. VAT: refused (422) if it differs by more than 5p."
                  },
                  "payment": {
                    "type": "object",
                    "properties": {
                      "status": {
                        "type": "string",
                        "enum": [
                          "paid",
                          "pending"
                        ]
                      },
                      "mode": {
                        "type": "string",
                        "enum": [
                          "stripe"
                        ],
                        "description": "Payment link on the company's Stripe account (successUrl and cancelUrl required)."
                      },
                      "paidAt": {
                        "type": "string"
                      },
                      "method": {
                        "type": "string"
                      },
                      "transactionId": {
                        "type": "string"
                      },
                      "successUrl": {
                        "type": "string"
                      },
                      "cancelUrl": {
                        "type": "string"
                      }
                    }
                  },
                  "reserveStock": {
                    "type": "boolean"
                  },
                  "reserveMinutes": {
                    "type": "integer",
                    "minimum": 10,
                    "maximum": 1440
                  },
                  "sendInvoice": {
                    "type": "boolean",
                    "description": "Email the paid invoice to the customer (default true)."
                  },
                  "sendConfirmation": {
                    "type": "boolean"
                  },
                  "cartId": {
                    "type": "string"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Order",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Order"
                }
              }
            }
          },
          "200": {
            "description": "Duplicate (same externalId) or test order",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Order"
                }
              }
            }
          },
          "422": {
            "description": "Invalid line, price, VAT, promo code or total"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      },
      "get": {
        "tags": [
          "E-commerce orders"
        ],
        "summary": "List website orders",
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "maximum": 100
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "updatedSince",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "date-time"
            },
            "description": "Orders changed since (oldest first) — for synchronisation."
          },
          {
            "name": "status",
            "in": "query",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Page",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "items": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Order"
                      }
                    },
                    "nextCursor": {
                      "type": "string",
                      "nullable": true
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      }
    },
    "/orders/{externalId}": {
      "get": {
        "tags": [
          "E-commerce orders"
        ],
        "summary": "Get one order",
        "parameters": [
          {
            "name": "externalId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "maxLength": 100
            },
            "description": "Your website's order id (URL-encoded)."
          }
        ],
        "responses": {
          "200": {
            "description": "Order",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Order"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/orders/{externalId}/paid": {
      "post": {
        "tags": [
          "E-commerce orders"
        ],
        "summary": "Confirm a payment received by your website",
        "parameters": [
          {
            "name": "externalId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "maxLength": 100
            },
            "description": "Your website's order id (URL-encoded)."
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "paidAt": {
                    "type": "string"
                  },
                  "method": {
                    "type": "string"
                  },
                  "transactionId": {
                    "type": "string"
                  }
                }
              }
            }
          }
        },
        "description": "The quote becomes a paid INV invoice (stock decremented, reservation released), the invoice is emailed and order.paid is sent to your webhook.",
        "responses": {
          "200": {
            "description": "Order",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Order"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/orders/{externalId}/shipped": {
      "post": {
        "tags": [
          "E-commerce orders"
        ],
        "summary": "Record a shipment (one or several parcels)",
        "parameters": [
          {
            "name": "externalId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "maxLength": 100
            },
            "description": "Your website's order id (URL-encoded)."
          }
        ],
        "description": "UK carriers with an automatic tracking link: Royal Mail, Parcelforce, DPD, Evri, Yodel, UPS, DHL, FedEx (or your own https trackingUrl). The customer receives the dispatch email with “Track my parcel”. Refused while a Stripe dispute is open.",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "carrier": {
                    "type": "string"
                  },
                  "trackingNumber": {
                    "type": "string"
                  },
                  "trackingUrl": {
                    "type": "string"
                  },
                  "notifyCustomer": {
                    "type": "boolean"
                  },
                  "lines": {
                    "type": "array",
                    "items": {
                      "type": "object",
                      "properties": {
                        "ref": {
                          "type": "string"
                        },
                        "productId": {
                          "type": "string"
                        },
                        "lineIndex": {
                          "type": "integer"
                        },
                        "qty": {
                          "type": "number"
                        }
                      }
                    },
                    "description": "Partial shipment; omitted = everything left to ship."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Order",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Order"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          }
        }
      }
    },
    "/orders/{externalId}/status": {
      "post": {
        "tags": [
          "E-commerce orders"
        ],
        "summary": "Set a manual status",
        "parameters": [
          {
            "name": "externalId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "maxLength": 100
            },
            "description": "Your website's order id (URL-encoded)."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "status"
                ],
                "properties": {
                  "status": {
                    "type": "string",
                    "enum": [
                      "processing",
                      "on_hold",
                      "delivered",
                      "returned"
                    ]
                  },
                  "note": {
                    "type": "string"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Order",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Order"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          }
        }
      }
    },
    "/orders/{externalId}/refund": {
      "post": {
        "tags": [
          "E-commerce orders"
        ],
        "summary": "Refund all or part of a paid order",
        "description": "Issues a CRN credit note on the refunded lines only (UK VAT reversed line by line), returns the stock (restock, default true) and sends order.refunded. The money itself is returned by your website or from the Stripe dashboard.",
        "parameters": [
          {
            "name": "externalId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "maxLength": 100
            },
            "description": "Your website's order id (URL-encoded)."
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "full": {
                    "type": "boolean"
                  },
                  "lines": {
                    "type": "array",
                    "items": {
                      "type": "object",
                      "properties": {
                        "ref": {
                          "type": "string"
                        },
                        "productId": {
                          "type": "string"
                        },
                        "lineIndex": {
                          "type": "integer"
                        },
                        "qty": {
                          "type": "number"
                        }
                      }
                    }
                  },
                  "shipping": {
                    "type": "boolean"
                  },
                  "restock": {
                    "type": "boolean"
                  },
                  "reason": {
                    "type": "string"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Order",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Order"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          }
        }
      }
    },
    "/orders/{externalId}/cancel": {
      "post": {
        "tags": [
          "E-commerce orders"
        ],
        "summary": "Cancel an unpaid order",
        "parameters": [
          {
            "name": "externalId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "maxLength": 100
            },
            "description": "Your website's order id (URL-encoded)."
          }
        ],
        "description": "Closes the Stripe payment link, releases the reserved stock and deletes the quote. A paid order is refunded instead.",
        "responses": {
          "200": {
            "description": "Cancelled"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          }
        }
      }
    },
    "/promo-codes": {
      "get": {
        "tags": [
          "E-commerce orders"
        ],
        "summary": "List promo codes (with their number of uses)",
        "responses": {
          "200": {
            "description": "OK"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      },
      "post": {
        "tags": [
          "E-commerce orders"
        ],
        "summary": "Create or replace a promo code",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "code",
                  "type",
                  "value"
                ],
                "properties": {
                  "code": {
                    "type": "string",
                    "pattern": "^[A-Z0-9_-]{2,40}$"
                  },
                  "type": {
                    "type": "string",
                    "enum": [
                      "amount",
                      "percent"
                    ],
                    "description": "amount = £ incl. VAT"
                  },
                  "value": {
                    "type": "number"
                  },
                  "label": {
                    "type": "string"
                  },
                  "active": {
                    "type": "boolean"
                  },
                  "includeShipping": {
                    "type": "boolean"
                  },
                  "maxUses": {
                    "type": "integer"
                  },
                  "minOrderTTC": {
                    "type": "number"
                  },
                  "expiresAt": {
                    "type": "string",
                    "format": "date-time"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Saved"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      }
    },
    "/promo-codes/{code}": {
      "delete": {
        "tags": [
          "E-commerce orders"
        ],
        "summary": "Delete a promo code",
        "parameters": [
          {
            "name": "code",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Deleted"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      }
    },
    "/carts": {
      "post": {
        "tags": [
          "E-commerce orders"
        ],
        "summary": "Report a basket (abandoned basket reminder)",
        "description": "With no order after the delay (2 hours by default), the customer is emailed “Your basket is waiting” from your mailbox (unsubscribe link, optional promo code) and the “Basket abandoned” automation fires. An order with this cartId cancels the reminder.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "cartId",
                  "customer",
                  "cartUrl",
                  "lines"
                ],
                "properties": {
                  "cartId": {
                    "type": "string"
                  },
                  "customer": {
                    "type": "object",
                    "properties": {
                      "email": {
                        "type": "string"
                      },
                      "name": {
                        "type": "string"
                      }
                    }
                  },
                  "cartUrl": {
                    "type": "string",
                    "description": "https address that reopens the basket"
                  },
                  "lines": {
                    "type": "array",
                    "items": {
                      "type": "object",
                      "properties": {
                        "name": {
                          "type": "string"
                        },
                        "ref": {
                          "type": "string"
                        },
                        "qty": {
                          "type": "number"
                        }
                      }
                    }
                  },
                  "totalTTC": {
                    "type": "number"
                  },
                  "reminderDelayMinutes": {
                    "type": "integer",
                    "minimum": 30,
                    "maximum": 2880
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Recorded"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          }
        }
      }
    },
    "/carts/{cartId}": {
      "delete": {
        "tags": [
          "E-commerce orders"
        ],
        "summary": "Cancel a basket reminder",
        "parameters": [
          {
            "name": "cartId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Cancelled"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      }
    },
    "/products/bulk": {
      "post": {
        "tags": [
          "Catalogue"
        ],
        "summary": "Import / sync products in bulk",
        "description": "200 products maximum per call. A product whose SKU (ref) already exists is updated (price, VAT, stock…), otherwise created. An identical product is not rewritten (counted in `unchanged`).",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "products"
                ],
                "properties": {
                  "products": {
                    "type": "array",
                    "maxItems": 200,
                    "items": {
                      "type": "object",
                      "properties": {
                        "ref": {
                          "type": "string"
                        },
                        "name": {
                          "type": "string"
                        },
                        "description": {
                          "type": "string"
                        },
                        "price": {
                          "type": "number",
                          "description": "Unit price excl. VAT"
                        },
                        "tva": {
                          "type": "number",
                          "description": "UK VAT rate (20, 5, 0)"
                        },
                        "productType": {
                          "type": "string",
                          "enum": [
                            "product",
                            "service"
                          ]
                        },
                        "needsShipping": {
                          "type": "boolean"
                        },
                        "trackStock": {
                          "type": "boolean"
                        },
                        "stockQuantity": {
                          "type": "integer"
                        },
                        "stockAlertThreshold": {
                          "type": "integer"
                        },
                        "parentRef": {
                          "type": "string",
                          "description": "SKU of the parent product (variant)"
                        },
                        "options": {
                          "type": "object",
                          "description": "Variant options, e.g. {\"Size\":\"M\",\"Colour\":\"Blue\"} (5 max)"
                        }
                      }
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Result: created, updated, unchanged, failed, items"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "bearerAuth": {
        "type": "http",
        "scheme": "bearer",
        "description": "API key `hot_sk_…` created in Settings → Integrations → API."
      }
    },
    "responses": {
      "BadRequest": {
        "description": "Invalid data",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "Unauthorized": {
        "description": "Key missing, invalid or revoked",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "Forbidden": {
        "description": "Not allowed with an API key, or plan quota reached",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "NotFound": {
        "description": "Not found",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "Conflict": {
        "description": "Conflict (duplicate, accounting record that cannot change, VAT status not set…)",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "TooManyRequests": {
        "description": "Too many requests: retry after the `Retry-After` delay",
        "headers": {
          "Retry-After": {
            "schema": {
              "type": "integer"
            }
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      }
    },
    "schemas": {
      "Error": {
        "type": "object",
        "required": [
          "error"
        ],
        "properties": {
          "error": {
            "type": "string",
            "description": "Message in English, displayable as-is."
          },
          "code": {
            "type": "string",
            "description": "Present on some errors, e.g. `VAT_STATUS_REQUIRED`.",
            "enum": [
              "VAT_STATUS_REQUIRED"
            ]
          },
          "duplicateOf": {
            "type": "string",
            "description": "On 409 for a duplicate: id of the existing record."
          }
        }
      },
      "Note": {
        "type": "object",
        "properties": {
          "text": {
            "type": "string"
          },
          "type": {
            "type": "string",
            "example": "note"
          },
          "date": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "Contact": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "readOnly": true
          },
          "name": {
            "type": "string",
            "maxLength": 300
          },
          "email": {
            "type": "string",
            "description": "Normalised to lower case. Unique per company (409 with `duplicateOf` otherwise)."
          },
          "phone": {
            "type": "string"
          },
          "company": {
            "type": "string"
          },
          "jobTitle": {
            "type": "string"
          },
          "activity": {
            "type": "string"
          },
          "city": {
            "type": "string"
          },
          "address": {
            "type": "string",
            "description": "Postal address — printed on VAT invoices."
          },
          "linkedin": {
            "type": "string"
          },
          "status": {
            "type": "string",
            "enum": [
              "lead",
              "contacted",
              "customer"
            ]
          },
          "source": {
            "type": "string",
            "description": "e.g. manual, chatbot, webhook, import…"
          },
          "score": {
            "type": "string",
            "enum": [
              "cold",
              "warm",
              "hot"
            ],
            "description": "Lead temperature."
          },
          "value": {
            "type": "number",
            "description": "Estimated deal value in £. Ignored for the Sales role."
          },
          "assignedTo": {
            "type": "string",
            "description": "Email of the team member in charge."
          },
          "notes": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Note"
            }
          },
          "createdAt": {
            "type": "string",
            "format": "date-time",
            "readOnly": true
          },
          "lastInteraction": {
            "type": "string",
            "format": "date-time",
            "readOnly": true
          }
        }
      },
      "LineItem": {
        "type": "object",
        "properties": {
          "description": {
            "type": "string"
          },
          "qty": {
            "type": "number",
            "minimum": 0
          },
          "priceHT": {
            "type": "number",
            "minimum": 0,
            "description": "Unit price excluding VAT."
          },
          "tva": {
            "type": "number",
            "description": "VAT rate of the line in % (0, 5, 20). 0 is kept as 0."
          },
          "remise": {
            "type": "number",
            "minimum": 0,
            "maximum": 100,
            "description": "Discount in %."
          },
          "ecoTax": {
            "type": "number",
            "description": "Unit recycling / environmental levy, excluding VAT (optional)."
          },
          "productId": {
            "type": "string",
            "description": "Catalogue product (stock decremented when the document becomes an invoice)."
          },
          "marginVat": {
            "type": "boolean",
            "description": "VAT Margin Scheme line (second-hand goods, VAT Notice 718): use sellPriceTTC instead of priceHT; no VAT is shown."
          },
          "sellPriceTTC": {
            "type": "number",
            "description": "Selling price including VAT (Margin Scheme lines only)."
          }
        }
      },
      "Invoice": {
        "type": "object",
        "description": "Quote (QUO-…), invoice (INV-…) or credit note (CRN-…, invoiceType 381). A quote converted into an invoice keeps the same id; its quote number is kept in `quoteNumber`.",
        "properties": {
          "id": {
            "type": "string",
            "readOnly": true
          },
          "invoiceNumber": {
            "type": "string",
            "readOnly": true,
            "example": "INV-2026-0042"
          },
          "invoiceType": {
            "type": "string",
            "enum": [
              "380",
              "381"
            ],
            "readOnly": true,
            "description": "380 = invoice/quote, 381 = credit note."
          },
          "status": {
            "type": "string",
            "enum": [
              "draft",
              "sent",
              "accepted",
              "invoiced",
              "paid",
              "overdue",
              "credit_note"
            ]
          },
          "contactId": {
            "type": "string"
          },
          "contactName": {
            "type": "string",
            "readOnly": true
          },
          "contactEmail": {
            "type": "string",
            "readOnly": true
          },
          "description": {
            "type": "string"
          },
          "lineItems": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/LineItem"
            }
          },
          "amountHT": {
            "type": "number",
            "description": "Net total, excluding Margin Scheme lines (computed by the server from lineItems)."
          },
          "tvaAmount": {
            "type": "number",
            "description": "VAT total. With the CIS reverse charge it is shown for the customer to account for, but not added to amountTTC."
          },
          "amountTTC": {
            "type": "number",
            "description": "Amount due."
          },
          "marginTotal": {
            "type": "number",
            "description": "Margin Scheme lines, VAT included."
          },
          "tvaRate": {
            "type": "number"
          },
          "hasMixedVatRates": {
            "type": "boolean",
            "readOnly": true
          },
          "vatCategoryCode": {
            "type": "string",
            "enum": [
              "S",
              "Z",
              "E",
              "O",
              "G",
              "AE"
            ],
            "description": "S standard/reduced, Z zero-rated, E exempt, O outside the scope of VAT, G zero-rated export, AE domestic reverse charge (set automatically with cisReverseCharge)."
          },
          "cisReverseCharge": {
            "type": "boolean",
            "description": "Construction (CIS) domestic reverse charge, s.55A VATA 1994."
          },
          "b2cConsumer": {
            "type": "boolean",
            "description": "Customer is a consumer: no Late Payment Act interest; cancellation-rights notice printed."
          },
          "dueDate": {
            "type": "string",
            "format": "date"
          },
          "invoicedAt": {
            "type": "string",
            "format": "date-time",
            "readOnly": true,
            "description": "Issue date / tax point of an invoice."
          },
          "quoteNumber": {
            "type": "string",
            "readOnly": true,
            "description": "Number of the quote this invoice was issued from."
          },
          "paidAt": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "readOnly": true
          },
          "originalInvoiceId": {
            "type": "string",
            "readOnly": true
          },
          "originalInvoiceNumber": {
            "type": "string",
            "readOnly": true,
            "description": "On a credit note: the invoice it corrects."
          },
          "needsShipping": {
            "type": "boolean",
            "readOnly": true
          },
          "shipped": {
            "type": "boolean",
            "readOnly": true
          },
          "createdAt": {
            "type": "string",
            "format": "date-time",
            "readOnly": true
          }
        }
      },
      "InvoiceCreate": {
        "type": "object",
        "required": [
          "contactId"
        ],
        "properties": {
          "contactId": {
            "type": "string"
          },
          "status": {
            "type": "string",
            "enum": [
              "draft",
              "sent",
              "accepted",
              "invoiced"
            ],
            "description": "`invoiced` creates an invoice INV-… straight away; otherwise a quote QUO-…."
          },
          "description": {
            "type": "string"
          },
          "lineItems": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/LineItem"
            },
            "description": "Recommended: totals are computed by the server."
          },
          "amountHT": {
            "type": "number",
            "minimum": 0,
            "description": "Only used without lineItems."
          },
          "tvaRate": {
            "type": "number",
            "description": "Only used without lineItems (20 by default)."
          },
          "dueDate": {
            "type": "string",
            "format": "date"
          },
          "vatCategoryCode": {
            "type": "string",
            "enum": [
              "S",
              "Z",
              "E",
              "O",
              "G",
              "AE"
            ]
          },
          "cisReverseCharge": {
            "type": "boolean"
          },
          "b2cConsumer": {
            "type": "boolean"
          }
        }
      },
      "StatusUpdate": {
        "type": "object",
        "properties": {
          "status": {
            "type": "string",
            "enum": [
              "draft",
              "sent",
              "accepted",
              "invoiced",
              "paid",
              "overdue"
            ],
            "description": "A quote moved to invoiced, paid or overdue becomes an invoice (INV number assigned, stock decremented). An issued invoice never goes back to a quote status; a credit note's status cannot change."
          },
          "paidAt": {
            "type": "string",
            "format": "date-time",
            "description": "Payment date (status `paid`), not in the future. Defaults to now."
          },
          "convertToInvoice": {
            "type": "boolean",
            "description": "Converts an accepted quote into an invoice."
          },
          "lineItems": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/LineItem"
            },
            "description": "Changes the lines (quotes; issued invoices only in quantity, stock follows the difference)."
          },
          "cisReverseCharge": {
            "type": "boolean"
          },
          "vatCategoryCode": {
            "type": "string",
            "enum": [
              "S",
              "Z",
              "E",
              "O",
              "G",
              "AE"
            ]
          },
          "dueDate": {
            "type": "string",
            "format": "date"
          }
        }
      },
      "Product": {
        "type": "object",
        "required": [
          "name"
        ],
        "properties": {
          "id": {
            "type": "string",
            "readOnly": true
          },
          "name": {
            "type": "string"
          },
          "description": {
            "type": "string"
          },
          "price": {
            "type": "number",
            "minimum": 0,
            "description": "Unit price excluding VAT."
          },
          "tva": {
            "type": "number",
            "description": "VAT rate in % (0 kept as 0)."
          },
          "ref": {
            "type": "string"
          },
          "ecoTax": {
            "type": "number"
          },
          "productType": {
            "type": "string",
            "enum": [
              "product",
              "service"
            ]
          },
          "needsShipping": {
            "type": "boolean",
            "description": "Products only: appears in “Orders to ship” once paid."
          },
          "trackStock": {
            "type": "boolean",
            "description": "Stock tracking (depends on the plan)."
          },
          "stockQuantity": {
            "type": [
              "integer",
              "null"
            ]
          },
          "stockQuantityWas": {
            "type": [
              "string",
              "integer"
            ],
            "writeOnly": true,
            "description": "PUT only: stock shown when you read the product. If a sale changed it since, the update is refused (409) instead of overwriting it."
          },
          "paymentLink": {
            "type": "string",
            "description": "Permanent Stripe Payment Link (https://), offered by the chatbot."
          },
          "createdAt": {
            "type": "string",
            "format": "date-time",
            "readOnly": true
          }
        }
      },
      "Reminder": {
        "type": "object",
        "required": [
          "title",
          "date"
        ],
        "properties": {
          "id": {
            "type": "string",
            "readOnly": true
          },
          "title": {
            "type": "string"
          },
          "description": {
            "type": "string"
          },
          "date": {
            "type": "string",
            "description": "ISO 8601 date."
          },
          "contactId": {
            "type": [
              "string",
              "null"
            ]
          },
          "done": {
            "type": "boolean",
            "readOnly": true
          },
          "createdAt": {
            "type": "string",
            "format": "date-time",
            "readOnly": true
          }
        }
      },
      "Appointment": {
        "type": "object",
        "required": [
          "contactId",
          "date",
          "time"
        ],
        "properties": {
          "id": {
            "type": "string",
            "readOnly": true
          },
          "contactId": {
            "type": "string"
          },
          "contactName": {
            "type": "string",
            "readOnly": true
          },
          "title": {
            "type": "string"
          },
          "date": {
            "type": "string",
            "format": "date",
            "example": "2026-10-15"
          },
          "time": {
            "type": "string",
            "example": "14:30"
          },
          "notes": {
            "type": "string"
          },
          "createdAt": {
            "type": "string",
            "format": "date-time",
            "readOnly": true
          }
        }
      },
      "WebhookEvent": {
        "type": "object",
        "description": "Body sent by the “Outgoing webhook” action of an automation.",
        "properties": {
          "event": {
            "type": "string",
            "enum": [
              "contact.created",
              "contact.updated",
              "status.changed",
              "quote.created",
              "quote.created.manual",
              "quote.created.auto",
              "quote.accepted",
              "quote.viewed_not_signed",
              "invoice.created",
              "invoice.paid",
              "invoice.overdue",
              "credit_note.created",
              "lead.inactive"
            ]
          },
          "data": {
            "type": "object",
            "description": "Depending on the event: contact, contactId, invoice, previousStatus (status.changed), changedFields (contact.updated), originalInvoice (credit_note.created).",
            "properties": {
              "contactId": {
                "type": "string"
              },
              "contact": {
                "$ref": "#/components/schemas/Contact"
              },
              "invoice": {
                "$ref": "#/components/schemas/Invoice"
              },
              "previousStatus": {
                "type": "string"
              },
              "changedFields": {
                "type": "array",
                "items": {
                  "type": "string"
                }
              },
              "originalInvoice": {
                "type": "object",
                "properties": {
                  "id": {
                    "type": "string"
                  },
                  "invoiceNumber": {
                    "type": "string"
                  }
                }
              }
            }
          }
        }
      },
      "Order": {
        "type": "object",
        "properties": {
          "externalId": {
            "type": "string"
          },
          "invoiceId": {
            "type": "string"
          },
          "number": {
            "type": "string",
            "description": "QUO-… while awaiting payment, INV-… once paid."
          },
          "quoteNumber": {
            "type": "string"
          },
          "status": {
            "type": "string",
            "enum": [
              "awaiting_payment",
              "paid"
            ]
          },
          "paid": {
            "type": "boolean"
          },
          "orderStatus": {
            "type": "string",
            "enum": [
              "awaiting_payment",
              "paid",
              "processing",
              "on_hold",
              "partially_shipped",
              "shipped",
              "delivered",
              "partially_refunded",
              "refunded",
              "returned",
              "cancelled"
            ]
          },
          "statusHistory": {
            "type": "array",
            "items": {
              "type": "object"
            }
          },
          "refunds": {
            "type": "array",
            "items": {
              "type": "object"
            }
          },
          "refundedTTC": {
            "type": "number"
          },
          "amountHT": {
            "type": "number"
          },
          "tvaAmount": {
            "type": "number"
          },
          "amountTTC": {
            "type": "number"
          },
          "currency": {
            "type": "string",
            "enum": [
              "GBP"
            ]
          },
          "discount": {
            "type": "object",
            "nullable": true
          },
          "stockReservedUntil": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "paymentUrl": {
            "type": "string",
            "nullable": true,
            "description": "Stripe payment link (payment.mode \"stripe\")."
          },
          "paymentMethod": {
            "type": "string",
            "nullable": true
          },
          "paidAt": {
            "type": "string",
            "nullable": true
          },
          "createdAt": {
            "type": "string"
          },
          "updatedAt": {
            "type": "string"
          },
          "needsShipping": {
            "type": "boolean"
          },
          "shipped": {
            "type": "boolean"
          },
          "shippedAt": {
            "type": "string",
            "nullable": true
          },
          "shipments": {
            "type": "array",
            "items": {
              "type": "object"
            }
          },
          "contactId": {
            "type": "string"
          },
          "documentUrl": {
            "type": "string",
            "nullable": true
          }
        }
      }
    }
  },
  "webhooks": {
    "automationEvent": {
      "post": {
        "summary": "Event sent by an “Outgoing webhook” automation action",
        "description": "Signed with `X-Hotorga-Signature-V2` (see the introduction).",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/WebhookEvent"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Return any 2xx to acknowledge."
          }
        }
      }
    }
  }
}