> ## Documentation Index
> Fetch the complete documentation index at: https://docs.praxa.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Build a Praxa backend with FastAPI

> Create a typed FastAPI boundary for Praxa tasks and test authentication, validation, replay, and upstream failures with pytest.

FastAPI dependencies are a good place to resolve the authenticated application
user before Praxa is called. Pydantic then bounds the request shape.

## Prerequisites

Before you begin, prepare:

* a trusted server runtime and application authentication boundary;
* a disposable personal workspace Praxa key with only the tutorial's required scopes;
* synthetic input plus a persisted application request ID for replay tests;
* a fake upstream for unit tests and a non-production environment for canaries;
* an acceptance assertion that proves the FastAPI dependency boundary admits one task and maps failures without leaking upstream data.

## 1. Install dependencies

```bash theme={null}
python -m pip install fastapi uvicorn httpx pytest
export PRAXA_API_KEY="praxa_sk_<redacted>"
```

## 2. Implement the route

```python app/main.py theme={null}
import hashlib
import os
import httpx
from fastapi import Depends, FastAPI, HTTPException, Response
from pydantic import BaseModel, Field

app = FastAPI()

class TaskInput(BaseModel):
    task: str = Field(min_length=1, max_length=16_000)
    request_id: str = Field(alias="requestId", min_length=16, max_length=128)

@app.post("/api/tasks")
async def create_task(
    body: TaskInput,
    response: Response,
    user=Depends(require_user),
):
    digest = hashlib.sha256(
        f"{user.id}\0{body.request_id}".encode()
    ).hexdigest()
    key = f"fastapi:{digest}"

    async with httpx.AsyncClient(timeout=30) as client:
        upstream = await client.post(
            "https://api.praxa.io/v1/execute",
            headers={
                "Authorization": f"Bearer {os.environ['PRAXA_API_KEY']}",
                "Idempotency-Key": key,
            },
            json={
                "apiVersion": "v1",
                "requestId": key,
                "mode": "task",
                "task": {"input": body.task},
                "idempotencyKey": key,
            },
        )

    if upstream.status_code >= 400:
        raise HTTPException(
            upstream.status_code,
            "Praxa request failed",
            headers={"Cache-Control": "no-store"},
        )
    response.headers["Cache-Control"] = "no-store"
    return upstream.json()
```

The application user comes from <code>require\_user</code>, not the body. Do
not include the upstream response text in a production exception unless you
have explicitly redacted it.

## 3. Test with dependency overrides

```python tests/test_tasks.py theme={null}
from fastapi.testclient import TestClient
from app.main import app, require_user

class User:
    id = "test-user"

app.dependency_overrides[require_user] = lambda: User()
client = TestClient(app)

def test_rejects_invalid_task():
    response = client.post(
        "/api/tasks",
        json={"task": "", "requestId": "request-00000001"},
    )
    assert response.status_code == 422
```

Intercept HTTPX for valid-route tests and assert the outgoing URL, bounded
body, server-owned authorization header, and deterministic idempotency key.
Add a separate dependency override that raises <code>401</code> and prove
there is no upstream call.

## 4. Run the live acceptance check

```bash theme={null}
uvicorn app.main:app --reload
pytest -q
```

Against staging, submit one disposable task twice with the same
<code>requestId</code>. Require the same logical run, then poll that run to a
terminal state. Revoke the test key after cleanup.

## Troubleshooting

| Symptom                       | Fix                                                    |
| ----------------------------- | ------------------------------------------------------ |
| Validation returns `422`      | Inspect Pydantic field names, aliases, and bounds      |
| Event loop stalls             | Use `httpx.AsyncClient` and reuse it across requests   |
| `401` from your route         | Fix application authentication before inspecting Praxa |
| `403` from Praxa              | Verify personal key ownership and exact scopes         |
| Timeout after submission      | Retry the exact body/key and read the run              |
| Upstream text reaches clients | Map `HTTPStatusError` to a safe application problem    |

## Best practices

* Create one `AsyncClient` at application startup and close it at shutdown.
* Resolve the user with a dependency before calling Praxa.
* Bound Pydantic models and request-body size.
* Do not include tenant, owner, scope, or Praxa key in the request model.
* Keep error details redacted and responses `no-store`.
* Use HTTPX `MockTransport` for unit tests and disposable keys for canaries.

<Card title="FastAPI testing" icon="arrow-up-right-from-square" href="https://fastapi.tiangolo.com/tutorial/testing/">
  Use the official TestClient and pytest guidance for the application boundary.
</Card>

## Optimize for production

* Reuse one configured HTTP or SDK client per process and bound concurrent upstream work.
* Prefer durable admission plus asynchronous readback over holding application requests open.
* Cache only non-sensitive, tenant-scoped reads within their documented freshness window.
* Measure p50/p95 latency, admission-to-terminal time, retries, conflicts, and connection reuse before tuning.

Optimize only after the correctness and isolation matrix passes. Lower latency or cost is not an improvement if verified outcomes, authority checks, or recovery rates regress.

## Cleanup and next steps

1. Revoke the disposable Praxa key and require a later request to fail.
2. Remove synthetic application records and any temporary environment files.
3. Cancel or archive unresolved test runs according to the application policy.
4. Retain only redacted request, run, and verification identifiers needed for the test record.

After cleanup, run the [shared integration test matrix](/tutorials/test-your-integration) and record any environment-specific check that remains pending.

## Frequently asked questions

### What proves this tutorial works?

The minimum observable result is that the FastAPI dependency boundary admits one task and maps failures without leaking upstream data. A compile, package import, mocked response, or initial admission alone does not prove the complete workflow.

### Can a browser, mobile app, or model prompt hold the credential?

No. Browser and mobile bundles are inspectable. Keep the Praxa credential in the trusted application backend.

### How should an ambiguous mutation be retried?

Persist the exact logical input and idempotency key before the first attempt. Reconcile through authoritative readback or replay the exact request with that same key before creating new work.

### What should we monitor after release?

Monitor application auth failures, upstream status/code, latency, retries, replay conflicts, and terminal run outcomes. Alert on authorization bypass, cross-tenant disclosure, repeated conflicts, or cleanup failure.
