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

# Sandbox (test API keys)

> How nex_test_ keys isolate rows, what is suppressed, and retention policy

# Sandbox (`nex_test_` keys)

Requests authenticated with a **`nex_test_`** API key use the **same production database** as live traffic, but only see and mutate rows marked **`is_sandbox = true`**. Live keys (`nex_live_`), personal access tokens (`nex_pat_`), and normal browser sessions only see rows where **`is_sandbox` is false** (legacy rows without the flag are treated as live).

For the full rationale and migration notes, see **ADR 0003** in the repository (`docs/decisions/0003-sandbox-environment.md`).

## Isolation rules

| Traffic         | Visible rows                           |
| --------------- | -------------------------------------- |
| `nex_test_`     | `is_sandbox = true` only               |
| Everything else | `is_sandbox = false` or unset (legacy) |

New rows created while handling a sandbox request are stored with **`is_sandbox = true`**. Updates and deletes apply only within that partition, so test keys cannot modify production shift/staff/credential/payroll rows and production traffic cannot touch sandbox copies.

### Tables in Phase 1 scope

Row-level isolation is enforced today on the highest-traffic transactional surfaces, including:

* **Shifts** (related assignments and shift-linked flows follow shift visibility)
* **Staff**
* **Credentials**
* **Payroll runs**
* **API key usage events** (each request log carries `is_sandbox`)

Other domains may not yet filter on **`is_sandbox`** everywhere. If you call an endpoint outside this scope, assume isolation is **not** guaranteed until covered by the same pattern.

## Side effects

Sandbox requests **do not** deliver real user notifications (push, SMS, email); attempts are skipped and logged.

Outbound **platform webhooks** are not emitted for sandbox-key traffic.

## Usage analytics and outcomes

All **`GET /api/api-usage/*`** aggregates (summary, timeseries, by-agent, by-tool, by-key, outcomes) include **production traffic only**: rows with **`is_sandbox = true`** are excluded so outcome counts align with billing-oriented views.

## Retention (TTL) and reset

Two independent mechanisms keep the sandbox tidy:

* **Retention purge (global safety net).** Sandbox rows older than **30 days** are deleted by a **daily** background job. Purge order favors dependents (e.g. shifts first where cascading applies), then staff, credentials, payroll runs, and sandbox usage-event rows.
* **On-demand reset (immediate, facility-scoped).** `POST /api/sandbox/reset` clears a single facility's sandbox rows and re-seeds a small synthetic dataset (**5 workers**, **10 open shifts**) so you can start from a predictable slate.

```bash theme={null}
curl -X POST https://api.nexspace365.com/api/sandbox/reset \
  -H "Content-Type: application/json" \
  -b "session=…" \
  -d '{ "facilityId": 1 }'
```

Via CLI (same session constraint — login with device-code / browser session,
not an API key):

```bash theme={null}
nexspace login
nexspace fixtures sandbox-reset --facility-id 1
```

<Note>
  Reset is a **dashboard/session** operation restricted to NexSpace 365 administrators. It **cannot** be called with an API key (including a `nex_test_` key) — this prevents a low-scope token from wiping data. Sign in and call it with your session, use `nexspace fixtures sandbox-reset`, or trigger it from the developer settings UI.
</Note>

The response reports what was cleared and re-seeded:

```json theme={null}
{
  "success": true,
  "facilityId": 1,
  "cleared": { "staff": 5, "shifts": 10, "payrollRuns": 0, "credentials": 0, "usageEvents": 42 },
  "seeded": { "staff": 5, "shifts": 10 }
}
```

## Related

* [Authentication](/authentication) — key prefixes and scopes
* [Usage analytics](/api-reference/usage-analytics) — OpenAPI for `/api/api-usage/*`
* [CLI fixtures](/cli/commands#fixtures--conformance) — `nexspace fixtures sandbox-reset`
