# Use SupaHost with Python

> Use supabase-py with a SupaHost project. Install the client, query and insert rows, sign users in, run admin scripts with the service_role key, verify users in FastAPI and use the async client.

Source: https://supahost.app/docs/getting-started/python · Updated 2026-11-11

`supabase-py` is the official Python client. It talks to your project over HTTPS through the same Data API, Auth, Storage and Functions endpoints the JavaScript client uses, so it works with SupaHost with no changes and no database connection. That matters on SupaHost, where Postgres isn't reachable from outside the instance except through the Business plan's SSH tunnel.

The examples use the `recipes` table from the [quickstart](/docs/getting-started/quickstart).

## Install

```bash
python -m venv .venv
source .venv/bin/activate
pip install supabase
```

## Add your project keys

Take the Project URL and keys from the project's **Connect** tab (subtab **App**) and export them:

```bash
export SUPABASE_URL=https://app-one.acme.supahost.space
export SUPABASE_ANON_KEY=eyJhbGciOiJIUzI1NiIs...
# Only for trusted server code and scripts:
export SUPABASE_SERVICE_ROLE_KEY=eyJhbGciOiJIUzI1NiIs...
```

Use the `anon` key for anything that acts on behalf of a user. The `service_role` key bypasses Row Level Security; keep it in server-side secrets.

## Query and insert

```python
import os
from supabase import create_client

supabase = create_client(os.environ["SUPABASE_URL"], os.environ["SUPABASE_ANON_KEY"])

response = (
    supabase.table("recipes")
    .select("id, title, minutes")
    .lte("minutes", 30)
    .order("created_at", desc=True)
    .execute()
)
for recipe in response.data:
    print(recipe["title"], recipe["minutes"])
```

Failed requests raise an exception carrying Postgres's error code and message:

```python
from postgrest.exceptions import APIError

try:
    supabase.table("recipes").insert({"title": "Lemon rice", "minutes": 30}).execute()
except APIError as err:
    print(err.code, err.message)  # 42501 for a Row Level Security violation
```

This insert fails while signed out, because the quickstart's insert policy is for `authenticated` users.

## Sign in

```python
supabase.auth.sign_up({"email": "cook@example.com", "password": "a-long-password"})

session = supabase.auth.sign_in_with_password(
    {"email": "cook@example.com", "password": "a-long-password"}
)
print(session.user.id)

# Requests on this client now carry the user's token.
supabase.table("recipes").insert({"title": "Lemon rice", "minutes": 30}).execute()

supabase.auth.sign_out()
```

## Admin scripts with the service role

For back-office jobs, such as creating users or fixing data, create a separate client with the `service_role` key. It skips Row Level Security and can call the Auth admin API:

```python
import os
from supabase import create_client

admin = create_client(os.environ["SUPABASE_URL"], os.environ["SUPABASE_SERVICE_ROLE_KEY"])

admin.auth.admin.create_user(
    {"email": "chef@example.com", "password": "a-long-password", "email_confirm": True}
)

old = admin.table("recipes").delete().lt("created_at", "2024-01-01").execute()
print(f"removed {len(old.data)} rows")
```

## Verify users in an API (FastAPI)

When your frontend calls a Python API, send the user's access token in the `Authorization` header. The API checks it with Auth and then queries as that user, so policies still apply:

```python
import os
from fastapi import FastAPI, Header, HTTPException
from supabase import create_client, ClientOptions

URL = os.environ["SUPABASE_URL"]
ANON = os.environ["SUPABASE_ANON_KEY"]

app = FastAPI()

@app.get("/my-recipes")
def my_recipes(authorization: str = Header(...)):
    token = authorization.removeprefix("Bearer ").strip()
    client = create_client(
        URL, ANON, options=ClientOptions(auto_refresh_token=False, persist_session=False)
    )
    try:
        user = client.auth.get_user(token).user
    except Exception:
        raise HTTPException(status_code=401, detail="invalid token")

    client.postgrest.auth(token)  # run queries as this user
    rows = client.table("recipes").select("id, title").eq("user_id", user.id).execute()
    return rows.data
```

Create a client per request, as above. A shared client would mix up users.

## The async client

For asyncio apps, use `acreate_client`:

```python
import asyncio, os
from supabase import acreate_client

async def main():
    supabase = await acreate_client(os.environ["SUPABASE_URL"], os.environ["SUPABASE_ANON_KEY"])
    res = await supabase.table("recipes").select("id, title").limit(5).execute()
    print(res.data)

asyncio.run(main())
```

## Edge Functions and Storage

The same client calls your other services:

```python
result = supabase.functions.invoke("estimate-cost", invoke_options={"body": {"recipe_id": 1}})
files = supabase.storage.from_("photos").list()
```

## SQLAlchemy, Django and psycopg

ORMs and drivers that open a Postgres connection need direct database access. On SupaHost that's only available on Business and Enterprise, through the [SSH tunnel](/docs/platform/ssh), and it's meant for tools on a developer machine, such as running migrations. For application code on other plans, use supabase-py as shown here. See [Using SQLAlchemy with Supabase Postgres](/kb/sqlalchemy).

## Next steps

- [Querying and filtering data](/docs/api/querying)
- [Calling database functions (RPC)](/docs/api/rpc) with `supabase.rpc()`
- [Managing users (admin API)](/docs/auth/user-management)
- [Use SupaHost from a Node.js server](/docs/getting-started/node-server) for the same patterns in JavaScript
