Issue short-lived CDP credentials for the same ready session. No request body is required. Set NSTDATA_API_KEY and NSTDATA_SESSION_ID privately; NSTDATA_API_ORIGIN and NSTDATA_TEAM_ID are optional. Credential output is sensitive.
curl --silent --show-error --fail-with-body \
--request POST "${NSTDATA_API_ORIGIN:-https://api.nstdata.io}/api/v1/cloud-browser/sessions/$NSTDATA_SESSION_ID/automation-credentials" \
--header "x-api-key: $NSTDATA_API_KEY"
For team context, send x-team-id with a nonempty, trimmed value. Do not follow redirects. Use the returned cdpUrl and headerName. Authorization requires Bearer ; other headers use the raw token. Never put tokens in query parameters.
Use Node.js 22 or later. This connects to an existing session without launching a local browser.
npm install playwright-core
Save as connect-cloud-browser.mjs and run with node.
import { chromium } from 'playwright-core';
const env = process.env;
for (const name of ['NSTDATA_API_KEY', 'NSTDATA_SESSION_ID']) {
if (!env[name]) throw new Error(`Missing environment variable: ${name}`);
}
if (!/^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(env.NSTDATA_SESSION_ID)) {
throw new Error('NSTDATA_SESSION_ID must be a UUID v4');
}
const origin = new URL(env.NSTDATA_API_ORIGIN || 'https://api.nstdata.io');
if (origin.protocol !== 'https:' || origin.username || origin.password ||
origin.pathname !== '/' || origin.search || origin.hash) {
throw new Error('NSTDATA_API_ORIGIN must be an HTTPS origin without a path');
}
const headers = { 'x-api-key': env.NSTDATA_API_KEY };
if (env.NSTDATA_TEAM_ID !== undefined) {
if (!env.NSTDATA_TEAM_ID || env.NSTDATA_TEAM_ID.trim() !== env.NSTDATA_TEAM_ID) {
throw new Error('Set a valid NSTDATA_TEAM_ID or omit it');
}
headers['x-team-id'] = env.NSTDATA_TEAM_ID;
}
const response = await fetch(new URL(
`/api/v1/cloud-browser/sessions/${env.NSTDATA_SESSION_ID}/automation-credentials`, origin,
), { method: 'POST', headers, redirect: 'error', signal: AbortSignal.timeout(20_000) });
const result = await response.json();
if (!response.ok || result?.err === true || result?.success === false) {
// Do not log the complete response, API key, or short-lived token.
const messageCode = typeof result?.msg === 'string' && /^CLOUD_BROWSER_[A-Z0-9_]+$/.test(result.msg)
? result.msg : 'See the documented HTTP status handling';
throw new Error(`Credential request failed: HTTP ${response.status}; ${messageCode}`);
}
const connection = result?.data;
if (!connection || connection.sessionId !== env.NSTDATA_SESSION_ID ||
typeof connection.cdpUrl !== 'string' ||
typeof connection.headerName !== 'string' || !connection.headerName ||
typeof connection.accessToken !== 'string' || !connection.accessToken ||
!Number.isFinite(Date.parse(connection.expiresAt)) || Date.parse(connection.expiresAt) <= Date.now()) {
throw new Error('Missing, invalid, or expired connection credentials');
}
const connectionHeaders = {
[connection.headerName]: connection.headerName.toLowerCase() === 'authorization'
? `Bearer ${connection.accessToken}` : connection.accessToken,
};
let browser;
try {
browser = await chromium.connectOverCDP(connection.cdpUrl, {
headers: connectionHeaders, timeout: 30_000,
});
const context = browser.contexts()[0];
if (!context) throw new Error('No browser context available');
const page = await context.newPage();
await page.goto('https://example.com/', { waitUntil: 'domcontentloaded', timeout: 30_000 });
if (await page.title() !== 'Example Domain') throw new Error('Unexpected page result');
console.log('Automation example completed');
} finally {
// For a CDP-connected Playwright browser, close() disconnects this client.
// Stop the remote session separately in the dashboard.
if (browser) await browser.close();
}
Select existing pages by URL or business conditions. Verify website results independently; connection success does not prove CAPTCHA acceptance. Stop the original session separately after use.
400: correct request/authentication. 401/403/470: check key, account and team permissions. 404: check ID and scope. 409: inspect readiness/stopping. 410: expired; credentials cannot renew it. 429/503: bounded backoff on the same active session. 500/network/parse failures: inspect service and original session; do not create replacements. Error identifiers may be in code or msg. Application errors use err:true; storage errors use success:false. Share diagnostic requestId with support, never full credentials.
curl --location --request POST '/api/v1/cloud-browser/sessions//automation-credentials' \
--header 'x-team-id;' \
--header 'x-api-key: <api-key>'{
"data": {
"sessionId": "11111111-1111-4111-8111-111111111111",
"cdpUrl": "wss://browser.example.com/devtools/browser/11111111-1111-4111-8111-111111111111",
"accessToken": "REDACTED_SHORT_LIVED_TOKEN",
"headerName": "Authorization",
"expiresAt": "2030-01-01T00:05:00Z"
},
"err": false,
"code": 200,
"msg": "SUCCESS"
}