# Warranty

TruTec's warranty program has three parts:

- A **warranty packet** is the customer's warranty document for one job: a certificate listing each covered item (for example *Sealcoating, 6,300 SY, 12 months*) and when its coverage ends. When a job reaches a completed stage, TruTec makes a **draft** packet and reminds the office to send it. Sending emails the customer a link to their certificate.
- A **work notice** tells the customer that a repair is being done under their warranty. TruTec decides the wording from the coverage: if the item has expired, or the job has no live coverage, the notice says the work is done at no charge instead (`kind` `goodwill`).
- The **tracker** lists every item on a sent packet, soonest to end first, as `active`, `expiring` (ends within 90 days) or `expired`.

Packets are built from a **warranty template**: the document text with merge fields, standard coverage items, and rules that set each sold service's term (for example any service with "Crack" in its name gets 6 months). Rows come from the job's sold estimate when it has one, otherwise from the template's standard items. Templates are edited in TruTec (Warranty > Templates).

> **These endpoints can email your customer.** `POST /v1/jobs/{job_id}/warranty/send`, `POST /v1/warranty-packets/{packet_id}/send` and `POST /v1/jobs/{job_id}/warranty/send-notice` email the customer from the sending person's connected mailbox, exactly like Send in TruTec. Preview first with `POST /v1/jobs/{job_id}/warranty/preview`. Nothing else here emails anyone.

Sends and voids act as the person who created the API key (a partner key acts as the workspace owner). Crew logins can't send or void warranty documents.

## The packet object

| Field | Type | Description |
|---|---|---|
| `id`, `job_id`, `template_id` | integer | |
| `status` | string | `draft` (made when the job was completed, waiting to be sent), `sent` or `void`. |
| `name`, `customer_name` | string | |
| `effective_date` | string | When coverage starts (`YYYY-MM-DD`). |
| `sent_at`, `sent_to_email` | | Last send. `sent_to_email` is null when it was shared as a link only. |
| `public_url` | string or null | The customer's certificate link while the packet is sent. Share it yourself when no email went out. |
| `first_viewed_at`, `last_viewed_at`, `view_count` | | When the customer opened it. |
| `items` | array | Coverage items (below). |
| `created_by`, `created_at` | | |

## The coverage item object

| Field | Type | Description |
|---|---|---|
| `id`, `job_id`, `packet_id` | integer | |
| `label`, `quantity` | string | What is covered and how much was sold, e.g. `Sealcoating`, `6,300 SY`. |
| `term_months` | integer | |
| `starts_on`, `ends_on` | string | Dates. |
| `status` | string | `active`, `expiring` (ends within 90 days) or `expired`. |
| `days_left` | integer | Negative once expired. |
| `notes` | string or null | |

## The work notice object

| Field | Type | Description |
|---|---|---|
| `id`, `job_id` | integer | |
| `status` | string | `sent` or `void`. |
| `kind` | string | `warranty`, or `goodwill` for work done at no charge outside live coverage. |
| `title`, `scheduled_for` | | What is being done and when. |
| `coverage_item_id`, `coverage_item`, `packet_id` | | The coverage item it falls under, if any. |
| `sent_at`, `sent_to_email`, `public_url`, `first_viewed_at`, `last_viewed_at`, `view_count` | | As on packets. |

## A job's warranty

### Get a job's warranty

`GET /v1/jobs/{job_id}/warranty`

**Permission:** Read jobs

Everything the job's Warranty page shows: `packets` (newest first), `notices`, `coverage` (items on sent packets), the workspace's `templates`, and `suggested_packet`: what a send would hold right now (template, recipient, customer name, subject and `rows`, with `from_estimate_id` when the rows come from the sold estimate).

| Name | Type | Required | Description |
|---|---|---|---|
| `template_id` | integer | No | Suggest the packet from this template instead of the default. |
| `effective_date` | string | No | Coverage start for the suggestion. Default today. |

```bash
curl "https://trutec.ai/api/v1/jobs/70412/warranty" \
  -H "X-API-Key: sk_your_api_key_here"
```

### Preview a document

`POST /v1/jobs/{job_id}/warranty/preview`

**Permission:** Read jobs

Returns the packet (`kind` `packet`, the default) or work notice (`kind` `notice`) as HTML with merge fields filled in. Takes the same fields as the send endpoints below. Nothing is saved or sent.

### Send the job's warranty packet

`POST /v1/jobs/{job_id}/warranty/send`

**Permission:** Write jobs. **Emails the customer.**

Fills in the job's draft packet (or starts one), freezes the document, adds each row to the tracker, marks it sent and emails the customer a link to their certificate. Every field is optional; the defaults are what `suggested_packet` shows.

