PyxlePyxle/Docs

API Routes

Files under pages/api/ are API endpoints. They are plain Python files (not .pyxl) that handle HTTP requests and return JSON or other responses.

Basic API route

Create pages/api/hello.py:

from starlette.requests import Request
from starlette.responses import JSONResponse

async def get(request: Request) -> JSONResponse:
    return JSONResponse({"message": "Hello, world!"})

This responds to GET /api/hello:

curl http://localhost:8000/api/hello
# {"message": "Hello, world!"}

HTTP methods

Define functions named after HTTP methods:

from starlette.requests import Request
from starlette.responses import JSONResponse

async def get(request: Request) -> JSONResponse:
    users = await fetch_all_users()
    return JSONResponse({"users": users})

async def post(request: Request) -> JSONResponse:
    body = await request.json()
    user = await create_user(body["name"], body["email"])
    return JSONResponse({"user": user}, status_code=201)

async def delete(request: Request) -> JSONResponse:
    body = await request.json()
    await remove_user(body["id"])
    return JSONResponse({"deleted": True})

Supported methods: get, post, put, patch, delete, options.

Requests to unsupported methods return 405 Method Not Allowed.

Using HTTPEndpoint classes

For more structure, use Starlette's HTTPEndpoint:

from starlette.endpoints import HTTPEndpoint
from starlette.requests import Request
from starlette.responses import JSONResponse

class Users(HTTPEndpoint):
    async def get(self, request: Request) -> JSONResponse:
        return JSONResponse({"users": []})

    async def post(self, request: Request) -> JSONResponse:
        body = await request.json()
        return JSONResponse({"created": True}, status_code=201)

Dynamic API routes

Use the same bracket syntax as page routes:

pages/api/users/[id].py  -->  /api/users/:id
from starlette.requests import Request
from starlette.responses import JSONResponse

async def get(request: Request) -> JSONResponse:
    user_id = request.path_params["id"]
    user = await fetch_user(user_id)
    if user is None:
        return JSONResponse({"error": "Not found"}, status_code=404)
    return JSONResponse({"user": user})

Reading request bodies

async def post(request: Request) -> JSONResponse:
    # JSON body
    body = await request.json()

    # Form data
    form = await request.form()

    # Raw body
    raw = await request.body()

    return JSONResponse({"received": True})

Error responses

Return appropriate HTTP status codes:

async def get(request: Request) -> JSONResponse:
    api_key = request.headers.get("x-api-key")
    if not api_key:
        return JSONResponse({"error": "Missing API key"}, status_code=401)

    data = await fetch_data(api_key)
    if data is None:
        return JSONResponse({"error": "Not found"}, status_code=404)

    return JSONResponse({"data": data})

API routes vs server actions

Feature API routes Server actions
File location pages/api/*.py Inside .pyxl files
HTTP methods Any (GET, POST, PUT, etc.) POST only
Response format Any Starlette Response JSON dict
Called from Anywhere (curl, fetch, etc.) <Form> or useAction
CSRF protection Not by default Enabled by default
Use case Public APIs, webhooks, integrations Form submissions, mutations

Next steps