Docs

Quickstart: Python and FastAPI

Build a notes API with FastAPI that signs users in through Sovaria and runs every query as the calling user with the async Python SDK.

Updated Oct 24, 2026

A FastAPI service with POST /signup, POST /signin, GET /notes and POST /notes, using the async client. Each request runs as the user whose token it carries, so the row rules decide what it sees.

#Before you start

Do steps 1 to 3 of the Quickstart first: create a project, copy its project URL and anon key (Quickstart), and create the notes table with its row rules (Quickstart). Every signed-in user then sees and changes only their own notes, whatever the app code does.

#1. Create the project

mkdir notes-api && cd notes-api
python3 -m venv .venv && . .venv/bin/activate
pip install "fastapi[standard]" sovaria-sdk
export SOVARIA_URL=https://<project-ref>.sovarias.in SOVARIA_ANON_KEY=<anon-key>

Python 3.11 or newer.

#2. The service

import os

from fastapi import Depends, FastAPI, Header, HTTPException
from pydantic import BaseModel
from sovaria import SovariaError, create_client

URL, ANON_KEY = os.environ["SOVARIA_URL"], os.environ["SOVARIA_ANON_KEY"]
app = FastAPI()


class Credentials(BaseModel):
    email: str
    password: str


class NoteIn(BaseModel):
    body: str


def to_objects(result: dict) -> list[dict]:
    """sql_query answers columns, rows and row_count; rows are lists in column order."""
    return [dict(zip(result["columns"], row)) for row in result["rows"]]


async def as_user(authorization: str = Header(default="")):
    """A client that runs every call as the user whose token the request carries."""
    token = authorization.removeprefix("Bearer ").strip()
    if not token:
        raise HTTPException(401, "Sign in first")
    async with create_client(URL, ANON_KEY) as client:
        client.set_access_token(token)
        yield client


def fail(err: SovariaError):
    raise HTTPException(err.status_code or 502, str(err.detail))


def tokens(t) -> dict:
    """What the app keeps: the access and refresh tokens (no other fields of the sign-in answer)."""
    return t.model_dump(include={"access_token", "refresh_token", "expires_in", "user_id"})


@app.post("/signup")
async def signup(c: Credentials):
    async with create_client(URL, ANON_KEY) as client:
        try:
            return tokens(await client.sign_up(c.email, c.password))
        except SovariaError as err:
            fail(err)


@app.post("/signin")
async def signin(c: Credentials):
    async with create_client(URL, ANON_KEY) as client:
        try:
            return tokens(await client.sign_in(c.email, c.password))
        except SovariaError as err:
            fail(err)


@app.get("/notes")
async def list_notes(sovaria=Depends(as_user)):
    try:
        return to_objects(await sovaria.sql_query("select id, body, created_at from notes order by id desc"))
    except SovariaError as err:
        fail(err)


@app.post("/notes", status_code=201)
async def add_note(note: NoteIn, sovaria=Depends(as_user)):
    try:
        result = await sovaria.sql_query("insert into notes (body) values ($1) returning id, body", params=[note.body])
        return to_objects(result)[0]
    except SovariaError as err:
        fail(err)

#3. Run and try it

fastapi dev main.py   # http://127.0.0.1:8000/docs

TOKEN=$(curl -s 127.0.0.1:8000/signup -H 'Content-Type: application/json' \
  -d '{"email":"ada@example.com","password":"Correct-Horse-9!"}' | jq -r .access_token)
curl -s 127.0.0.1:8000/notes -H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' -d '{"body":"From FastAPI"}'
curl -s 127.0.0.1:8000/notes -H "Authorization: Bearer $TOKEN"

#Next steps

#Coming from Firebase or Supabase?

PlatformWhat this is called there
FirebaseFirebase Admin SDK for Python
Supabasesupabase-py with FastAPI
SovariaQuickstart: Python and FastAPI

Moving an existing app? Migrate from Supabase or Firebase imports users (with their passwords), data, files and rules.