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

# Integrate ParaLens into Your Frontend Web Application

> Connect your web app to the ParaLens API using a backend proxy — keep your API URL private, add caching, and handle errors gracefully.

ParaLens uses permissive CORS so browsers can call the API directly, but production apps should use a backend or API route as a proxy. This keeps the Railway URL out of client code and gives you control over caching, rate limiting, and future API key forwarding.

## Direct vs. Proxied Calls

|                          | Direct                                  | Proxied (recommended)                   |
| ------------------------ | --------------------------------------- | --------------------------------------- |
| **Setup complexity**     | None — call the API from your component | Requires a small backend route          |
| **Best for**             | Prototyping, local development          | Production applications                 |
| **Railway URL exposure** | Visible in browser network tab          | Stays server-side                       |
| **API key support**      | Cannot be added securely                | Forward via server environment variable |
| **Caching**              | Not available                           | Add at the proxy layer                  |

## Architecture

```mermaid theme={null}
flowchart LR
  user[User] --> app[Your Frontend]
  app --> proxy[Your Backend / API Route]
  proxy --> api[ParaLens /analyze]
  api --> proxy
  proxy --> app
```

## Setting Up a Proxy

The following example uses a Node.js Express server as the proxy. If you are using Next.js, see the [Next.js Proxy guide](/docs/guides/nextjs-proxy) for a more complete integration.

<Steps>
  <Step title="Install Express">
    Add Express (and optionally `node-cache` for caching) to your project.

    ```bash theme={null}
    npm install express node-fetch
    ```
  </Step>

  <Step title="Create the proxy route">
    Create a file at `server/proxy.js` (or wherever your backend lives) with the following contents.

    ```js theme={null}
    // server/proxy.js
    const express = require('express');
    const router = express.Router();

    const PARALENS_API_URL = process.env.PARALENS_API_URL;

    router.post('/analyze', async (req, res) => {
      try {
        const upstream = await fetch(`${PARALENS_API_URL}/analyze`, {
          method: 'POST',
          headers: {
            'content-type': 'application/json',
            // Future API key support:
            // ...(process.env.PARALENS_API_KEY && { 'x-api-key': process.env.PARALENS_API_KEY }),
          },
          body: JSON.stringify(req.body),
        });

        const data = await upstream.json();
        return res.status(upstream.status).json(data);
      } catch (err) {
        return res.status(502).json({ error: 'Upstream request failed' });
      }
    });

    module.exports = router;
    ```
  </Step>

  <Step title="Set the environment variable">
    Add the Railway URL to your server environment. Never commit this value to source control.

    ```bash theme={null}
    # .env (server-side only)
    PARALENS_API_URL=https://paralens-production.up.railway.app
    ```
  </Step>

  <Step title="Mount the router">
    Mount the proxy router in your main Express app.

    ```js theme={null}
    // server/index.js
    const express = require('express');
    const proxyRouter = require('./proxy');

    const app = express();
    app.use(express.json());
    app.use('/api', proxyRouter);

    app.listen(3001);
    ```
  </Step>
</Steps>

## Error Handling

ParaLens returns three meaningful HTTP status codes. Your client should handle each one explicitly.

| Status | Meaning                                                 | Action                                            |
| ------ | ------------------------------------------------------- | ------------------------------------------------- |
| `200`  | Success                                                 | Parse and render the `TxReport`                   |
| `400`  | User error — malformed or unrecognized transaction hash | Show a validation message; do not retry           |
| `502`  | Upstream failure — ParaLens could not complete analysis | Show a transient error; retry after a short delay |

```ts theme={null}
// client/analyze.ts
async function analyze(txHash: string): Promise<TxReport> {
  const response = await fetch('/api/analyze', {
    method: 'POST',
    headers: { 'content-type': 'application/json' },
    body: JSON.stringify({ chain: 'ethereum', tx_hash: txHash }),
  });

  if (response.status === 400) {
    const error = await response.json();
    throw new Error(`Invalid transaction: ${error.error ?? 'check the hash and try again'}`);
  }

  if (response.status === 502) {
    throw new Error('Analysis temporarily unavailable — please retry in a moment');
  }

  if (!response.ok) {
    throw new Error(`Unexpected error (HTTP ${response.status})`);
  }

  return response.json();
}
```

## Caching

Ethereum transactions are immutable once confirmed. A transaction hash always resolves to the same on-chain data, so caching responses by hash is safe and strongly recommended.

<Tip>
  Cache responses by `tx_hash` with a TTL of **24 hours**. This eliminates redundant upstream calls for any hash your users look up more than once and dramatically reduces latency for popular transactions.
</Tip>

A minimal in-memory cache example:

```ts theme={null}
// client/cache.ts
const cache = new Map<string, { data: TxReport; expiresAt: number }>();
const TTL_MS = 24 * 60 * 60 * 1000; // 24 hours

export function getCached(txHash: string): TxReport | null {
  const entry = cache.get(txHash);
  if (!entry || Date.now() > entry.expiresAt) return null;
  return entry.data;
}

export function setCached(txHash: string, data: TxReport): void {
  cache.set(txHash, { data, expiresAt: Date.now() + TTL_MS });
}
```

For production use, prefer a shared cache such as Redis or an HTTP cache layer so multiple server instances share the same warm entries.

## TypeScript Types

Use these minimal interfaces to type the parts of `TxReport` most relevant to frontend rendering. The full schema includes additional fields; see [Dashboard Layouts](/docs/guides/dashboard-layouts) for guidance on which fields to surface per intent kind.

```ts theme={null}
interface DisplayMoney {
  /** Human-readable amount string, e.g. "$12.34" */
  text: string;
  /** Always check this before using `text` — false means pricing data was unavailable */
  available: boolean;
  /** Explains why `available` is false, when set */
  reason?: string;
}

interface TxReport {
  schema_version: number;
  classification: {
    intent_kind: string;
    intent_label: string;
    status: string;
    confidence: string;
    actor: string | null;
    actor_matches_signer: boolean;
  };
  economics: {
    token_in: string;
    token_out: string;
    net_usd_before_gas: DisplayMoney;
    fee_usd: DisplayMoney;
  };
  tx: {
    hash: string;
    signer: string;
    etherscan_url: string;
  };
  execution: {
    outcome: string;
    reverted: boolean;
    gas_cost_usd: DisplayMoney;
  };
}
```

<Warning>
  Always check `DisplayMoney.available` before rendering `text`. When `available` is `false`, the `text` field may be empty or a placeholder — display a fallback such as "Price unavailable" instead.
</Warning>

## Next Steps

For a complete integration walkthrough including response caching and future API key support, see the [Next.js Proxy guide](/docs/guides/nextjs-proxy).
