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

# Security & Best Practices

> Protecting credentials, handling sensitive data, and building a resilient client.

## Protecting your API key and secret

* **Server-side only.** Your API key/secret and any bearer token derived from them must never reach a browser, mobile app, or other client an end user can inspect. Exchange for a token on a server you control and proxy authenticated calls through it.
* **Secrets manager, not source control.** Store the key/secret in your platform's secrets manager (AWS Secrets Manager, GCP Secret Manager, Vault, or equivalent) or, at minimum, an untracked `.env` file. Never commit them.
* **Transport security.** Only ever call `https://api.spenza.com` over TLS — never construct a plain-HTTP variant of the base URL.

## Secret rotation

There is no self-service key/secret rotation endpoint in this API. If a credential may have been exposed (checked into a public repo, logged accidentally, shared over an insecure channel), contact [Support](/support) immediately to have it rotated — don't wait to confirm the exposure was exploited.

## Webhook credentials

Webhook registrations carry two separate secrets — don't conflate them:

* **`authentication`** (the credential you configure) protects **your** endpoint from unauthorized callers — Spenza presents it when calling you.
* **`signingSecret`** (returned once, at registration) is what you use to verify a delivery actually came from Spenza, via the `X-Spenza-Signature` header. See **[Webhooks → Verifying delivery signatures](/webhooks#verifying-delivery-signatures)**.

Store both like any other credential — a secrets manager, never source control — and treat a lost `signingSecret` as a reason to re-register (there's no way to retrieve it again).

## Logging and sensitive-data handling

Several fields that pass through this API are sensitive and should be excluded from application logs, error-tracking tools, and analytics:

* **Credentials** — your API secret, any bearer token, a webhook's `authentication` credential and `signingSecret`.
* **Port-in carrier credentials** — `currentAccountPassword` (the losing carrier's account PIN) is functionally a password; never log the request body of a port-in call unredacted.
* **Personal data** — names, emails, phone numbers and billing addresses flow through the Users, SIMs, Devices, and Port-in surfaces. Handle this like any other customer PII in your systems: minimize retention, restrict access, and exclude it from third-party logging/analytics tools that don't need it.

If you build request/response logging for debugging, redact these fields (or the whole body on auth/port-in endpoints) before writing them anywhere persistent.

## Idempotency

Many write endpoints accept an `Idempotency-Key` request header — use it on anything that spends money or has a side effect you can't cheaply reverse (purchases, assignments, group membership changes). It's the difference between "safe to retry blindly" and "must check state first." See **[Core Concepts → Idempotency](/core-concepts#idempotency)** and **[Errors → Retry recommendations](/errors#retry-recommendations)** for the full mechanics. Not every endpoint supports it — check each operation's page in the API Reference.

## Handling rate limits

`429 RATE_LIMITED` means back off, not retry immediately — see **[Rate Limits](/rate-limits)** for the full strategy (exponential backoff with jitter, applied per endpoint) and the published per-endpoint thresholds.

## Request retries

Retry `429` and `5xx` responses with backoff; don't retry `4xx` responses unchanged — they mean the request itself needs to change. Prefer sending an `Idempotency-Key` on write calls over guessing whether a retry is safe. The full matrix is in **[Errors → Retry recommendations](/errors#retry-recommendations)**.

## Timeout recommendations

* **Synchronous endpoints** (most `GET`/`POST`/`PUT`/`DELETE` calls) typically resolve quickly; a client timeout in the 10–30 second range is a reasonable general default. This isn't a number Spenza publishes — size it to your own latency tolerance and adjust if you observe slower calls in practice.
* **Async calls** (`POST /api/v1.1/esim`, the `/api/v3` purchase variants, top-up, port-in) return immediately with a `transactionId` — don't hold a long-lived connection waiting for the transaction to finish. eSIM provisioning specifically can take **up to \~45 minutes** end to end; poll `GET /api/v3/transactions/{transactionId}` on the interval described in **[Guides → Handling async operations](/guides#handling-async-operations)** instead of waiting on the original request.

## Next steps

* **[Errors](/errors)** for the full error taxonomy and retry matrix.
* **[Rate Limits](/rate-limits)** for throttling behavior.
* **[Webhooks](/webhooks)** for securing inbound webhook delivery specifically.
