SDK Node.js Usage
This guide covers using @portaidentity/sdk in Node.js applications — automation scripts, CI/CD pipelines, microservices, and backend integrations.
Setup
Install
yarn add @portaidentity/sdkCreate Client
import { createPortaClient } from '@portaidentity/sdk';
import { createNodeTransport, createTokenAuth } from '@portaidentity/sdk/node';
const transport = createNodeTransport({
baseUrl: 'https://porta.local:3443/api/admin',
auth: createTokenAuth('your-bearer-token'),
});
const porta = createPortaClient({ transport });Authentication Providers
The Node.js entrypoint provides three authentication strategies.
Bearer Token (Simple)
Use a pre-obtained token. Best for scripts and one-off automation:
import { createTokenAuth } from '@portaidentity/sdk/node';
const auth = createTokenAuth('eyJhbGciOiJFUzI1NiIs...');- ✅ Simplest setup
- ❌ No automatic refresh — when the token expires, requests fail
Client Credentials (Machine-to-Machine)
Use OIDC client credentials grant for server-to-server communication. Best for long-running services:
import { createClientCredentialsAuth } from '@portaidentity/sdk/node';
const auth = createClientCredentialsAuth({
tokenEndpoint: 'https://porta.local:3443/super-admin/token',
clientId: 'my-service-client-id',
clientSecret: 'my-service-client-secret',
});Features:
- ✅ Automatic token fetching and caching
- ✅ Automatic refresh when token expires
- ✅ Concurrent request deduplication (avoids thundering herd)
- ✅ No user interaction required
CLI Auth (Stored Credentials)
Reads credentials from ~/.porta/credentials.json, written by porta login. Best for CLI tools:
import { createCliAuth } from '@portaidentity/sdk/node';
const auth = createCliAuth({
credentialsPath: '~/.porta/credentials.json',
refreshEndpoint: 'https://porta.local:3443/super-admin/token',
clientId: 'porta-admin-cli',
});Features:
- ✅ Reads from
porta logincredential file - ✅ Automatic token refresh via refresh_token grant
- ✅ No manual credential handling
401 Handling & Token Refresh
The NodeTransport automatically handles 401 responses:
- On 401, it calls
auth.refresh()to get a new token - Retries the original request with the new token
- If refresh also fails, throws
PortaAuthenticationError
This happens transparently — you don't need to handle 401s in your code.
Common Patterns
Automation Script
import { createPortaClient } from '@portaidentity/sdk';
import { createNodeTransport, createClientCredentialsAuth } from '@portaidentity/sdk/node';
const porta = createPortaClient({
transport: createNodeTransport({
baseUrl: 'https://porta.example.com/api/admin',
auth: createClientCredentialsAuth({
tokenEndpoint: 'https://porta.example.com/super-admin/token',
clientId: process.env.PORTA_CLIENT_ID!,
clientSecret: process.env.PORTA_CLIENT_SECRET!,
}),
}),
});
// Create an organization with full setup
const org = await porta.organizations.create({
name: 'Acme Corp',
slug: 'acme',
});
// Create a deployment-global application (no organization scope)
const app = await porta.applications.create({
name: 'Main App',
slug: 'main',
});
const client = await porta.clients.create({
organizationId: org.id,
applicationId: app.id,
clientName: 'Web App',
clientType: 'public',
redirectUris: ['https://app.acme.com/callback'],
});
console.log('Setup complete:', { orgId: org.id, appId: app.id, clientId: client.clientId });Bulk Operations
// Deactivate multiple users at once
const result = await porta.bulk.userStatus({
ids: ['user-1', 'user-2', 'user-3'],
action: 'deactivate',
organizationId: 'org-uuid',
});
console.log(`Total: ${result.total}, Succeeded: ${result.succeeded}, Failed: ${result.failed}`);Data Export
// Export all users as CSV
const csvBuffer = await porta.exports.download({
entityType: 'users',
format: 'csv',
});
await fs.promises.writeFile('users-export.csv', csvBuffer);Environment Portability
import { writeFile } from 'node:fs/promises';
const exported = await porta.exports.manifest({
scope: { kind: 'organization', organization_slug: 'acme' },
categories: ['organizations', 'applications_authorization'],
application_selection: { all_applications: true, application_slugs: [] },
});
await writeFile('acme-porta.json', JSON.stringify(exported.manifest, null, 2), 'utf8');
const preview = await porta.imports.preview(exported.manifest);
if (preview.errors.length === 0) {
const result = await porta.imports.apply(exported.manifest, 'keep-existing');
// Store result.credentials securely now; generated secrets are returned only once.
}The server is the strict manifest-validation authority. Applications should preview before apply and must not add automatic mutation retries.
Iterate All Records
// Auto-paginate through all organizations
const allOrgs = await porta.organizations.listAll();
console.log(`Total: ${allOrgs.length} organizations`);
// With filter
const activeOrgs = await porta.organizations.listAll({ status: 'active' });Error Handling
import {
PortaHttpError,
PortaAuthenticationError,
PortaValidationError,
PortaNotFoundError,
PortaRateLimitError,
} from '@portaidentity/sdk';
try {
await porta.organizations.create({ name: '' });
} catch (err) {
if (err instanceof PortaValidationError) {
console.error('Validation failed:', err.details);
process.exit(1);
}
if (err instanceof PortaAuthenticationError) {
console.error('Authentication failed — check credentials');
process.exit(1);
}
if (err instanceof PortaRateLimitError) {
console.error('Rate limited — retry after', err.retryAfter, 'seconds');
}
if (err instanceof PortaHttpError) {
console.error(`API error ${err.status}: ${err.message}`);
}
throw err;
}CI/CD Integration
GitHub Actions Example
env:
PORTA_URL: https://porta.example.com
PORTA_CLIENT_ID: ${{ secrets.PORTA_CLIENT_ID }}
PORTA_CLIENT_SECRET: ${{ secrets.PORTA_CLIENT_SECRET }}
steps:
- name: Configure test environment
run: |
npx tsx scripts/configure-test-env.tsDocker / Container
# Your app Dockerfile
FROM node:22-alpine
WORKDIR /app
COPY package.json yarn.lock ./
RUN yarn install --production
COPY dist/ dist/
CMD ["node", "dist/index.js"]// Your app — uses SDK with env-based configuration
const porta = createPortaClient({
transport: createNodeTransport({
baseUrl: process.env.PORTA_API_URL!,
auth: createClientCredentialsAuth({
tokenEndpoint: process.env.PORTA_TOKEN_ENDPOINT!,
clientId: process.env.PORTA_CLIENT_ID!,
clientSecret: process.env.PORTA_CLIENT_SECRET!,
}),
}),
});See Also
- SDK Overview — Installation, quick start, full API reference
- SDK Browser Usage — Browser/SPA integration
- SDK AI Agent Guide — AI integration
- Environment Portability — Selective JSON manifest export and import