This guide walks you through collecting your first user consent and querying their data.
Get AI-powered help while integrating by connecting these docs to your AI tools.
Cursor
Claude Code
VS Code
Add to .cursor/mcp.json: {
"mcpServers" : {
"Emerge" : { "type" : "http" , "url" : "https://docs.emergedata.ai/mcp" }
}
}
claude mcp add --transport http emerge https://docs.emergedata.ai/mcp
Add to .vscode/mcp.json: {
"mcpServers" : {
"Emerge" : { "type" : "http" , "url" : "https://docs.emergedata.ai/mcp" }
}
}
Prerequisites
The Control Room account with API credentials
Don’t have an account? Sign up here .
Set up your company branding for the consent flow.
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
})
});
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.
Step 2: Generate a signed link
Create an HMAC-signed URL that users click to start the consent flow.
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.
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'
});
Step 3: Handle the callback
After the user completes the consent flow, they’re redirected to your redirect_uri with these parameters:
Parameter Description statussuccess, reauthorized, or failurestateThe state value you provided (verify this matches) uidThe user identifier returned by Emerge (your value or a generated one) error_codeOnly present if status=failure
// 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' );
}
});
Step 4: Check export status
Before querying, poll GET /export/status/{uid} until the provider you need is ready.
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 } ` );
}
Response example
{
"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
}
]
}
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.
Step 5: Query user data
Once consent is granted and data is exported, query it using the sync endpoints.
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" }, ...]
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
Link Guide Deep dive into consent flow options
Query Guide Learn about sync vs async queries
Webhooks Track consent and data export lifecycle changes
SDK Reference Full SDK implementation examples