Pulling a spreadsheet or API export into a report used to mean pasting secrets into a one-off script, or asking an admin to put keys in an environment nobody else could see. When that key rotated, every copy had to change.
Integrations is an organization credential vault for those secrets. Owners create named entries under Admin → Integrations. Editors bind a credential to a data source when an integration script needs it. Secrets are encrypted at rest and never shown again after save.
#Key Concepts
Integration — A named vault entry (for example “Warehouse Sheets”) that holds one credential. The name is what editors see when binding a data source; the secret itself is write-only after create.
Credential type — The shape of the secret (for example a Cursor API key, or Google Service Account JSON). Some types are shared. Others are created for your organization only, and those appear in your Type list and nowhere else. You only see types Threaded has assigned to you. If you need a type that is not listed, ask Threaded support to add it.
Allowed addresses — Integration scripts can reach only HTTPS sites approved for the credential type and bound provider, plus the Threaded app and configured report-storage sites. Addresses outside that combined allowlist are denied. A credential type with no sites listed may still reach sites allowed by the provider, Threaded app, and report storage. The entry is the site itself, not a path or query on that site. Ask Threaded support to add a site when a script needs one that is not already listed. Putting the address only in the script does not allow the call.
Expiration date — Optional. After that UTC calendar day, scripts that use the credential will not start. Leave it blank when the secret does not expire. The Integrations table shows Expired, Expires soon (within 14 days), the expiration date, or Does not expire.
Provider — A data-source wizard (for example Google Sheets) that binds to one credential type. Staff also manage providers; you supply the secret and connection details.
Bound data source — A data source that points at an Integration. While any data source is still bound, Threaded blocks deleting that Integration (HTTP 409) so scripts do not lose auth mid-flight. Unbind first, then delete.
#Who can use it
- Owners create, rotate (update), and delete Integrations. The Admin → Integrations page is owner-only.
- Editors can list Integrations by name (no secrets) and bind or unbind them on a data source’s detail page.
- The Integrations feature must be enabled for your organization (standalone Integrations access). Contact Threaded if you need it turned on.
#How It Works
#Opening Integrations
Go to Admin → Integrations. You will see each Integration’s name, description, type, expiration, how many data sources are bound, who created it, and when.
#Creating an Integration
- Click Create integration.
- Enter a Name and optional Description.
- Choose the Type from the list available to your organization. If the type you need is not there, ask Threaded support to add a credential type for your organization and to list the HTTPS sites its scripts may call.
- Paste the Credential plaintext. Threaded encrypts it on save and does not display it again.
- Optionally set Expiration date. The helper text reads: after this UTC calendar day, scripts that use this credential will not start. Clear the date if it should not expire.
- Click Create.
#Rotating a credential
Open Edit on the row, paste the new secret, and save. Leave Credential blank to keep the current secret and change only the name, description, or expiration date. The same Integration stays bound to any data sources, so you do not need to rebind them. Existing Integrations can still be renamed or rotated if Threaded staff later remove that credential type from your organization; new Integrations of that type cannot be created, and the type no longer appears in the picker.
An expired credential shows an error on Edit: scripts that use it will not start until you update the expiration date or replace the credential. A data source bound to that credential shows the same block on its integration card, and Run integration will not start.
#Binding to a data source
On Reporting → Data sources, open a non-managed data source. In the Integration credential card at the bottom of the page, choose an Integration from the dropdown (or None to unbind). Editors can do this; the picker only appears when Integrations is enabled for the org.
When an integration script runs, Threaded decrypts the bound credential and passes it to the isolate as the INTEGRATION_CREDENTIAL environment variable. See Manufacturing Report Data Sources for scripts, Run integration, and schedules.
#Fetching a Google Sheet with a service account
Hosted JavaScript can sign a Google service-account JWT with crypto.subtle and call the external API with fetch. Use require('threaded/helpers') for base64url, httpJson, and die — that module works on isolate QuickJS node and Container distro node. xlsx, lodash, and csv-parse/sync remain isolate-only. The calls below stay inside the Google credential type’s allowed sites. This example downloads a Sheet as CSV to /tmp/sheet.csv:
const fs = require('fs')
const { base64url, die, httpJson } = require('threaded/helpers')
const serviceAccount = JSON.parse(process.env.INTEGRATION_CREDENTIAL)
const keyBytes = Buffer.from(serviceAccount.private_key.replace(/-----[^-]+-----/g, '').replace(/\s/g, ''), 'base64')
const issuedAt = Math.floor(Date.now() / 1000)
const unsignedJwt =
base64url({ alg: 'RS256', typ: 'JWT' }) +
'.' +
base64url({
iss: serviceAccount.client_email,
scope: 'https://www.googleapis.com/auth/drive.readonly',
aud: serviceAccount.token_uri,
iat: issuedAt,
exp: issuedAt + 3600
})
crypto.subtle
.importKey('pkcs8', keyBytes, { name: 'RSASSA-PKCS1-v1_5', hash: 'SHA-256' }, false, ['sign'])
.then(key => crypto.subtle.sign('RSASSA-PKCS1-v1_5', key, Buffer.from(unsignedJwt)))
.then(signature => unsignedJwt + '.' + base64url(Buffer.from(signature)))
.then(assertion =>
httpJson(serviceAccount.token_uri, {
method: 'POST',
headers: { 'content-type': 'application/x-www-form-urlencoded' },
body:
'grant_type=' +
encodeURIComponent('urn:ietf:params:oauth:grant-type:jwt-bearer') +
'&assertion=' +
encodeURIComponent(assertion)
})
)
.then(token =>
fetch('https://www.googleapis.com/drive/v3/files/YOUR_SHEET_ID/export?mimeType=text%2Fcsv', {
headers: { authorization: 'Bearer ' + token.access_token }
})
)
.then(response => response.text())
.then(csv => fs.writeFileSync('/tmp/sheet.csv', csv))
.catch(error => die(error.message))
Replace YOUR_SHEET_ID and share the Sheet with the service account’s client_email. For large grids, keep the CSV path shown here instead of building a SheetJS worksheet with aoa_to_sheet. A shell integration can then parse the CSV, load it with hosted sqlite3, and upload the .sqlite result.
#Calling the Cursor Admin API
Create an Integration with type Cursor API key and paste the key from the Cursor dashboard (API Keys). Bind it to the data source. Cursor expects HTTP Basic auth with the key as the username and an empty password. Admin and Analytics both use https://api.cursor.com:
const fs = require('fs')
const { die, httpJson } = require('threaded/helpers')
const apiKey = process.env.INTEGRATION_CREDENTIAL
const authorization = 'Basic ' + Buffer.from(apiKey + ':').toString('base64')
httpJson('https://api.cursor.com/teams/members', {
headers: { authorization }
})
.then(body => fs.writeFileSync('/tmp/cursor-members.json', JSON.stringify(body)))
.catch(error => die(error.message))
#Deleting an Integration
Use Delete on the row and confirm. If any data sources are still bound, Threaded keeps the Integration and shows an error asking you to unbind them first — follow the Data sources link in that message, clear the Integration picker on each bound source, then delete again.
#Why This Matters
Credentials stay in one vault instead of in scripts, tickets, or personal password managers. Binding is explicit per data source, rotation does not break bindings, and delete-while-bound is blocked so a refresh job cannot suddenly lose auth. A script can reach only HTTPS sites approved for its credential type and bound provider, plus the Threaded app and configured report-storage sites; every other address is denied.