# Environments and base URL

> Where your organisation's API lives, which addresses it serves and how to test safely.

## Base URL

BatteryPass runs per deployment; there is no shared global API host. Your base URL is the BatteryPass address
of your organisation, the same address you use for the portal, for example `https://batterypass.example.com`.
The API lives under `/api/v1` on that address.

## Addresses

| Path on your BatteryPass address               | What                                                        | Authentication |
| ---------------------------------------------- | ----------------------------------------------------------- | -------------- |
| `/api/v1/...`                                  | The integration API                                         | API key        |
| `/api/v1/public/...`                           | Public passport reads                                       | None           |
| `/api/v1/openapi.json`, `/api/v1/openapi.yaml` | The published contract                                      | None           |
| `/01/{gtin}/21/{serial}`                       | GS1 Digital Link resolver, the target of the QR codes       | None           |
| `/pass/{passportId}`                           | Public passport page for browsers                           | None           |
| `/.well-known/batterypass/keys.json`           | Public signing keys (JWK set) to verify passport signatures | None           |
| `/docs/`                                       | This documentation                                          | None           |

> [!NOTE]
> A deployment can print a separate resolver host into its QR codes. Take a unit's Digital Link from the
> `gs1_digital_link` member of [`getPassport`](https://passport.kabin.sh/docs/en/api-reference/passports/get-passport) instead of
> building it yourself. See [GS1 Digital Link](https://passport.kabin.sh/docs/en/concepts/digital-link).

## Conventions

- **HTTPS.** Send requests, and above all keys, only over HTTPS.
- **JSON.** Request and response bodies are UTF-8 JSON; send `Content-Type: application/json` with a body.
  Bodies are limited to 1 MiB by default (`413 payload_too_large`), and unknown properties are refused.
- **Times** (`date-time`) are UTC in RFC 3339, e.g. `2026-10-24T02:00:00Z`. Send times with an offset;
  UTC is the safest choice.
- **Dates** (`date`, e.g. `occurred_on`, `manufacturing_date`) are `YYYY-MM-DD` without a time zone.
- **Ids** are UUID strings. The permanent passport identifier is `urn:dpp:{uuid}`; public routes take only
  the UUID.

## Configure your client

All samples read two environment variables:

| Variable              | Value                                                                                     |
| --------------------- | ----------------------------------------------------------------------------------------- |
| `BATTERYPASS_URL`     | Your BatteryPass address without a trailing slash, e.g. `https://batterypass.example.com` |
| `BATTERYPASS_API_KEY` | An API key of your organisation, `bp_live_...`                                            |

```bash title="Shell"
export BATTERYPASS_URL="https://batterypass.example.com"
export BATTERYPASS_API_KEY="bp_live_..."
```

```ts title="TypeScript"
const url = process.env.BATTERYPASS_URL?.replace(/\/+$/, '');
const key = process.env.BATTERYPASS_API_KEY;
if (!url || !key) throw new Error('Set BATTERYPASS_URL and BATTERYPASS_API_KEY.');
if (!url.startsWith('https://')) console.warn('BATTERYPASS_URL should use HTTPS.');

export const api = `${url}/api/v1`;
export const headers = { Authorization: `Bearer ${key}` };
```

```python title="Python"
import os

url = os.environ.get("BATTERYPASS_URL", "").rstrip("/")
key = os.environ.get("BATTERYPASS_API_KEY", "")
if not url or not key:
    raise SystemExit("Set BATTERYPASS_URL and BATTERYPASS_API_KEY.")
if not url.startswith("https://"):
    print("BATTERYPASS_URL should use HTTPS.")

api = f"{url}/api/v1"
headers = {"Authorization": f"Bearer {key}"}
```

```go title="Go"
package main

import (
	"fmt"
	"log"
	"os"
	"strings"
)

func main() {
	url := strings.TrimRight(os.Getenv("BATTERYPASS_URL"), "/")
	key := os.Getenv("BATTERYPASS_API_KEY")
	if url == "" || key == "" {
		log.Fatal("Set BATTERYPASS_URL and BATTERYPASS_API_KEY.")
	}
	if !strings.HasPrefix(url, "https://") {
		log.Print("BATTERYPASS_URL should use HTTPS.")
	}
	api := url + "/api/v1"
	fmt.Println("API base:", api)
}
```

Keep the key in a secret store or your platform's secret management, not in source code or in files you
commit. See [Secure key handling](https://passport.kabin.sh/docs/en/guides/secure-key-handling).

## Testing

BatteryPass has no public sandbox. Test against an environment where mistakes do no harm:

- **A separate test organisation**, or a separate BatteryPass deployment, with its own keys. Data is always
  separated by organisation, so a test key never sees or changes production data. Ask the people who run your
  BatteryPass deployment to set it up.
- **Do not test writes against production.** A condition update by key can re-publish a public passport
  at once, depending on the organisation's settings, and lifecycle events cannot be deleted through the API.
- **Mind what is public.** Passports published in a test organisation are public too, at the same addresses.
  Do not use the GTINs of real products for test data, and do not print or hand out test QR codes.
- **Separate configuration.** Keep test and production values of `BATTERYPASS_URL` and `BATTERYPASS_API_KEY`
  apart, and give test keys only the scopes the test needs.
