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:
- An app token (identifies your app to the API)
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:
- LOGIN_REQUIRED (401): User not logged into Gipity. Redirect to
error.loginUrl. - CONSENT_REQUIRED (403): User is logged in but hasn't granted permission. Redirect to
error.consentUrl.
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
- Reading
response.tokeninstead ofresponse.data.token- the token endpoint wraps its response in adataobject - Forgetting
credentials: 'include'- without this, the session cookie won't be sent on cross-origin requests toa.gipity.ai - Using the project GUID as the token - the GUID identifies your app, but you still need to call
POST /api/tokento get an actual bearer token - Using relative URLs - app code runs on
app.gipity.aiordev.gipity.ai, so API calls must use the full URLhttps://a.gipity.ai/...