Proposals

Proposals are documents that pitch work to a client, with the fee packages they can accept or choose between.

A proposal is made of ordered sections. A section is either freeform text, stored as Quill delta operations, or a fees section: one or more priced packages the client sees and, when the section allows a choice, picks from. Any section may require the client's signature.

Get Proposals

GET /api/v2/proposals

Accepts status (draft, sent, viewed, signed, voided), from_date, and to_date, by proposal date.

Templates are not included; only proposals issued to clients. Each row carries its sections, as in the single-proposal response below.

{
  "total_count": 3,
  "page_count": 1,
  "page_size": 20,
  "proposals": [
    {
      "id": 1,
      "subject": "Lawn refresh agreement",
      "status": "draft",
      "date": "2026-09-11",
      "client_id": 12,
      "project_id": null,
      "created_at": "2026-09-11T16:10:59Z",
      "updated_at": "2026-09-11T16:10:59Z"
    },
    ...
  ]
}
GET /api/v2/clients/:client_id/proposals

Proposals are also a nested resource for Clients

Get A Proposal

GET /api/v2/proposals/:id

url is the proposal in the app. client_view_url is the client's own view, no login needed, and pdf_url is its PDF. Both keyed links can be shared as they stand.

{
  "id": 1,
  "subject": "Lawn refresh agreement",
  "status": "draft",
  "date": "2026-09-11",
  "client_id": 12,
  "url": "https://yourdomain.clientary.com/proposals/1",
  "client_view_url": "https://yourdomain.clientary.com/proposals/1/client_view/k-abc123",
  "pdf_url": "https://yourdomain.clientary.com/proposals/1/pdf/k-abc123.pdf",
  "contract_sections": [
    {
      "id": 10,
      "position": 0,
      "section_type": "freeform",
      "content": [
        { "insert": "Scope" },
        { "insert": "\n", "attributes": { "header": 2 } },
        { "insert": "Remove old sod and lay new turf.\n" }
      ]
    },
    {
      "id": 11,
      "position": 1,
      "section_type": "fees",
      "content": {
        "type": "static",
        "title": "Fee",
        "instructions": [ { "insert": "\n" } ],
        "options": [
          {
            "title": "",
            "description": "",
            "line_items": [
              { "item_type": "service", "title": "Lawn refresh", "quantity": 1, "price": 5000, "taxable": false }
            ]
          }
        ]
      }
    }
  ]
}

Create A New Proposal

POST /api/v2/proposals
{
  "proposal": {
    "client_id": 12,
    "subject": "Lawn refresh agreement",
    "date": "2026-09-11",
    "contract_sections_attributes": [
      {
        "position": 0,
        "section_type": "freeform",
        "content": [ { "insert": "Remove old sod and lay new turf.\n" } ]
      },
      {
        "position": 1,
        "section_type": "fees",
        "content": {
          "type": "static",
          "title": "Fee",
          "instructions": [ { "insert": "\n" } ],
          "options": [
            {
              "line_items": [
                { "item_type": "service", "title": "Lawn refresh", "quantity": 1, "price": 5000, "taxable": false }
              ]
            }
          ]
        }
      }
    ]
  }
}

Required Fields: client_id, date, and at least one section or a template_id (HTTP 422 on failure)
From a template: pass template_id beside the document to start from one of the account's templates or a built-in default. See Templates.
Optional Fields: settings_auto_create_invoice_after_sign - when the client signs, an invoice for what they chose is created and sent to them automatically

Freeform sections

content is an array of Quill delta operations. Plain text is one insert ending in a newline. A wrapped form, { "ops": [...] }, is accepted as well.

Fees sections

content is an object:

type: static shows the packages. single lets the client choose one package; multi lets them choose several. Both choice types need at least two options, each with a title.
title: Heading shown above the packages.
instructions: Quill delta operations shown above the packages; [ { "insert": "\n" } ] for none.
options: The packages. Each has an optional title and description and a required line_items array.
line_items[].item_type: service for a priced line, header for a label with no amount.
line_items[].title: Required.
line_items[].quantity, price: Required for a service line.
line_items[].taxable: Required, true or false.

A package may also carry currency_code, tax, tax_label, tax2, tax2_label, tax2_enabled, and compound_tax.

Update A Proposal

PUT /api/v2/proposals/:id

You may provide a partial list of fields to update. Sections are updated through contract_sections_attributes: include an id to change an existing section, omit it to add one, or send "_destroy": true with an id to remove one. A proposal the client has signed can no longer be changed.

{
  "proposal": {
    "subject": "Lawn refresh agreement, revised",
    "contract_sections_attributes": [
      { "id": 10, "_destroy": true },
      { "position": 0, "section_type": "freeform", "content": [ { "insert": "Revised scope.\n" } ] }
    ]
  }
}

Delete A Proposal

DELETE /api/v2/proposals/:id

Deletions are permanent and not reversible.

Send A Proposal

POST /api/v2/proposals/:id/messages
{
  "recipients": ["abc@example.com", "xyz@example.com"],
  "subject": "Subject for the proposal",
  "message": "Body message for the proposal",
  "send_copy": 1,
  "attach_pdf": 1
}

Required Fields: recipients - an array of email addresses. (HTTP 422 on failure)
Encouraged Fields: subject, message are strongly encouraged, since this is what displays in the email, but it is not required.
Optional Fields: send_copy optionally allows the sending user (dependent on API Token used) to receive a copy of the email, attach_pdf allows an optional PDF to be attached.

An address that is not yet a contact of the client becomes one. Each recipient gets their own link to view and sign it. Sending marks the proposal sent.