When a function has auth_level: "user" or an LLM service uses user_pays billing, the app authenticates Gipity users via session cookies.

How It Works

Users logged into Gipity already have a session cookie on .gipity.ai. Your app needs two things:

  1. An app token (identifies your app to the API)
  2. credentials: 'include' on all fetch calls (sends the user's session cookie cross-origin)

Step 1: Get an App Token

POST https://a.gipity.ai/api/token with your project GUID.

const res = await fetch('https://a.gipity.ai/api/token', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({ app: '<PROJECT_GUID>' })
});
const json = await res.json();
const appToken = json.data.token;  // IMPORTANT: .data.token, NOT .token

Response shape:

{ "data": { "token": "eyJ...", "expiresIn": 3600 } }

The token is short-lived (1 hour). Cache it for the session and refresh when it expires.

Step 2: Check Auth State (Recommended)

GET /api/<PROJECT_GUID>/auth/status - returns auth state + URLs in one call.

Add X-App-Token header and credentials: 'include'.

Response shapes:

// Not logged in
{ "authenticated": false, "consented": false, "user": null, "loginUrl": "..." }

// Logged in, no consent
{ "authenticated": true, "consented": false, "user": null, "consentUrl": "..." }

// Fully authed
{ "authenticated": true, "consented": true, "user": { "guid", "displayName", "avatarUrl", "accountSlug" } }

Optional query param: ?permissions=N (default 1 = IDENTITY). Use 5 for IDENTITY + AI.

Lightweight Check

GET /api/auth/me (with credentials: 'include') returns { user: { guid, displayName, avatarUrl, accountSlug } } or { user: null }. No app context - cannot return login/consent URLs.

Error Codes (Function Calls)

The function API also returns auth-related error codes with redirect URLs:

Append &return=<app_url> to redirect URLs so users return to the app after auth. The return URL must be on a Gipity-hosted domain (app.gipity.ai, dev.gipity.ai, gipity.ai).

Client Code - Popup Flow (Recommended)

Opens login/consent in a popup. The app stays visible behind it.

async function checkAuth(appGuid, appToken) {
  const res = await fetch(\`https://a.gipity.ai/api/\${appGuid}/auth/status\`, {
    credentials: 'include',
    headers: { 'X-App-Token': appToken }
  });
  const data = await res.json();

  if (data.authenticated && data.consented) {
    return data.user; // Already authed
  }

  // Open login or consent popup
  const authUrl = (data.loginUrl || data.consentUrl) + '&mode=popup';
  const popup = window.open(authUrl, 'gipity_auth', 'width=450,height=600');
  return new Promise(resolve => {
    window.addEventListener('message', function handler(e) {
      if (e.data?.type === 'gipity_auth') {
        window.removeEventListener('message', handler);
        if (e.data.status === 'success') {
          // Re-check to get user profile
          checkAuth(appGuid, appToken).then(resolve);
        } else {
          resolve(null); // User denied
        }
      }
    });
  });
}

Client Code - Redirect Flow

Navigates away from the app. Use &return=<app_url> so users come back after auth.

const res = await fetch(\`https://a.gipity.ai/api/\${appGuid}/auth/status\`, {
  credentials: 'include',
  headers: { 'X-App-Token': token }
});
const data = await res.json();
if (!data.authenticated) {
  location.href = data.loginUrl + '&return=' + encodeURIComponent(location.href);
} else if (!data.consented) {
  location.href = data.consentUrl + '&return=' + encodeURIComponent(location.href);
}

Complete Example (Copy-Paste Ready)

Full flow from token fetch to authenticated API call:

const API = 'https://a.gipity.ai';
const APP_GUID = '<PROJECT_GUID>';

// Step 1: Get app token
const tokenRes = await fetch(\`\${API}/api/token\`, {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({ app: APP_GUID })
});
const tokenData = await tokenRes.json();
const appToken = tokenData.data.token;  // NOTE: .data.token, not .token

// Step 2: Check auth status
const authRes = await fetch(\`\${API}/api/\${APP_GUID}/auth/status\`, {
  credentials: 'include',
  headers: { 'X-App-Token': appToken }
});
const auth = await authRes.json();

if (!auth.authenticated) {
  // Open login popup
  const popup = window.open(auth.loginUrl + '&mode=popup', 'gipity_auth', 'width=450,height=600');
} else if (!auth.consented) {
  // Open consent popup
  const popup = window.open(auth.consentUrl + '&mode=popup', 'gipity_auth', 'width=450,height=600');
} else {
  // Step 3: Call an authenticated function
  const res = await fetch(\`\${API}/api/\${APP_GUID}/fn/my_function\`, {
    method: 'POST',
    credentials: 'include',
    headers: { 'Content-Type': 'application/json', 'X-App-Token': appToken },
    body: JSON.stringify({ param1: 'value' })
  });
  const result = await res.json();
}

Common Mistakes