> ## 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.

# Integrate Praxa from your backend framework

> Keep Praxa credentials on the server while integrating Next.js, Express, Cloudflare Workers, FastAPI, React, Vue, and Svelte applications.

Praxa credentials belong in trusted server code. A browser should call an
application-specific backend route that authenticates the user, derives the
workspace, validates input, and then calls Praxa with a server-only credential.

```text theme={null}
Browser or mobile client
        |
        | authenticated application request
        v
Your backend route or Worker
        |
        | server-only Praxa credential
        v
Praxa API or Integration Gateway
```

## 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 chosen framework preserves the same server-owned credential, authority, and replay contract.

## Shared boundary requirements

Every framework implementation should:

1. Authenticate the application user before accepting work.
2. Derive the tenant or workspace from the session, not the request body.
3. Validate and bound user input before forwarding it.
4. Create one stable idempotency key per logical mutation.
5. Keep Praxa keys, OAuth tokens, provider clients, and webhook secrets server-only.
6. Return only customer-safe Praxa projections to the browser.
7. Rate limit by the authenticated principal and application use case.

## Next.js App Router

```ts app/api/tasks/route.ts theme={null}
import { NextResponse } from "next/server";

export async function POST(request: Request) {
  const session = await requireSession();
  const body = await request.json();
  if (typeof body.input !== "string" || body.input.length < 1 || body.input.length > 16_000) {
    return NextResponse.json({ error: "Invalid task input" }, { status: 400 });
  }

  const idempotencyKey = await idempotencyKeyFor(session.user.id, body.requestId);
  const response = await fetch("https://api.praxa.io/v1/execute", {
    method: "POST",
    headers: {
      authorization: `Bearer ${process.env.PRAXA_API_KEY}`,
      "content-type": "application/json",
      "idempotency-key": idempotencyKey,
    },
    body: JSON.stringify({
      apiVersion: "v1",
      requestId: idempotencyKey,
      mode: "task",
      task: { input: body.input },
      idempotencyKey,
    }),
  });

  return new NextResponse(response.body, {
    status: response.status,
    headers: {
      "content-type": response.headers.get("content-type") ?? "application/json",
      "cache-control": "no-store",
    },
  });
}
```

Do not prefix the key variable with `NEXT_PUBLIC_`. That would make it eligible
for the client bundle.

## Express

```ts theme={null}
app.post("/api/tasks", requireSession, async (request, response, next) => {
  try {
    const input = request.body?.input;
    if (typeof input !== "string" || input.length < 1 || input.length > 16_000) {
      return response.status(400).json({ error: "Invalid task input" });
    }

    const idempotencyKey = await idempotencyKeyFor(
      request.user.id,
      request.body.requestId,
    );
    const upstream = await fetch("https://api.praxa.io/v1/execute", {
      method: "POST",
      headers: {
        authorization: `Bearer ${process.env.PRAXA_API_KEY}`,
        "content-type": "application/json",
        "idempotency-key": idempotencyKey,
      },
      body: JSON.stringify({
        apiVersion: "v1",
        requestId: idempotencyKey,
        mode: "task",
        task: { input },
        idempotencyKey,
      }),
    });
    response.status(upstream.status).send(await upstream.text());
  } catch (error) {
    next(error);
  }
});
```

## Cloudflare Workers or Hono

```ts theme={null}
type Env = { PRAXA_API_KEY: string };

export default {
  async fetch(request: Request, env: Env): Promise<Response> {
    const principal = await authenticateApplicationRequest(request, env);
    if (!principal) return new Response("Unauthorized", { status: 401 });

    const { input, requestId } = await request.json<{
      input: string;
      requestId: string;
    }>();
    if (!input || input.length > 16_000) {
      return new Response("Invalid input", { status: 400 });
    }

    const idempotencyKey = await idempotencyKeyFor(principal.id, requestId);
    return fetch("https://api.praxa.io/v1/execute", {
      method: "POST",
      headers: {
        authorization: `Bearer ${env.PRAXA_API_KEY}`,
        "content-type": "application/json",
        "idempotency-key": idempotencyKey,
      },
      body: JSON.stringify({
        apiVersion: "v1",
        requestId: idempotencyKey,
        mode: "task",
        task: { input },
        idempotencyKey,
      }),
    });
  },
};
```

Store the key with `wrangler secret put PRAXA_API_KEY`, not in
`wrangler.jsonc` or source control.

## FastAPI

```python theme={null}
import os
import hashlib
import httpx
from fastapi import Depends, FastAPI, HTTPException
from pydantic import BaseModel, Field

app = FastAPI()

class TaskInput(BaseModel):
    input: str = Field(min_length=1, max_length=16_000)
    request_id: str = Field(
        alias="requestId",
        min_length=16,
        max_length=128,
        pattern=r"^[A-Za-z0-9][A-Za-z0-9._:-]{15,127}$",
    )

@app.post("/api/tasks")
async def create_task(body: TaskInput, user=Depends(require_user)):
    digest = hashlib.sha256(f"{user.id}\0{body.request_id}".encode()).hexdigest()
    key = f"task:{digest}"
    async with httpx.AsyncClient(timeout=30) as client:
        response = 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.input},
                "idempotencyKey": key,
            },
        )
    if response.status_code >= 400:
        raise HTTPException(response.status_code, response.text)
    return response.json()
```

The FastAPI example hashes the authenticated user ID with the caller's stable
application request ID. That makes retries deterministic without leaking user
identifiers into the upstream key. Every idempotency key must satisfy the Praxa
identifier grammar.

## React, Vue, and Svelte clients

Frontend frameworks use the same application backend boundary:

```ts theme={null}
export async function startTask(input: string) {
  const response = await fetch("/api/tasks", {
    method: "POST",
    credentials: "same-origin",
    headers: { "content-type": "application/json" },
    body: JSON.stringify({ input, requestId: crypto.randomUUID() }),
  });
  if (!response.ok) throw new Error(await response.text());
  return response.json();
}
```

React, Vue, and Svelte components may call this function. They should never
call `api.praxa.io` with a server API key or receive provider credentials.

## Verify the framework boundary

1. Search the production client bundle for `praxa_sk_`, webhook secrets, and OAuth tokens; require zero matches.
2. Call the route without an application session and require `401` before any Praxa request.
3. Send an over-limit input and require `400` before any Praxa request.
4. Submit valid input and require a customer-safe run projection.
5. Retry with the same application request ID and require the same logical run.
6. Attempt to supply a tenant or workspace ID in the browser body and require it to be ignored or rejected.
7. Revoke the backend Praxa credential and require the route to return a safe, non-secret error.

## Troubleshooting

| Symptom                        | Resolution                                                                          |
| ------------------------------ | ----------------------------------------------------------------------------------- |
| Browser can see a Praxa key    | Move every Praxa call into the authenticated backend and rebuild the client bundle. |
| Timeout may have admitted work | Reuse the exact stored body and idempotency key, then reconcile the run.            |
| Unexpected `403`               | Verify personal workspace ownership and the exact endpoint scopes.                  |
| Upstream details reach users   | Map problems to a bounded application error and log only safe identifiers.          |

## Best practices

* Authenticate, rate-limit, and validate a bounded body before calling Praxa.
* Derive tenant and subject from the application session, never client input.
* Reuse clients and connection pools; set connect, request, and total deadlines.
* Persist request identity before the first mutation and reconcile ambiguous outcomes.
* Scan production client assets and telemetry for credential-shaped values.

## 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 chosen framework preserves the same server-owned credential, authority, and replay contract. 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.
