> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://docs.fluveo.com/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://docs.fluveo.com/_mcp/server.

# Authentication

The Fluveo `/v1` API authenticates with **secret API keys**, using the same
schemes declared by the Stripe OpenAPI contract. Existing Stripe-style auth code
works unchanged.

## Secret keys

A secret key is a server-side credential. Both modes use one API hostname:

| Key prefix    | Environment | Base URL                    |
| ------------- | ----------- | --------------------------- |
| `sk_test_...` | Test        | `https://api.devfluveo.com` |
| `sk_live_...` | Production  | `https://api.fluveo.dev`    |

Responses carry a `livemode` boolean so you can confirm which mode authenticated
the request. Onboarding also issues `pk_test_...` for browser-side test integrations.
Controlled BR setup may issue live keys after an active live binding exists,
but public live `/v1` admission remains disabled.

## Key lifecycle

Keys are managed from the dashboard **API keys** page (and the first one is
shown when you create your merchant).

* **Issue** — “Create secret key” mints a new `sk_test_` and shows the full key
  **exactly once**. Fluveo stores only a SHA-256 hash; the plaintext is never
  logged and can never be re-displayed.
* **Hint** — after the show-once screen, a key is listed by a hint that keeps
  its full type+mode prefix plus the last four characters, e.g. `sk_test_…a1b2`.
  The hint preserves enough to tell test from live at a glance, but not enough
  to reconstruct the key.
* **Revoke** — revoking a key is immediate and permanent; a revoked key is
  rejected on `/v1`. Revoked keys stay listed (with their hint and last-used
  time) for your audit trail. **Propagation:** a just-revoked key may keep
  working for up to **60 seconds** while a cached resolution expires. Treat 60s
  as the worst-case window when rotating after a suspected leak.
* **Recover a lost key** — there is no “reveal”. If you lose a key, create a new
  one and revoke the old one.

## The two schemes

The `/v1` API accepts either scheme. They are equivalent — pick whichever fits
your HTTP client.

| Scheme          | How                                                           |
| --------------- | ------------------------------------------------------------- |
| HTTP Basic Auth | secret key as the username, empty password: `-u sk_test_...:` |
| Bearer Auth     | `Authorization: Bearer sk_test_...`                           |

Docs and runnable examples prefer **Basic Auth** because it is the canonical curl
form. Note the trailing colon in `-u sk_test_...:` — it sets an empty password so
curl doesn't prompt for one.

```bash
# Basic Auth (canonical)
curl https://api.devfluveo.com/v1/payment_intents \
  -u sk_test_...: \
  -d amount=4242 -d currency=usd

# Bearer Auth (equivalent)
curl https://api.devfluveo.com/v1/payment_intents \
  -H "Authorization: Bearer sk_test_..." \
  -d amount=4242 -d currency=usd
```

## Secret keys vs. publishable keys

Publishable keys (`pk_test_...`, `pk_live_...`) are **browser-side** keys. They
pair with a PaymentIntent's `client_secret` so the customer's browser can
complete authentication directly — they are **not** valid for secret-key server
operations and will be rejected on `/v1` write calls.

| Key                     | Side    | Used for                                      |
| ----------------------- | ------- | --------------------------------------------- |
| `sk_test_` / `sk_live_` | Server  | Authenticating `/v1` API requests             |
| `pk_test_` / `pk_live_` | Browser | Client-side confirmation with `client_secret` |

The launch `/v1` surface does **not** accept an `api-key` header —
secret-key auth is the only supported scheme.

## Keeping keys safe

* **Never expose a secret key in browser code, mobile apps, or a public
  repository.** Anyone with `sk_live_...` can move real money. Use the
  publishable key + `client_secret` for anything client-side.
* **Use sandbox keys (`sk_test_`) everywhere except production.** They run against
  the sandbox and can't touch live funds.
* **Store keys in a secrets manager / environment variable**, not in source.
* **Rotate** a key from the dashboard if it may have leaked: create a new key,
  move your integration over, then revoke the old one. Revocation takes effect
  within **60 seconds** (see [Key lifecycle](#key-lifecycle)).

## Authentication errors

A missing, invalid, or revoked key returns HTTP `401` with an
`authentication_error`. The message never confirms whether a key once existed:

```json
{
  "error": {
    "type":    "authentication_error",
    "message": "Invalid API Key provided."
  }
}
```

If Fluveo cannot verify your key because authentication dependencies are temporarily
unavailable, at capacity, or past their deadline, the response is `503 api_error`.
Retry with backoff using the same key; do not rotate it because of this response:

```json
{
  "error": {
    "type": "api_error",
    "message": "The request could not be completed because authentication is temporarily unavailable. Please try again shortly."
  }
}
```

The `503` happens while the key is being checked, before Fluveo does any work for
the request.

An internal authentication configuration or response-integrity failure returns
`500 api_error` with the message "The request could not be completed due to an
internal error. Please try again or contact support." The `500` can happen while
the key is being checked, or later, after Fluveo has read the request (for example
after ownership or idempotency checks), but always before any call to a payment
processor. Neither refusal contains `code` or `param`, and neither sets
`Retry-After`. The `503` carries `Stripe-Should-Retry: true` (nothing ran, so a
retry is safe); the `500` carries no retry header. An invalid or revoked key remains a `401`.
These authentication responses apply to every region, including Brazil.

See [Errors](/errors) for the full error envelope and every error type.