# Settings

The settings API is one surface over TruTec's owner Settings pages. Each **section** is one card in Settings. Reading returns every section with its current values; changing a section runs that card's own save, so TruTec's checks, cleanup and side effects apply exactly as when an owner clicks Save.

**Only the fields you send change.** Some Settings cards save all of their fields at once; the API fills in the current value of every field you didn't send, so a change never resets anything else.

**Who can change settings.** Reads need **Read jobs**. Changes need **Write jobs** and a key created by a **workspace owner** who still owns the workspace (settings are owner-only in TruTec). Any other key gets `403` `Owner key required`. Partner keys act as the linked workspace's owner.

**Not in the API.** Billing and the plan, payment connections, API keys, members, roles and permissions, teams, sign-in and security, integration connections (QuickBooks, HubSpot, Jobber, email, cloud storage) and deleting the workspace are changed only in TruTec. Reading or changing those section names returns `403` `Not available through the API` with where to go. Invite members with [`POST /v1/users/invite`](users-and-workspace.md).

Rates are fractions: `0.075` means 7.5%. Money is in dollars.

!!! note
    Partner keys that act on behalf of several workspaces must also pass `firm_id` (query string, or JSON body on writes).

## Sections

| Section | Fields | Where in TruTec |
|---|---|---|
| `company` | `name` (2–200 characters), `website`, `address` (the office or yard address trucks leave from), `timezone` (an IANA name such as `America/Chicago`; set from the crew schedule, so only workspaces with crew scheduling can change it here) | Settings › Workspace |
| `branding` | `logo_from_url` (write only: a public link to a PNG, JPG, WebP or GIF; TruTec copies it into the logo library and makes it the active logo), `brand_color` (customer portal color), `drawing_accent_color` (construction drawing exports), `share_subdomain`. Reading also returns `logo_url`, a temporary link to the active logo | Settings › Workspace, Settings › Exports & portal |
| `units` | `default_area_unit`: `sf` or `sy`, the area unit new services start with | Estimating › Defaults |
| `estimating` | `default_markup_method` (`markup` or `margin`), `default_markup_rate`, `default_tax_rate`, `taxable_categories` (any of `material`, `crew`, `equipment`, `trucking`, `sub`, `misc`, `overhead`, `unit_costed`), `default_contingency_rate`, `minimum_job_charge`, `approval_margin_floor`, `default_burden_rate`, `overtime_burden_rate`, `overtime_after_hours`, `double_time_after_hours`, `double_time_multiplier`, `workday_hours`, `production_rate_basis` (`hour` or `day`), `estimate_number_start`, `proposal_quantity_rounding` (`{step, mode}`), `rounding` (`{labor_time_step_days, material_whole_units, proposal_increment, proposal_allocation}`; keys you leave out keep their values), `difficulty_multipliers` (`{easy, standard, hard}`), `auto_carry_takeoff_services`, `one_minimum_per_service`, `show_empty_cost_categories`, `labor_sets_enabled` | Catalog › Settings |
| `sales_tax` | `areas` (`[{id, name, rate, match, state, place}]`; `match` is `state`, `county` or `city`; the most specific match for a job's address wins), `exempt_question` and `reason_question` (requirements question ids) | Settings › Workspace › Sales tax areas |
| `proposal_terms` | The company default terms on new proposals: `title`, `body_html`, `items` (`[{text, title, bold, on}]`), `footer`, `footer_hidden` | Catalog › Proposal content |
| `proposal_clauses` | `clauses`: the reusable clause library, `[{id, title, text, bold, is_default}]`. Default clauses are added to every new proposal | Catalog › Proposal content |
| `job_numbering` | `prefix` (up to 12 characters), `start`. Existing job numbers don't change | Settings › Workspace › Job numbering |
| `job_creation` | `required_fields` (any of `address`, `company`, `job_type`, `source`, `contact_email`), `require_assignment`, `disable_quick_create`, `warn_duplicate_addresses`, `assign_to_creator` | Settings › Workspace |
| `job_requirements` | `questions`: the requirements questions asked on every job | Settings › Workspace › Requirements |
| `jobs_board` | `remember_filters`, `lock_column_headers`, `show_prevailing_wage_fields`, `show_customer_review` | Settings › Workspace |
| `takeoffs` | `show_address_pin_on_load`, `include_adjacent_crosshatch_with_ada`, `require_scale_verification` (plan sheets lock until someone confirms the scale), `show_legacy_takeoff_tools` | Settings › Workspace › Measurement preferences |
| `photos` | `default_visibility` (`external` = customer can see new photos, `internal` = team only), `default_include_in_report` | Settings › Workspace › Measurement preferences |
| `scheduling` | `working_hours_per_day`, `chip_text` (`auto`, `dark` or `light`), `chip_strength` (`soft`, `medium` or `bold`), `group_by` (`service_group`, `service`, `job`, `job_number`, `crew` or null; needs crew scheduling) | Settings › Workspace › Crew schedule |
| `notifications` | Emails to the team when a customer acts on a proposal: `proposal_viewed`, `proposal_signed`, `proposal_accepted`, `proposal_declined`, `proposal_paid` | Settings › Notifications |
| `lists` | Read only: `job_stages`, `job_types` and `pick_lists` (`lead_sources`, `customer_types`, `property_types`, `contact_types`). Change them with the endpoints under [Lists](#lists) | Jobs board, Settings |

Lists inside a section (`sales_tax.areas`, `proposal_clauses.clauses`, `proposal_terms.items`, `job_requirements.questions`) are replaced whole: read the section, change the list and send all of it back. Keep each entry's `id` to keep it.

`estimating`, `proposal_terms` and `proposal_clauses` need Estimating Pro; on other workspaces they're listed as unavailable and changes return `409`.

## Read settings

### Get every section

`GET /v1/settings`

**Permission:** Read jobs

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

```json
{
  "data": {
    "company": { "name": "Northgate Paving", "timezone": "America/Chicago", "address": "2200 Yard Rd, Austin, TX", "website": "northgatepaving.example" },
    "job_numbering": { "prefix": "J", "start": 1000 },
    "notifications": { "proposal_viewed": true, "proposal_signed": true, "proposal_accepted": true, "proposal_declined": true, "proposal_paid": true }
  },
  "sections": [
    { "key": "company", "title": "Company profile", "settings_page": "Settings › Workspace", "fields": ["name", "website", "address", "timezone"], "available": true, "unavailable_reason": null, "note": null }
  ],
  "not_in_api": ["api_keys", "billing", "delete_workspace", "members", "roles", "security"]
}
```

(Response trimmed.) `sections` lists each section's fields and whether this workspace has it.

### Get one section

`GET /v1/settings/{section}`

**Permission:** Read jobs

Returns `{"data": {...}}` for that section. **Errors:** `404` for an unknown section, `403` for a section that isn't in the API, `409` for a section this workspace doesn't have.

## Change settings

### Change a section

`PATCH /v1/settings/{section}`

**Permission:** Write jobs, and a key created by a workspace owner

Send the fields to change. Returns the section after the save, and `changed`: each field you sent that changed, with `before` and `after`.

```bash
curl -X PATCH "https://trutec.ai/api/v1/settings/estimating" \
  -H "X-API-Key: sk_your_api_key_here" \
  -H "Content-Type: application/json" \
  -d '{"default_markup_method": "markup", "default_markup_rate": 0.3, "default_tax_rate": 0.075}'
```

```json
{
  "data": { "default_markup_method": "markup", "default_markup_rate": 0.3, "default_tax_rate": 0.075, "minimum_job_charge": 0.0 },
  "changed": {
    "default_markup_method": { "before": "margin", "after": "markup" },
    "default_markup_rate": { "before": 0.0, "after": 0.3 },
    "default_tax_rate": { "before": 0.0, "after": 0.075 }
  }
}
```

(Response trimmed.)

```bash
curl -X PATCH "https://trutec.ai/api/v1/settings/sales_tax" \
  -H "X-API-Key: sk_your_api_key_here" \
  -H "Content-Type: application/json" \
  -d '{"areas": [{"name": "Travis County", "rate": 0.0825, "match": "county", "state": "TX", "place": "Travis"}]}'
```

**Errors:** `400` `Unknown fields: …` (with `allowed_fields`), `<field> must be …`, or TruTec's own reason (for example "That company name is already taken."). `403` `API key lacks job write permission`, `Owner key required`, `Not available through the API`. `404` unknown section. `409` a section this workspace doesn't have.

## Lists

Pipeline stages, job types and pick lists are edited one entry at a time, the way the jobs board and Settings edit them. These also need **Write jobs** and an owner's key; removing a stage or job type also needs **Delete records**. Read them with `GET /v1/job-stages`, `GET /v1/job-types` and the `lists` section.

### Add a job stage

`POST /v1/job-stages`

| Name | Type | Required | Description |
|---|---|---|---|
| `label` | string | Yes | Up to 80 characters. |
| `stage_type` | string | No | What it counts as in reports: `open` (default), `won`, `lost`, `excluded` or `warranty`. |
| `color` | string | No | Like `#2563eb`. |
| `default_probability` | integer | No | Win chance in percent, 0–100. |

Adds the stage at the end of the board. Returns `201` with the stage (the `GET /v1/job-stages` shape).

### Change a job stage

`PATCH /v1/job-stages/{key}`

Same fields; only what you send changes. Returns the stage and `changed`. Changing a won stage's type is refused while won jobs are locked and sit in it.

### Remove a job stage

`DELETE /v1/job-stages/{key}`

**Permission:** Write jobs and Delete records, owner key

Its jobs move to the stage before it (the next one when it's the first). The last stage can't be removed. Returns `204`.

### Reorder job stages

`POST /v1/job-stages/reorder`

Body: `{"keys": [...]}` with every stage key once, in the new order. Returns the stages.

### Job types

| Endpoint | What it does |
|---|---|
| `POST /v1/job-types` | Add one: `{label, category}`. Returns `201` with `{value, label, category}`. |
| `PATCH /v1/job-types/{value}` | Rename or recategorize. Returns it and `changed`. |
| `DELETE /v1/job-types/{value}` | Remove it from the list (Delete records). Jobs that already have it keep it. `204`. |
| `POST /v1/job-types/reorder` | `{"values": [...]}` in the new order; any left out follow. |

### Pick lists

`GET /v1/pick-lists/{kind}` and `PATCH /v1/pick-lists/{kind}`, where `kind` is `lead_sources`, `customer_types`, `property_types` or `contact_types`.

The PATCH body is `{"add": [...], "remove": [...]}` (option names, matched without regard to case; adding one that's there already does nothing). Returns the list after and `changed` (`before`, `after`). Removing an option that isn't in the list is a `404`.

```bash
curl -X PATCH "https://trutec.ai/api/v1/pick-lists/lead_sources" \
  -H "X-API-Key: sk_your_api_key_here" \
  -H "Content-Type: application/json" \
  -d '{"add": ["Yard sign"], "remove": ["Newspaper"]}'
```
