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

# Quickstart

> Integrate Emerge lightning-fast

This guide walks you through collecting your first user consent and querying their data.

## Connect your AI tools

Get AI-powered help while integrating by connecting these docs to your AI tools.

<Tabs>
  <Tab title="Cursor">
    Add to `.cursor/mcp.json`:

    ```json theme={null}
    {
      "mcpServers": {
        "Emerge": { "type": "http", "url": "https://docs.emergedata.ai/mcp" }
      }
    }
    ```
  </Tab>

  <Tab title="Claude Code">
    ```bash theme={null}
    claude mcp add --transport http emerge https://docs.emergedata.ai/mcp
    ```
  </Tab>

  <Tab title="VS Code">
    Add to `.vscode/mcp.json`:

    ```json theme={null}
    {
      "mcpServers": {
        "Emerge": { "type": "http", "url": "https://docs.emergedata.ai/mcp" }
      }
    }
    ```
  </Tab>
</Tabs>

## Prerequisites

<Check>The Control Room account with API credentials</Check>

Don't have an account? [Sign up here](https://dashboard.emergedata.ai).

## Step 1: Configure your flow

Set up your company branding for the consent flow.

<CodeGroup>
  ```typescript TypeScript theme={null}
  const response = await fetch('https://link.emergedata.ai/configs', {
    method: 'POST',
    headers: {
      'Authorization': `Bearer ${API_TOKEN}`,
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({
      config_name: 'default',
      company_name: 'Your Company',
      logo_url: 'https://yourcompany.com/logo.png',
      privacy_policy_url: 'https://yourcompany.com/privacy',
      is_default: true
    })
  });
  ```

  ```python Python theme={null}
  import requests

  response = requests.post(
      'https://link.emergedata.ai/configs',
      headers={
          'Authorization': f'Bearer {API_TOKEN}',
          'Content-Type': 'application/json'
      },
      json={
          'config_name': 'default',
          'company_name': 'Your Company',
          'logo_url': 'https://yourcompany.com/logo.png',
          'privacy_policy_url': 'https://yourcompany.com/privacy',
          'is_default': True
      }
  )
  ```
</CodeGroup>

<Note>
  `POST /configs` is an upsert. Reusing the same `config_name` updates the existing config instead of returning a conflict, which makes setup scripts idempotent. Use `GET /configs` to list your configs and preview URLs.
</Note>

## Step 2: Generate a signed link

Create an HMAC-signed URL that users click to start the consent flow.

<Warning>
  Do not ask end-users for `uid` or collect it in the frontend. Use your internal user id server-side or omit `uid` and store the generated callback `uid`. This keeps user scoping private and avoids wrong-user data access.
</Warning>

