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

# Use Praxa from React Native and Expo

> Call a trusted Praxa backend from React Native or Expo without embedding API keys, and test loading, retry, offline, cancellation, and secure-session behavior.

React Native provides `fetch`, but a mobile bundle cannot safely hold a Praxa
API key or backend OAuth token. Your app calls your authenticated application
backend; that backend validates the user and calls Praxa.

```mermaid theme={null}
flowchart LR
  App["React Native or Expo app"] -->|"app session"| BFF["Your HTTPS backend"]
  BFF -->|"server-only Praxa credential"| Praxa["Praxa API"]
  Praxa --> Run["Run or mission projection"]
  BFF -->|"bounded app response"| App
```

## Prerequisites

Before you begin, prepare:

* an authenticated application backend that owns the Praxa credential;
* a reviewed mobile session mechanism and an app-facing bounded API contract;
* a simulator or emulator plus at least one physical-device test plan;
* synthetic data and a persisted local request identifier for lifecycle recovery;
* an acceptance assertion that proves the app never receives a Praxa key and recovers the same request after backgrounding.

## 1. Define the app contract

Your backend should expose a product-specific route such as:

```json theme={null}
POST /api/tasks
{
  "task": "Summarize the incident and propose a safe next action.",
  "requestId": "mobile-request-0001"
}
```

The backend derives user, tenant, scopes, Praxa credential, and upstream
idempotency key. The app never sends them.

## 2. Implement the mobile client

```ts src/api/praxa.ts theme={null}
export type TaskRun = {
  runId: string;
  status: "queued" | "running" | "completed" | "failed" | "cancelled";
};

export async function startTask(input: {
  apiOrigin: string;
  applicationToken: string;
  task: string;
  requestId: string;
  signal?: AbortSignal;
}): Promise<TaskRun> {
  const response = await fetch(`${input.apiOrigin}/api/tasks`, {
    method: "POST",
    headers: {
      authorization: `Bearer ${input.applicationToken}`,
      "content-type": "application/json",
    },
    body: JSON.stringify({ task: input.task, requestId: input.requestId }),
    signal: input.signal,
  });

  if (!response.ok) {
    throw new Error(`Task request failed with HTTP ${response.status}`);
  }
  return response.json();
}
```

`applicationToken` belongs to your app, not Praxa. Store user-session material
with your reviewed secure-storage approach, not Async Storage.

## 3. Preserve the logical request across retries

Create and persist `requestId` when the user commits the action. Reuse it while
that exact task is unresolved:

```ts theme={null}
const requestId = pendingDraft.requestId ?? newApplicationRequestId();
await savePendingDraft({ task, requestId });

const run = await startTask({
  apiOrigin: config.apiOrigin,
  applicationToken: session.accessToken,
  task,
  requestId,
  signal: controller.signal,
});

await markDraftAdmitted(requestId, run.runId);
```

Implement `newApplicationRequestId()` with the secure UUID facility or reviewed
UUID library already used by your app; do not derive it from timestamps or
user identifiers.

If the user edits the task after a failed attempt, create a new request ID.

## 4. Design reachable states

| State                 | UI behavior                                                            |
| --------------------- | ---------------------------------------------------------------------- |
| Offline before submit | Keep the draft and explain that no request was sent                    |
| Sending               | Disable duplicate submit while preserving the label and draft          |
| Timeout/unknown       | Offer “Check status” or exact retry with the same request ID           |
| Admitted              | Show run ID/status; do not call it complete                            |
| Running               | Poll or subscribe through your backend                                 |
| Failed                | Show a customer-safe cause and a deliberate retry path                 |
| Completed             | Show the verified projection supplied by your backend                  |
| Signed out/revoked    | Return to application authentication; never fall back to a bundled key |

## 5. Test the app

With Jest, React Native Testing Library, or your network mock:

1. Assert the app calls only your application origin.
2. Assert no `praxa_sk_`, Gateway token, or provider key exists in the bundle.
3. Assert repeated taps do not create a second logical request.
4. Assert an ambiguous failure retains the draft and request ID.
5. Assert cancellation aborts the local request but does not claim the run was
   cancelled.
6. Assert `401` clears or refreshes only the application session.
7. Test offline, slow, `429`, malformed response, and app-background recovery.

Then run one staging task on both iOS and Android, background and restore the
app, and reconcile the run through your backend.

## Troubleshooting

| Symptom                       | Fix                                                                                      |
| ----------------------------- | ---------------------------------------------------------------------------------------- |
| Works on web but not device   | Verify HTTPS, device-reachable app origin, ATS, and Android cleartext policy             |
| CORS assumption is wrong      | Native networking does not use the browser CORS model; still enforce auth on the backend |
| Token visible in bundle       | It is not safe; revoke it and move the Praxa call server-side                            |
| Retry creates duplicate work  | Persist request ID before the first request                                              |
| Cookies behave inconsistently | Use your reviewed mobile auth library and explicit application bearer/session design     |
| Backgrounding loses status    | Persist request/run IDs and reconcile on foreground                                      |

## Best practices

* Keep Praxa credentials and provider clients on the backend.
* Use HTTPS and platform network-security defaults.
* Store only application session material in secure storage.
* Persist unresolved request identity, not full sensitive payloads, when
  possible.
* Redact task content from crash and analytics products.
* Test physical-device network, background, and resume behavior separately
  from unit tests.

<CardGroup cols={2}>
  <Card title="React Native networking" icon="arrow-up-right-from-square" href="https://reactnative.dev/docs/network">
    Review current Fetch behavior and native platform networking constraints.
  </Card>

  <Card title="React Native security" icon="shield" href="https://reactnative.dev/docs/security">
    Review why secrets do not belong in app code and how an orchestration layer
    protects server API keys.
  </Card>
</CardGroup>

## Optimize for production

* Debounce repeated UI actions while preserving the same logical request ID.
* Return small app-facing projections and paginate history instead of copying upstream payloads.
* Reconcile on foreground with one bounded read rather than restarting the operation.
* Measure device-to-backend latency, resume success, duplicate prevention, payload size, and energy impact.

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 disposable backend credentials and test application sessions.
2. Delete synthetic server records and clear test-only secure-storage entries.
3. Remove captured screenshots, logs, and crash reports containing synthetic payloads.
4. Record physical-device, background, and recovery checks separately from unit tests.

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 app never receives a Praxa key and recovers the same request after backgrounding. 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. The application calls its own authenticated backend; the backend owns every Praxa and provider credential.

### 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 mobile request latency, background/resume recovery, duplicate suppression, app-facing errors, and terminal readback. Alert on authorization bypass, cross-tenant disclosure, repeated conflicts, or cleanup failure.
