# Environments

BillerAPI provides separate sandbox and production environments so you can develop and test without affecting real data.

## Sandbox vs Production

Every BillerAPI account has access to both environments. Sandbox uses test data and test billers — no real credentials are ever processed. Production connects to real biller sites and returns real bill data.

|  | Sandbox | Production |
| --- | --- | --- |
| Base URL | `https://sandbox.api.billerapi.com` | `https://api.billerapi.com` |
| API key prefix | `bak_test_` | `bak_live_` |
| Billers | 5 test billers (see below) | Real biller integrations |
| Data | Synthetic test data, deterministic responses | Real bills and account data |
| Rate limits | Same as production | 100 req/min (most endpoints) |
| Webhooks | Sandbox events only | Real-time events |

There is also a **staging** environment at `https://api.staging.billerapi.com` used for internal testing. This is not available to external developers.

## Telling sandbox webhooks from production webhooks

The webhook envelope has **no** `environment` field. Environment separation happens one level up, at **registration**: a webhook endpoint is created against one environment (`sandbox` or `production`), and only that environment's events are ever delivered to it. Register two endpoints, point them at two URLs, and each side is already routed — you never branch inside a single handler.

Signing secrets are per-endpoint too, so the signature you verify is itself the environment discriminator. If you deliberately point both environments at *one* URL, use `producer_service`: simulated sandbox traffic is stamped `billerapi.sandbox-service`, real platform traffic `billerapi.webhooks-service`. Everything else about the two envelopes is identical by design — the handler you write against sandbox is the handler that runs in production.

**The delivered envelope (identical in both environments)**

**JSON**

```json
{
  "id": "evt_01HX9K7MZQR4F3X5V2W8N1B2C3",
  "object": "event",
  "type": "bill.created",
  "api_version": "2026-04-29",
  "created": 1714387200,
  "producer_service": "billerapi.sandbox-service",
  "correlation_id": null,
  "data": {
    "object": {
      "id": "bill_01HX...",
      "object": "bill",
      "user_id": "user_42",
      "biller_id": "sb_utility",
      "status": "PENDING"
    }
  },
  "request": { "id": null, "idempotency_key": null }
}
```

**Shared-URL routing (JS)**

```javascript
function handleWebhook(envelope) {
  // Only needed if you point BOTH registered endpoints at one URL.
  // The normal setup is two endpoints and two URLs, already separated.
  const isSandbox = envelope.producer_service === 'billerapi.sandbox-service';
  return (isSandbox ? sandboxHandler : prodHandler)(envelope);
}
```

## `livemode` / `mode` on API responses

Every top-level JSON object returned by a client-authenticated REST call carries two additive fields so you can see, on the wire, which environment served the request:

- `livemode` — a boolean, `true` in production and `false` in sandbox. This mirrors the Stripe-style field integrators expect.
- `mode` — the same signal as an explicit string, `"sandbox"` or `"production"`.

For a paginated response, the indicator is added **once** at the top level of the envelope (alongside `has_more` / `next_cursor`) — never repeated inside each row. Bare-array responses, streamed ([SSE](/docs/api/connect-events)) responses, and error envelopes do not carry it. Both fields are additive, so existing integrations that ignore them keep working unchanged.

Note this is a two-mode indicator (sandbox vs production) scoped to API responses. It has no webhook counterpart: the webhook envelope carries no `environment` field at all, as described above — a webhook endpoint is registered against one environment and only that environment's events reach it.

**Object response with livemode / mode**

**JSON**

```json
{
  "id": "bill_01HX...",
  "object": "bill",
  "amount_due": 12750,
  "livemode": false,
  "mode": "sandbox"
}
```

**Guarding on it (JS)**

```javascript
// Refuse to act on sandbox data in a production code path.
if (!bill.livemode) {
  console.warn('Ignoring sandbox bill in production handler');
  return;
}
```

## Switching Environments

To move from sandbox to production, change two things: the base URL and the API keys. All endpoint paths, request bodies, and response shapes stay the same.

**Environment configuration**

**JavaScript**

```javascript
// Sandbox
const BASE_URL = 'https://sandbox.api.billerapi.com';
const BILLERAPI_API_KEY = 'bak_test_xxxxxxxx';

// Production — just swap these two values
const BASE_URL = 'https://api.billerapi.com';
const BILLERAPI_API_KEY = 'bak_live_xxxxxxxx';

// The key is a self-contained Bearer token — no companion id header.
// fetch(`${BASE_URL}/v1/billers`, {
//   headers: { Authorization: `Bearer ${BILLERAPI_API_KEY}` },
// });
```

**Python**

```python
# Sandbox
BASE_URL = 'https://sandbox.api.billerapi.com'
BILLERAPI_API_KEY = 'bak_test_xxxxxxxx'

# Production — just swap these two values
BASE_URL = 'https://api.billerapi.com'
BILLERAPI_API_KEY = 'bak_live_xxxxxxxx'

# The key is a self-contained Bearer token — no companion id header.
# requests.get(f'{BASE_URL}/v1/billers',
#              headers={'Authorization': f'Bearer {BILLERAPI_API_KEY}'})
```

## Sandbox Test Billers

The sandbox environment includes five test billers that simulate different biller types. These are always available and return deterministic test data.

| Biller ID | Name | Type |
| --- | --- | --- |
| `sb_utility` | Sandbox Utility | Utility |
| `sb_power` | Sandbox Power | Financial |
| `sb_telecom` | Sandbox Telecom | Telecom |
| `sb_gas` | Sandbox Gas | Insurance |
| `sb_water` | Sandbox Water | Utility |

Use account number `4242424242` with any username and password to simulate a successful account link. See the [Getting Started](/docs/guides/getting-started) guide for the full list of test account numbers and scenarios.

## Related

- [Go Live Guide](/docs/guides/go-live) — checklist for moving from sandbox to production
- [Getting Started](/docs/guides/getting-started) — first integration walkthrough using sandbox
- [Authentication](/docs/guides/authentication) — API key management and security
- [Webhooks](/docs/guides/webhooks) — full envelope shape and signature verification