<CodeGroup>
  ```typescript TypeScript theme={null}
  import crypto from 'crypto';

  function createLinkUrl(params: {
    clientId: string;
    signingSecret: string;
    redirectUri: string;
    userId: string;
  }) {
    const timestamp = new Date().toISOString();
    const state = crypto.randomBytes(16).toString('hex');

    // Build parameters object
    const urlParams: Record<string, string> = {
      client_id: params.clientId,
      redirect_uri: params.redirectUri,
      state: state,
      timestamp: timestamp,
      uid: params.userId
    };

    // Sort parameters alphabetically for signature
    const sortedKeys = Object.keys(urlParams).sort();
    const signatureBase = sortedKeys
      .map(key => `${key}=${urlParams[key]}`)
      .join('&');

    const signature = crypto
      .createHmac('sha256', params.signingSecret)
      .update(signatureBase)
      .digest('hex');

    // Build final URL with URL-encoded parameters
    const finalParams = new URLSearchParams(urlParams);
    finalParams.append('signature', signature);

    return `https://link.emergedata.ai/link/start?${finalParams.toString()}`;
  }

  // Usage
  const linkUrl = createLinkUrl({
    clientId: 'your_client_id',
    signingSecret: 'your_signing_secret',
    redirectUri: 'https://yourcompany.com/callback',
    userId: 'psub_d4e5f6789012345678901234abcdef01'
  });
  ```

  ```python Python theme={null}
  import hmac
  import hashlib
  from datetime import datetime, timezone
  import secrets
  from urllib.parse import urlencode

  def create_link_url(
      client_id: str,
      signing_secret: str,
      redirect_uri: str,
      user_id: str
  ) -> str:
      timestamp = datetime.now(timezone.utc).isoformat().replace('+00:00', 'Z')
      state = secrets.token_hex(16)

      params = {
          'client_id': client_id,
          'redirect_uri': redirect_uri,
          'state': state,
          'timestamp': timestamp,
          'uid': user_id
      }

      # Sort parameters alphabetically for signature
      sorted_params = sorted(params.items())
      signature_base = '&'.join(f'{k}={v}' for k, v in sorted_params)

      signature = hmac.new(
          signing_secret.encode(),
          signature_base.encode(),
          hashlib.sha256
      ).hexdigest()

      params['signature'] = signature

      return f"https://link.emergedata.ai/link/start?{urlencode(params)}"

  # Usage
  link_url = create_link_url(
      client_id='your_client_id',
      signing_secret='your_signing_secret',
      redirect_uri='https://yourcompany.com/callback',
      user_id='psub_d4e5f6789012345678901234abcdef01'
  )
  ```
</CodeGroup>

## Step 3: Handle the callback

After the user completes the consent flow, they're redirected to your `redirect_uri` with these parameters:

| Parameter    | Description                                                            |
| ------------ | ---------------------------------------------------------------------- |
| `status`     | `success`, `reauthorized`, or `failure`                                |
| `state`      | The state value you provided (verify this matches)                     |
| `uid`        | The user identifier returned by Emerge (your value or a generated one) |
| `error_code` | Only present if `status=failure`                                       |

<CodeGroup>
  ```typescript TypeScript theme={null}
  // Express.js example
  app.get('/callback', (req, res) => {
    const { status, state, uid, error_code } = req.query;

    // Verify state matches what you stored
    if (!verifyState(state)) {
      return res.status(400).send('Invalid state');
    }

    if (status === 'success' || status === 'reauthorized') {
      // Consent granted - data export will start automatically
      // Next step: poll /export/status/{uid} and wait for provider-level readiness
      console.log(`User ${uid} granted consent`);
      res.redirect('/success');
    } else {
      console.log(`Consent failed: ${error_code}`);
      res.redirect('/error');
    }
  });
  ```

  ```python Python theme={null}
  # FastAPI example
  from fastapi import FastAPI, Request
  from fastapi.responses import RedirectResponse

  app = FastAPI()

  @app.get('/callback')
  async def callback(
      status: str,
      state: str,
      uid: str,
      error_code: str = None
  ):
      # Verify state matches what you stored
      if not verify_state(state):
          return RedirectResponse('/error')

      if status in ('success', 'reauthorized'):
          # Consent granted - data export will start automatically
          print(f'User {uid} granted consent')
          return RedirectResponse('/success')
      else:
          print(f'Consent failed: {error_code}')
          return RedirectResponse('/error')
  ```
</CodeGroup>

## Step 4: Check export status

Before querying, poll `GET /export/status/{uid}` until the provider you need is ready.

<CodeGroup>
  ```typescript TypeScript theme={null}
  async function waitForProviderExport(uid: string, provider: 'google_data' | 'gmail') {
    for (let attempt = 1; attempt <= 10; attempt += 1) {
      const response = await fetch(
        `https://link.emergedata.ai/export/status/${encodeURIComponent(uid)}`,
        {
          headers: {
            Authorization: `Bearer ${API_TOKEN}`
          }
        }
      );

      if (!response.ok) {
        const body = await response.text();
        throw new Error(`Export status failed (${response.status}): ${body}`);
      }

      const payload = await response.json() as {
        uid: string;
        sources: Array<{ provider: string; data_ready: boolean }>;
      };

      const source = payload.sources.find((item) => item.provider === provider);
      if (source?.data_ready) {
        return;
      }

      await new Promise((resolve) => setTimeout(resolve, 1500 * attempt));
    }

    throw new Error(`Export not ready for provider ${provider}`);
  }
  ```

  ```python Python theme={null}
  import time
  import requests

  def wait_for_provider_export(uid: str, provider: str) -> None:
      for attempt in range(1, 11):
          response = requests.get(
              f'https://link.emergedata.ai/export/status/{uid}',
              headers={'Authorization': f'Bearer {API_TOKEN}'}
          )
          response.raise_for_status()

          payload = response.json()
          source = next(
              (item for item in payload.get('sources', []) if item.get('provider') == provider),
              None
          )

          if source and source.get('data_ready') is True:
              return

          time.sleep(1.5 * attempt)

      raise RuntimeError(f'Export not ready for provider {provider}')
  ```
</CodeGroup>

### Response example

```json theme={null}
{
  "uid": "psub_d4e5f6789012345678901234abcdef01",
  "sources": [
    {
      "provider": "google_data",
      "data_ready": true,
      "data_landed_at": "2026-02-12T09:10:11Z",
      "export_status": "completed",
      "export_completed_at": "2026-02-12T09:10:11Z"
    },
    {
      "provider": "gmail",
      "data_ready": false,
      "data_landed_at": null,
      "export_status": "running",
      "export_completed_at": null
    }
  ]
}
```

<Note>
  You can also subscribe to `data.ready` and `data.failed` webhooks for near-real-time export updates. Keep polling `GET /export/status/{uid}` as a fallback for missed webhook deliveries.
</Note>

## Step 5: Query user data

Once consent is granted and data is exported, query it using the sync endpoints.

<CodeGroup>
  ```typescript TypeScript theme={null}
  async function getUserSearchHistory(uid: string) {
    const response = await fetch(
      `https://query.emergedata.ai/v1/sync/get_search?uid=${uid}`,
      {
        headers: {
          'Authorization': `Bearer ${API_TOKEN}`
        }
      }
    );

    const data = await response.json();
    return data;
  }

  // Usage
  const searchHistory = await getUserSearchHistory('psub_d4e5f6789012345678901234abcdef01');
  console.log(searchHistory.data);
  // [{ query: "best restaurants nearby", timestamp: "2024-01-15T10:30:00Z" }, ...]
  ```

  ```python Python theme={null}
  import requests

  def get_user_search_history(uid: str) -> dict:
      response = requests.get(
          f'https://query.emergedata.ai/v1/sync/get_search',
          params={'uid': uid},
          headers={'Authorization': f'Bearer {API_TOKEN}'}
      )
      return response.json()

  # Usage
  search_history = get_user_search_history('psub_d4e5f6789012345678901234abcdef01')
  print(search_history['data'])
  # [{'query': 'best restaurants nearby', 'timestamp': '2024-01-15T10:30:00Z'}, ...]
  ```
</CodeGroup>

## Common mistakes to avoid

* Using `https://link.emergedata.ai/link` instead of `https://link.emergedata.ai/link/start`
* Signing URL parameters in the wrong order or signing URL-encoded values
* Treating `state` as a user identifier or skipping state verification
* Asking users to input `uid` or failing to store the callback `uid`
* Querying before `GET /export/status/{uid}` shows `data_ready: true` for your provider
* Exposing `signing_secret` or API tokens in client-side code

## Next steps

<CardGroup cols={2}>
  <Card title="Link Guide" icon="link" href="/link/overview">
    Deep dive into consent flow options
  </Card>

  <Card title="Query Guide" icon="database" href="/query/overview">
    Learn about sync vs async queries
  </Card>

  <Card title="Webhooks" icon="webhook" href="/link/webhooks">
    Track consent and data export lifecycle changes
  </Card>

  <Card title="SDK Reference" icon="code" href="/sdks/typescript">
    Full SDK implementation examples
  </Card>
</CardGroup>
