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

# Quickstart

> One secret, one mount, one call.

Four lines on your server, two components on your page, one secret: `PAYMAN_APP_KEY`. Node 22.5 or newer.

<Note>
  **Building an agent?** If your agent already runs a model against a tools array, give it `payman.tool()` and answer the call with `handleToolUse()`: see [Add it as a tool](/build/tool-integration). That swaps only the `run()` call below. The app, the mount, the buttons, and the four results are the same.
</Note>

<Steps>
  <Step title="Register an app">
    In the developer console (the button at the top right), open **Apps**, then **Register an app**.

    | Field         | Rule                                                                                                                                                                                                                                              |
    | ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
    | Environment   | `sandbox` or `live`. Chosen once, permanent. Sandbox apps offer the Durango demo bank on the consent page.                                                                                                                                        |
    | Name          | What customers read on the consent page as "... wants access".                                                                                                                                                                                    |
    | Providers     | What your app asks to reach. The consent page offers at most what you declare: deprecated providers drop out.                                                                                                                                     |
    | Redirect URIs | `https://yourapp.example/payman/callback`, and `http://localhost:3000/payman/callback` for local dev. Absolute, no wildcard, `https` unless the host is loopback: `localhost` and `127.0.0.1` are different bytes, so register the one you serve. |

    The app key is shown once. Copy it then; if you lose it, generate a new one.

    <Warning>
      The app key is a secret. Server only: never a browser, a query string, or a tool schema. It is also the only Payman value you configure; the SDK resolves everything else from it.
    </Warning>
  </Step>

  <Step title="Install">
    ```bash theme={null}
    npm install @paymanai/connect @paymanai/connect-express
    npm install @paymanai/connect-react   # React UI components
    ```

    ```bash theme={null}
    export PAYMAN_APP_KEY=pgc_app_...     # pgc_app_test_... = sandbox
    ```

    One base URL serves sandbox and live; the key prefix picks the environment. Set `PAYMAN_BASE_URL` only when pointing at a local or self-hosted stack.

    <Note>
      **Not on Express?** The same routes ship as web-standard handlers at `@paymanai/connect/fetch`, a subpath of the core package. Next.js has its own wrapper: see [Next.js](/nextjs). The rest of this page is identical either way.
    </Note>
  </Step>

  <Step title="Mount and run">
    ```ts theme={null}
    import express from "express";
    import { PaymanConnect } from "@paymanai/connect";

    const payman = new PaymanConnect();   // reads PAYMAN_APP_KEY
    const app = express();
    app.use(express.json());

    await payman.express.mount(app, { currentUser: (req) => req.session.userId });

    app.post("/ask", async (req, res) => {
      res.json(await payman.forUser(req.session.userId).run(req.body.text));
    });
    ```

    That is the whole server. `mount()` is async: await it. It registers three GET routes on your origin: `/payman/callback` for the consent return, plus `/payman/approvals/:ref/stream` and `/payman/resume/stream` for your page to watch. `currentUser` is the only thing the SDK reads from your auth; wire it to however you identify users.

    Connections land in an encrypted SQLite store at `./.payman`. `forUser()` is per call; never hoist it.

    Your session cookie must be `SameSite=Lax` or the consent return drops it in production, which you cannot catch locally; `mount()` prints a reminder. `await payman.doctor()` checks your registered redirect URI and the registration clock; run it in CI.
  </Step>

  <Step title="Render the UI">
    ```tsx theme={null}
    import "@paymanai/connect-react/styles.css";
    import { PaymanProvider, ConnectButton, ApprovalButton } from "@paymanai/connect-react";

    // At the root. autoResume replays the held question after consent.
    // basePath matches where mount() served the routes; both default to /payman.
    <PaymanProvider basePath="/payman" autoResume>
      <Chat />
    </PaymanProvider>;

    // Wherever a result renders:
    {result.kind === "needs_connection" && <ConnectButton result={result} />}

    {result.kind === "approval" && (
      <ApprovalButton
        approval={result.approval}
        onSettled={(r) => note(`Sent. Reference ${r.transactionRef}.`)}
      />
    )}
    ```

    `<ConnectButton>` opens the consent popup, falls back to a plain link when the popup is blocked, and retires itself once connected. `<ApprovalButton>` opens Payman's approval page; the customer approves there. Your app never approves and has no field that could carry a code; `onSettled` hands you the receipt.

    The components are headless. Import `styles.css` once, or style the class names yourself; `className` replaces the default class.
  </Step>

  <Step title="Handle the four results">
    `run()` resolves one of four shapes and never throws for wire failures. Switch on `result.kind`. The [tool path](/build/tool-integration) returns the same four on `turn.ui`, so this table and the buttons above serve either integration.

    | `result.kind`      | What happened                               | Your app                                                                       |
    | ------------------ | ------------------------------------------- | ------------------------------------------------------------------------------ |
    | `ok`               | The action completed                        | Render `result.message`                                                        |
    | `needs_connection` | No connection for this user, or it ended    | Render `<ConnectButton>`. The instruction is stashed and replays after consent |
    | `approval`         | A payment froze for the customer's approval | Render `<ApprovalButton>`; `onSettled` delivers the receipt                    |
    | `error`            | Something failed                            | Show `result.guidance`. Retry once if `result.retryable`                       |

    `JSON.stringify(result)` is safe to send to a browser; diagnostics live on a non-enumerable symbol.
  </Step>
</Steps>

## Next

<CardGroup cols={2}>
  <Card title="Add it as a tool" icon="wrench" href="/build/tool-integration">
    Your agent already runs a model. This is the one tool it needs.
  </Card>

  <Card title="Actions" icon="bolt" href="/build/actions">
    run(), sessions, streaming, the four results in detail.
  </Card>

  <Card title="Approvals" icon="shield-check" href="/build/approvals">
    What happens when a payment freezes, start to receipt.
  </Card>

  <Card title="Any framework" icon="layer-group" href="/frameworks">
    The same routes on Hono, SvelteKit, Bun, Deno.
  </Card>

  <Card title="Example app" icon="play" href="/reference/example-app">
    The whole pattern as a running app you can read.
  </Card>
</CardGroup>
