---
name: stripe-fastapi
description: "Provisions Stripe test-mode keys (a shared sandbox account, no signup) for a FastAPI app with `npx extraorbital add stripe` (no signup, no card) and wires it into FastAPI with `stripe`. Use when a FastAPI project needs Stripe test keys, payments, checkout, subscriptions or STRIPE_SECRET_KEY, or when STRIPE_SECRET_KEY is missing or unset in .env."
---

# Stripe test keys in FastAPI

<!-- Generated from ExtraOrbital's service registry by `pnpm skills:build` in platform/app. Edit the generator, not this file. -->

ExtraOrbital provisions Stripe test-mode keys (a shared sandbox account, no signup) in one command and writes the credentials into the project's env file. For anything this recipe does not cover, use the `extraorbital` skill or https://extraorbital.dev/llms.txt.

## 1. Provision

```bash
npx extraorbital add stripe
```

Run it from the project root. It is idempotent (a second run returns the same resource) and writes to `.env.local` if the project has one, otherwise `.env`, keeping that file out of git. Add `--json` to parse the result; credential values are redacted there, and you should never print them either.

| Variable | |
| --- | --- |
| `STRIPE_SECRET_KEY` | `sk_test_…`, server only |
| `NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY` | `pk_test_…`, safe in the browser |
| `STRIPE_WEBHOOK_SECRET` | Written only where the shared account has an endpoint; `stripe listen` prints your own |

Free on the Prototype plan: Shared test account, free on every plan.

## 2. Install the client

```bash
pip install pydantic-settings stripe
```

## 3. Connect

`pydantic-settings` reads `.env` and `.env.local` itself; if `app/config.py` already exists, add the fields to its `Settings`.

**`app/config.py`**

```python
from pydantic_settings import BaseSettings, SettingsConfigDict


class Settings(BaseSettings):
    # ExtraOrbital writes .env.local when the project has one, else .env; the later file wins.
    model_config = SettingsConfigDict(env_file=(".env", ".env.local"), extra="ignore")

    STRIPE_SECRET_KEY: str


settings = Settings()
```

**`app/payments.py`**

```python
import stripe

from app.config import settings

stripe.api_key = settings.STRIPE_SECRET_KEY
# The account is shared with every ExtraOrbital project: tag what you create.
STRIPE_APP_TAG = "my-app"
```

## 4. Use it

**`app/api/stripe.py`**

```python
from fastapi import APIRouter

from app.payments import stripe

router = APIRouter()
# In app/main.py: from app.api import stripe; app.include_router(stripe.router)


@router.get("/api/stripe")
def check_stripe():
    balance = stripe.Balance.retrieve()
    return {"livemode": balance.livemode}
```

Create, then find only your own:

```python
customer = stripe.Customer.create(email="buyer@example.com", metadata={"app": STRIPE_APP_TAG})
# Search, not list: a list returns every project's customers.
mine = stripe.Customer.search(query=f"metadata['app']:'{STRIPE_APP_TAG}'")
```

## 5. Verify

```bash
npx extraorbital list        # the resource is listed as active
fastapi dev app/main.py
curl http://localhost:8000/api/stripe
```

A JSON answer means the credentials, the client and the route all work. Delete the route afterwards if the app does not need it.

## Pitfalls

- From the catalog: Shared test account: every ExtraOrbital project provisioning stripe gets these same keys. Test mode only, so no real money moves, but the customers, products and payment intents you create are visible to everyone else using it and may be deleted at any time. Namespace anything you create, and use your own account for anything you need to keep.
- Render `NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY` into the page that loads Stripe.js; it is public by design. The name is just a name outside Next.js.
- Test cards: `4242 4242 4242 4242` succeeds, `4000 0000 0000 9995` declines. Search results can lag a few seconds behind a create.
- Before taking real payments: `npx extraorbital remove stripe`, then set your own keys under the same names (otherwise `provision` writes the shared ones back).
- A blocking client (`boto3`, `stripe`) belongs in a plain `def` route, which FastAPI runs in a threadpool; in `async def` it stalls every other request.
- With `uv`, `uv add` the same packages instead of `pip install`.
- Exit code 3 means the account is past its free allowance: show the human the link the command printed and wait. Do not retry or work around it.

Docs: https://extraorbital.dev/docs/resources/stripe.md