| Field | Type | Description |
|---|---|---|
| `template_id` | integer | Default: the workspace default template. |
| `effective_date` | string | Coverage start. Default today. |
| `to_email` | string or null | Left out: the job's contact email. `null` or `""`: nobody is emailed; the packet is sent as a link (`public_url`). |
| `customer_name` | string | Default: the job's company or contact. |
| `rows` | array | `{label, quantity, term_months, notes}` per covered item (up to 100). `term_months` defaults to the template's term for that service. |
| `cover_subject`, `cover_body` | string | The email's subject and message. Default: the template's. |
| `body_html` | string | The warranty document (simple HTML with merge fields such as `{{customer_name}}`). Default: the template's. |

```bash
curl -X POST "https://trutec.ai/api/v1/jobs/70412/warranty/send" \
  -H "X-API-Key: sk_your_api_key_here" \
  -H "Content-Type: application/json" \
  -d '{"to_email": "pm@northgate.example", "effective_date": "2026-10-13"}'
```

```json
{
  "data": {
    "id": 3310,
    "job_id": 70412,
    "status": "sent",
    "name": "Standard paving warranty — Northgate Plaza reseal",
    "template_id": 41,
    "customer_name": "Northgate Properties",
    "effective_date": "2026-10-13",
    "sent_at": "2026-10-13T16:02:11Z",
    "sent_to_email": "pm@northgate.example",
    "public_url": "https://trutec.ai/warranty/certificate/Qm9n...",
    "view_count": 0,
    "items": [
      { "id": 9120, "label": "Sealcoating", "quantity": "6,300 SY", "term_months": 12,
        "starts_on": "2026-10-13", "ends_on": "2027-10-13", "status": "active", "days_left": 365 }
    ]
  },
  "emailed": true
}
```

If the email can't be delivered the packet is still sent: the response has `emailed: false` and `email_error`, and you can share `public_url` yourself.

### Send a work notice

`POST /v1/jobs/{job_id}/warranty/send-notice`

**Permission:** Write jobs. **Emails the customer.** Returns `201`.

| Field | Type | Description |
|---|---|---|
| `title` | string | Required. What's being done, e.g. *Crack repair at the entrance*. |
| `coverage_item_id` | integer | One of this job's coverage items (from `coverage`). |
| `scheduled_for` | string | When the work is planned. |
| `to_email`, `customer_name` | | As for packets. |
| `body_html` | string | Default: the standard warranty wording, or the no-charge wording when coverage has run out. |

## Packets, notices and the tracker

### List warranty packets

`GET /v1/warranty-packets`

**Permission:** Read jobs

Newest first. Filter by `job_id` and `status` (`draft` lists packets waiting to be sent). `limit` (default 50, maximum 200) and `offset`.

### Get a warranty packet

`GET /v1/warranty-packets/{packet_id}`

**Permission:** Read jobs

### Send a packet again

`POST /v1/warranty-packets/{packet_id}/send`

**Permission:** Write jobs. **Emails the customer.**

Sends a draft for the first time, or a sent packet again, with any changes. Same fields as sending from the job; the defaults are the packet's own recipient, customer name, template and coverage start. Its coverage items are rebuilt from the rows. A void packet can't be sent (`409`).

### Void a packet

`POST /v1/warranty-packets/{packet_id}/void`

**Permission:** Write jobs

The customer's link stops working and the packet's items leave the tracker. A void packet can't be sent again; send a new one from the job.

### List work notices

`GET /v1/warranty-notices`

**Permission:** Read jobs

Newest first. Filter by `job_id`, `status` (`sent`, `void`) and `kind` (`warranty`, `goodwill`).

### Void a work notice

`POST /v1/warranty-notices/{notice_id}/void`

**Permission:** Write jobs

### List coverage (the tracker)

`GET /v1/warranty-items`

**Permission:** Read jobs

Every item on a sent packet, soonest to end first, each with the work notices sent under it. `counts` totals `active`, `expiring` and `expired` across the workspace (or the job).

| Name | Type | Required | Description |
|---|---|---|---|
| `status` | string | No | `active`, `expiring` or `expired`. |
| `job_id` | integer | No | |
| `limit`, `offset` | integer | No | Default 50, maximum 200. |

### List warranty templates

`GET /v1/warranty-templates`

**Permission:** Read jobs

Active templates, default first, with `standard_items` and `coverage_rules` (`{keyword, term_months}`: the first keyword found in a sold service's name sets its term).

**Errors:** `400` bad dates, rows or fields; `403` missing permission, or the key's creator is a crew login or no longer in the workspace; `404` a job, packet, notice, template or coverage item that isn't in this workspace; `409` sending a void packet.
