# Authentication & API keys

## How keys work

- A key belongs to **one TruTec workspace** and can only read or change that workspace's data.
- Workspace **owners** create and revoke keys in **Settings › API keys**.
- A key is shown **once**, when you create it. We store only a salted hash, so we can't show it again or recover it. Lost a key? Revoke it and make a new one.
- Keys start with `sk_`. The first 11 characters (for example `sk_4fQ9xT2b`) are the key's public prefix: it's what you see in Settings and what to quote to support.
- A workspace can have as many active keys as you need. Give each system its own key so you can revoke one without touching the others.
- Connecting ChatGPT or Claude by signing in with TruTec creates a key for that assistant, read only or read and change as the owner picks ([AI assistants](mcp.md)). It's listed and revoked like any other key.
- API access comes with plans that include it. If the plan or billing lapses, calls return `403` or `402` with a message saying why ([Errors](errors.md)).

## Send the key

Put the key in the `X-API-Key` header on every request:

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

```python
import os
import requests

API = "https://trutec.ai/api/v1"
session = requests.Session()
session.headers["X-API-Key"] = os.environ["TRUTEC_API_KEY"]

jobs = session.get(f"{API}/jobs", timeout=30).json()
```

```javascript
// Node 18+, server-side only
const API = "https://trutec.ai/api/v1";
const res = await fetch(`${API}/jobs`, {
  headers: { "X-API-Key": process.env.TRUTEC_API_KEY },
});
const jobs = await res.json();
```

The [MCP server](mcp.md) also accepts `Authorization: Bearer sk_...`, because some AI clients can only send that header. The REST endpoints read `X-API-Key`.

## Call from your servers only

API keys are secrets with access to your company's data. Call the API from your backend, a scheduled job or a serverless function, and keep the key in an environment variable or a secrets manager. Never put a key in browser JavaScript, a mobile app or a public repository. If a key leaks, revoke it in **Settings › API keys**: revocation takes effect on the next request.

## Permissions

Each key carries a set of permissions, and every endpoint checks them. A call without the permission it needs returns `403` and names what's missing.

| Permission | Lets the key | Default |
|---|---|---|
| Read measurements | Measure sites, rate pavement condition, and read measurement results, saved takeoffs and imagery | On |
| Read jobs | Read customers, sites, jobs, estimates, job costing, events, webhook deliveries and exports | On |
| Write jobs | Create and update customers, sites and jobs; manage webhooks; redeliver and replay events | On for read & write keys |
| Write measurements | Save a measurement onto a job as a takeoff, rename takeoffs | On for read & write keys |
| Delete records | Delete or archive customers, sites, contacts, photos, files, takeoffs and other records (needs the matching write permission too) | Off |
| Partner | Manage the workspaces of a software platform's own customers ([Partner API](partner-api.md)) | Off, granted by TruTec |

Keys created in **Settings › API keys** can read and write (Read measurements, Read jobs, Write jobs, Write measurements), and can't delete. For reporting, warehouse syncs and AI assistants a **read-only** key is the safer choice: it can see everything and change nothing. Ask us and we'll make any key read-only.

**Delete records** is off for every new key. If your integration needs to delete or archive records, email [support@trutec.ai](mailto:support@trutec.ai) from a workspace owner's address with the key's prefix and we'll turn it on for that key.

Each endpoint's page lists the permission it needs.

## Test and production keys

Keep test traffic away from production data with a **sandbox workspace**: a separate TruTec workspace with sample customers, sites, jobs and estimates. Its keys can only see sandbox data, so a bug in your test code can't touch your real records. Switching to production means swapping the key, nothing else: the URL and the API are the same. See [Sandbox & testing](sandbox.md).

Name keys after where they run, for example `erp-sync-prod`, `erp-sync-staging`, `warehouse-nightly`, so the list in Settings tells you what each one is for.

## Rotate a key

Rotation needs no downtime, because a workspace can have more than one active key:

1. Create a new key in **Settings › API keys** (and ask us to match its permissions if the old key was read-only or could delete).
2. Deploy it to your system.
3. Confirm calls succeed with the new key.
4. Revoke the old key.

Rotate on a schedule that suits your security policy (every 90 days is common), whenever someone with access to the key leaves, and right away if a key may have leaked.

## Authentication errors

| Status | `error` | What it means |
|---|---|---|
| `401` | `Missing API key` | No `X-API-Key` header |
| `401` | `Invalid API key` | Wrong key, revoked key, or a key whose workspace no longer exists |
| `403` | `Plan upgrade required` | The workspace plan doesn't include API access |
| `403` | `API access disabled` | API access is turned off for the workspace. Contact support |
| `402` | `Subscription past due` / `Subscription inactive` | Billing needs attention. Access comes back as soon as it's fixed |
| `403` | `Trial expired` / `Subscription canceled` | The workspace has no active subscription |
| `429` | `API usage limit exceeded` | This key reached the call cap set on it |

Every error body has the same shape:

```json
{
  "error": "Invalid API key",
  "message": "The provided API key is invalid or has been revoked"
}
```
