Contracts

Contracts are documents a client reads and signs, such as service agreements and terms of engagement.

A contract 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 Contracts

GET /api/v2/contracts

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

{
  "total_count": 3,
  "page_count": 1,
  "page_size": 20,
  "contracts": [
    {
      "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/contracts

Contracts are also a nested resource for Clients

Get A Contract

GET /api/v2/contracts/:id

url is the contract 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/contracts/1",
  "client_view_url": "https://yourdomain.clientary.com/contracts/1/client_view/k-abc123",
  "pdf_url": "https://yourdomain.clientary.com/contracts/1/pdf/k-abc123.pdf",
  "contract_sections": [
    {
      "id": 10,
      "position": 0,
      "section_type": "freeform",
      "requires_signature": false,
      "content": [
        { "insert": "Scope" },
        { "insert": "\n", "attributes": { "header": 2 } },
        { "insert": "Remove old sod and lay new turf.\n" }
      ]
    },
    {
      "id": 11,
      "position": 1,
      "section_type": "fees",
      "requires_signature": true,
      "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 Contract

POST /api/v2/contracts
{
  "contract": {
    "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",
        "requires_signature": true,
        "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 (HTTP 422 on failure)

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 Contract

PUT /api/v2/contracts/: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 contract the client has signed can no longer be changed.

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

Delete A Contract

DELETE /api/v2/contracts/:id

Deletions are permanent and not reversible.