> ## Documentation Index
> Fetch the complete documentation index at: https://docs.sitevisit.app/llms.txt
> Use this file to discover all available pages before exploring further.

# Authentication

> How to authenticate requests with an API key.

The SiteVisit API uses **Bearer token authentication**. Every request must include an `Authorization` header carrying a key you generated in the dashboard.

```bash theme={null}
Authorization: Bearer sv_live_e059b3544b7fa4b93c7e26933aeba781
```

## Key format

```
sv_live_<32 hex chars>   ← real customer data
sv_test_<32 hex chars>   ← parallel sandbox dataset
```

* `sv_` is the SiteVisit prefix.
* `live` or `test` identifies the **mode** — test keys operate on a parallel sandbox dataset that's completely separate from your production data. See [Test mode](#test-mode) below.
* The tail is 16 random bytes encoded as 32 lowercase hex characters.

We store only a SHA-256 hash of the tail and the prefix in cleartext (just enough to identify which key is which in the UI). On the wire we compare in constant time.

## Generate a key

1. Sign in to [sitevisit.app](https://sitevisit.app).
2. Open **Settings → Developers**.
3. Type a name (e.g. `production-warehouse`, `marc-laptop`).
4. Choose **Mode → Live** for production data, or **Mode → Test** for the sandbox.
5. Click **Generate key**.
6. **Copy the full value immediately** — we display it once.

## Test mode

Stripe-style — `sv_test_*` keys operate on a **parallel sandbox dataset** that's completely separate from your production data. Properties, site visits, action items, capture tokens, and webhook endpoints created with a test key are tagged `isTest: true` and never visible to live-mode callers (or vice versa).

**Test mode is free + unmetered.** Test resources don't count against your plan limits — developers on any tier can build end-to-end without paying.

```bash theme={null}
# List your test-mode properties
curl https://sitevisit.app/api/v1/properties \
  -H "Authorization: Bearer sv_test_..."

# Create a test property
curl -X POST https://sitevisit.app/api/v1/properties \
  -H "Authorization: Bearer sv_test_..." \
  -H "Content-Type: application/json" \
  -d '{"name": "Test Property"}'
```

Every response includes `isTest: true|false` so you can confirm at a glance which dataset you hit.

### Webhooks in test mode

Webhook endpoints have their own mode (`live` or `test`, set when you create the endpoint in Settings → Developers → Webhooks). Test-mode endpoints **only** receive events triggered by `sv_test_*` keys; live endpoints **only** receive events from `sv_live_*` keys. Test traffic never crosses into live receivers.

Every webhook payload includes a top-level `livemode` boolean so receivers can branch without parsing the resource itself:

```jsonc theme={null}
{
  "id": "evt_abc123",
  "event_type": "property.created",
  "livemode": false,           // ← true for sv_live_, false for sv_test_
  "created_at": "2026-06-06T...",
  "data": { "property": { /* PropertyDto */ } }
}
```

<Warning>
  Once you close the reveal panel, the full secret is gone forever. We keep only a hash. If you lose it, revoke and re-issue.
</Warning>

## Rotate a key

There's no rotation endpoint — generate a new key, swap it into your secret store, then revoke the old one once traffic has moved over. This pattern keeps you in control of the cutover window.

## Revoke a key

In **Settings → Developers**, click **Revoke** next to the key. Revoked keys reject all subsequent requests with `401 invalid_api_key`. Revocation is immediate; we do not honor a grace period.

## Scopes

Every key has full read + write access to the account that issued it. The only access boundary today is the **mode** (live vs test) — test keys can never reach into live data or vice versa.

## Where to store keys

API keys are secrets. **Don't:**

* Commit them to a repository.
* Embed them in mobile apps or frontend JavaScript.
* Share them across teams in chat or email.

Instead, store them in your platform's secret manager (Vercel envs, AWS Secrets Manager, 1Password, etc.) and inject at runtime.

## Errors

| Status | Code              | Meaning                                                           |
| ------ | ----------------- | ----------------------------------------------------------------- |
| `401`  | `missing_api_key` | No `Authorization` header was sent.                               |
| `401`  | `invalid_api_key` | Header present but the key doesn't match a live, non-revoked key. |
| `403`  | `forbidden`       | The key is valid but isn't allowed to access this resource.       |

See the [Errors](/errors) page for the full envelope shape and the `docs_url` link pattern.
