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

# Quickstart

> A complete working Monei Connect integration in Node.js and Python from redirect to API call

This guide walks you through a full OAuth 2.0 integration with Monei Connect. By the end, your app will be able to redirect users to Monei, receive an authorization code, exchange it for tokens, and call the Monei API on the user's behalf.

**Prerequisites:** You've already [registered your app](/connect/register-app) and have a `client_id` and `client_secret`.

***

## What we're building

A simple web server that:

1. Redirects a user to Monei to authorize wallet access
2. Handles the callback and exchanges the code for tokens
3. Calls `GET /api/v1/wallet/me` using the access token

***

## Environment setup

```bash theme={null}
# .env
MONEI_CLIENT_ID=mc_a3f9b2c1d4e5...
MONEI_CLIENT_SECRET=mcs_8d4e7f2a9b...
MONEI_REDIRECT_URI=http://localhost:3000/monei/callback
SESSION_SECRET=your_session_secret
```

***

## The integration

<CodeGroup>
  ```javascript Node.js theme={null}
  import express from 'express';
  import session from 'express-session';
  import crypto from 'crypto';
  import 'dotenv/config';

  const app = express();
  app.use(express.json());
  app.use(session({
    secret: process.env.SESSION_SECRET,
    resave: false,
    saveUninitialized: false,
  }));

  const MONEI_BASE = 'https://api.monei.cc/api/v1';

  // ─── Step 1: Redirect user to Monei ──────────────────────────────────────────

  app.get('/connect-monei', (req, res) => {
    // Generate a random state value to prevent CSRF attacks
    const state = crypto.randomBytes(16).toString('hex');
    req.session.oauthState = state;

    const params = new URLSearchParams({
      client_id:    process.env.MONEI_CLIENT_ID,
      redirect_uri: process.env.MONEI_REDIRECT_URI,
      scope:        'wallet:read profile:read',
      state,
    });

    res.redirect(`https://monei.cc/connect/authorize?${params}`);
  });

  // ─── Step 2: Handle the callback ─────────────────────────────────────────────

  app.get('/monei/callback', async (req, res) => {
    const { code, state, error } = req.query;

    // User denied access
    if (error) {
      return res.redirect('/dashboard?error=access_denied');
    }

    // Always validate state to prevent CSRF
    if (state !== req.session.oauthState) {
      return res.status(400).send('Invalid state parameter');
    }

    // ─── Step 3: Exchange code for tokens (server-side only) ─────────────────

    const tokenRes = await fetch(`${MONEI_BASE}/connect/token`, {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({
        grant_type:    'authorization_code',
        code,
        client_id:     process.env.MONEI_CLIENT_ID,
        client_secret: process.env.MONEI_CLIENT_SECRET,
        redirect_uri:  process.env.MONEI_REDIRECT_URI,
      }),
    });

    const tokens = await tokenRes.json();

    if (!tokenRes.ok) {
      console.error('Token exchange failed:', tokens);
      return res.redirect('/dashboard?error=token_exchange_failed');
    }

    // Store exactly what was granted — user may have approved fewer scopes
    req.session.moneiAccessToken  = tokens.access_token;
    req.session.moneiRefreshToken = tokens.refresh_token;
    req.session.moneiTokenExpiry  = Date.now() + (tokens.expires_in * 1000);
    req.session.moneiScopes       = tokens.scopes; // what was actually granted

    res.redirect('/dashboard?connected=true');
  });

  // ─── Step 4: Call the Monei API on behalf of the user ────────────────────────

  app.get('/dashboard', async (req, res) => {
    const token = req.session.moneiAccessToken;

    if (!token) {
      return res.json({ connected: false });
    }

    const walletRes = await fetch(`${MONEI_BASE}/wallet/me`, {
      headers: { Authorization: `Bearer ${token}` },
    });

    const wallet = await walletRes.json();

    res.json({
      connected: true,
      scopes: req.session.moneiScopes,
      wallet: wallet.data,
    });
  });

  app.listen(3000, () => console.log('Running on http://localhost:3000'));
  ```

  ```python Python theme={null}
  from flask import Flask, redirect, request, session, jsonify
  import os
  import secrets
  import requests
  from dotenv import load_dotenv

  load_dotenv()

  app = Flask(__name__)
  app.secret_key = os.getenv("SESSION_SECRET")

  MONEI_BASE      = "https://api.monei.cc/api/v1"
  CLIENT_ID       = os.getenv("MONEI_CLIENT_ID")
  CLIENT_SECRET   = os.getenv("MONEI_CLIENT_SECRET")
  REDIRECT_URI    = os.getenv("MONEI_REDIRECT_URI")

  # ─── Step 1: Redirect user to Monei ──────────────────────────────────────────

  @app.get("/connect-monei")
  def connect_monei():
      # Generate a random state value to prevent CSRF attacks
      state = secrets.token_hex(16)
      session["oauth_state"] = state

      params = {
          "client_id":    CLIENT_ID,
          "redirect_uri": REDIRECT_URI,
          "scope":        "wallet:read profile:read",
          "state":        state,
      }

      from urllib.parse import urlencode
      url = f"https://monei.cc/connect/authorize?{urlencode(params)}"
      return redirect(url)

  # ─── Step 2: Handle the callback ─────────────────────────────────────────────

  @app.get("/monei/callback")
  def monei_callback():
      code  = request.args.get("code")
      state = request.args.get("state")
      error = request.args.get("error")

      # User denied access
      if error:
          return redirect("/dashboard?error=access_denied")

      # Always validate state to prevent CSRF
      if state != session.get("oauth_state"):
          return "Invalid state parameter", 400

      # ─── Step 3: Exchange code for tokens (server-side only) ─────────────────

      token_res = requests.post(
          f"{MONEI_BASE}/connect/token",
          json={
              "grant_type":    "authorization_code",
              "code":          code,
              "client_id":     CLIENT_ID,
              "client_secret": CLIENT_SECRET,
              "redirect_uri":  REDIRECT_URI,
          },
      )

      tokens = token_res.json()

      if not token_res.ok:
          print("Token exchange failed:", tokens)
          return redirect("/dashboard?error=token_exchange_failed")

      # Store exactly what was granted — user may have approved fewer scopes
      session["monei_access_token"]  = tokens["access_token"]
      session["monei_refresh_token"] = tokens["refresh_token"]
      session["monei_token_expiry"]  = tokens["expires_in"]
      session["monei_scopes"]        = tokens["scopes"]  # what was actually granted

      return redirect("/dashboard?connected=true")

  # ─── Step 4: Call the Monei API on behalf of the user ────────────────────────

  @app.get("/dashboard")
  def dashboard():
      token = session.get("monei_access_token")

      if not token:
          return jsonify({"connected": False})

      wallet_res = requests.get(
          f"{MONEI_BASE}/wallet/me",
          headers={"Authorization": f"Bearer {token}"},
      )

      wallet = wallet_res.json()

      return jsonify({
          "connected": True,
          "scopes":    session.get("monei_scopes"),
          "wallet":    wallet.get("data"),
      })

  if __name__ == "__main__":
      app.run(port=3000, debug=True)
  ```
</CodeGroup>

***

## Test it locally

```bash theme={null}
# Visit this in your browser
http://localhost:3000/connect-monei
```

You'll be redirected to Monei's consent screen. After approving, you land back at `/dashboard` with live wallet data.

***

## What to do next

<CardGroup cols={2}>
  <Card title="OAuth Flow Deep Dive" icon="arrows-rotate" href="/connect/oauth-flow">
    Understand every step in detail in state validation, error handling, edge cases
  </Card>

  <Card title="Handling Partial Grants" icon="shield-halved" href="/connect/partial-grants">
    Users may approve fewer scopes than you requested, here's how to handle that
  </Card>

  <Card title="Token Management" icon="key" href="/connect/tokens">
    Refresh tokens before expiry, revoke on disconnect
  </Card>

  <Card title="All Scopes" icon="list" href="/connect/scopes">
    Full reference of every scope and which endpoints it unlocks
  </Card>
</CardGroup>
