Skip to content

SDK Package Selection Guide

UnboundBytes provides two TypeScript/JavaScript SDK packages designed for different use cases:

  • @unboundbytes/sdk - High-level SDK with domain-specific API methods (Recommended for most users)
  • @unboundbytes/sdk-client - Low-level HTTP client with token refresh and retry logic

This guide helps you choose the right package for your project and understand when to use each.

Feature@unboundbytes/sdk@unboundbytes/sdk-client
Recommended ForMost applicationsCustom integrations, advanced use cases
API Methods✅ Domain-specific methods (listDevices, createDeployment)❌ Generic HTTP methods (get, post, put)
Authentication✅ Bearer, HMAC, API Key⚠️ Manual (via interceptors)
Token Refresh✅ Automatic (via underlying client)✅ Automatic with concurrent request handling
Retry Logic✅ Exponential backoff✅ Exponential backoff with jitter
WebSocket Support✅ Built-in real-time commands❌ Not included
Type Safety✅ Full TypeScript types from OpenAPI✅ Generic type support
Request Interceptors❌ Not exposed✅ Request/Response interceptors
Bundle SizeLarger (includes all API methods)Smaller (HTTP client only)
Learning CurveLower (intuitive API methods)Higher (manual request construction)
Use CasePortal, Agent, CLI toolsCustom API clients, middleware

Choose @unboundbytes/sdk if you:

✅ Are building a Portal application or Agent ✅ Want ready-to-use API methods without constructing requests manually ✅ Need multiple authentication types (Bearer, HMAC, API Key) ✅ Want WebSocket support for real-time command delivery ✅ Prefer a higher-level abstraction over raw HTTP calls ✅ Are following UnboundBytes best practices for integration

This is the recommended package for 95% of use cases.

  • Portal Web Application: User management, deployment dashboard
  • Agent Daemon: Device heartbeat, command polling, tunnel management
  • CLI Tools: Administrative scripts, deployment automation
  • Backend Services: Webhook handlers, background jobs
import { UnboundBytesClient } from '@unboundbytes/sdk';
// Portal: Bearer token authentication
const client = new UnboundBytesClient({
auth: {
type: 'bearer',
token: 'your-oidc-token'
}
});
// High-level API methods (no manual request construction)
const devices = await client.listDevices('ten_abc123');
const dashboard = await client.getTenantDashboard('ten_abc123');
// Agent: HMAC authentication
const agentClient = new UnboundBytesClient({
auth: {
type: 'hmac',
agentId: 'agt_xyz',
tenantId: 'ten_abc',
sharedSecret: 'your-shared-secret'
}
});
await agentClient.sendHeartbeat('dev_123', {
cpu: 50.0,
memory: 4096,
disk: 10240,
timestamp: new Date().toISOString()
});
// WebSocket support for real-time commands
const ws = agentClient.createWebSocketClient(
agentClient.config.auth,
'dev_123',
{
onCommand: async (command) => {
console.log('Received:', command);
await agentClient.acknowledgeCommand('dev_123', command.commandId, {
status: 'completed'
});
}
}
);
ws.connect();

Choose @unboundbytes/sdk-client if you:

✅ Need fine-grained control over HTTP requests ✅ Are building custom API clients or middleware ✅ Want to minimize bundle size (client only, no API methods) ✅ Need request/response interceptors for custom logic ✅ Are implementing custom authentication flows ✅ Want low-level access to token refresh and retry mechanisms

This is for advanced use cases where the high-level SDK is too opinionated.

  • Custom API Gateway: Middleware with custom auth logic
  • Third-Party Integrations: Wrapping UnboundBytes API in your own SDK
  • Proxy Services: Request forwarding with custom headers
  • Testing Infrastructure: Mock servers, request interception
import { ApiClient } from '@unboundbytes/sdk-client';
// Low-level HTTP client with token refresh
const client = new ApiClient({
baseUrl: 'https://api.unboundbytes.com',
accessToken: 'your-access-token',
refreshToken: 'your-refresh-token',
refreshEndpoint: '/api/auth/refresh',
timeout: 30000,
maxRetries: 3,
});
// Manual request construction (generic HTTP methods)
const response = await client.get<Device[]>('/v1/devices', {
headers: {
'X-Tenant-ID': 'ten_abc123'
}
});
// Custom request interceptor
client.addRequestInterceptor(async (url, init) => {
const headers = new Headers(init.headers);
headers.set('X-Custom-Header', 'my-value');
headers.set('X-Request-ID', crypto.randomUUID());
console.log(`Request: ${init.method} ${url}`);
return { url, init: { ...init, headers } };
});
// Custom response interceptor
client.addResponseInterceptor(async (response) => {
console.log(`Response: ${response.status} from ${response.url}`);
// Custom error handling
if (response.status === 429) {
const retryAfter = response.headers.get('Retry-After');
throw new Error(`Rate limited. Retry after ${retryAfter}s`);
}
return response;
});
// Skip token refresh for specific endpoints
const loginResponse = await client.post('/auth/login', {
email: 'user@example.com',
password: 'password',
}, {
skipTokenRefresh: true,
});
// Skip retry logic for specific requests
const criticalResponse = await client.get('/api/critical-resource', {
skipRetry: true,
timeout: 5000,
});

Internally, @unboundbytes/sdk uses @unboundbytes/sdk-client as its underlying HTTP client.

┌─────────────────────────────────────────┐
│ @unboundbytes/sdk │
│ ┌─────────────────────────────────┐ │
│ │ High-Level API Methods │ │
│ │ - listDevices() │ │
│ │ - createDeployment() │ │
│ │ - sendHeartbeat() │ │
│ │ - WebSocketClient │ │
│ └─────────────┬───────────────────┘ │
│ │ │
│ ↓ │
│ ┌─────────────────────────────────┐ │
│ │ @unboundbytes/sdk-client │ │
│ │ - Token Refresh │ │
│ │ - Retry Logic │ │
│ │ - Request Queue │ │
│ │ - HTTP Client │ │
│ └─────────────────────────────────┘ │
└─────────────────────────────────────────┘

Key Points:

  • If you use @unboundbytes/sdk, you get both the high-level API methods and the underlying token refresh/retry logic from sdk-client.
  • You do not need to install both packages unless you’re building a custom integration.
  • @unboundbytes/sdk exposes sdk-client functionality through its configuration options (timeout, maxRetries, etc.).

From @unboundbytes/sdk-client to @unboundbytes/sdk

Section titled “From @unboundbytes/sdk-client to @unboundbytes/sdk”

If you started with the low-level client and want to migrate to the high-level SDK:

Before (sdk-client):

import { ApiClient } from '@unboundbytes/sdk-client';
const client = new ApiClient({
baseUrl: 'https://api.unboundbytes.com',
accessToken: 'your-token',
refreshEndpoint: '/api/auth/refresh',
});
// Manual request construction
const devices = await client.get<Device[]>('/v1/devices', {
headers: { 'X-Tenant-ID': 'ten_abc123' }
});
const newDeployment = await client.post<Deployment>('/v1/deployments', {
tenantId: 'ten_abc123',
name: 'My Deployment',
region: 'us-east-1',
});

After (sdk):

import { UnboundBytesClient } from '@unboundbytes/sdk';
const client = new UnboundBytesClient({
auth: {
type: 'bearer',
token: 'your-token'
}
});
// High-level methods (no manual headers or paths)
const devices = await client.listDevices('ten_abc123');
const newDeployment = await client.createDeployment('ten_abc123', {
name: 'My Deployment',
region: 'us-east-1',
});

Migration Steps:

  1. Replace package: npm install @unboundbytes/sdk (you can remove @unboundbytes/sdk-client)
  2. Update imports: Change ApiClient to UnboundBytesClient
  3. Update auth config: Use the auth object with type field
  4. Replace HTTP calls: Use domain-specific methods instead of get/post
  5. Remove manual headers: Tenant ID and auth headers are handled automatically
  6. Update error handling: Use SDK-specific error types (APIError, NetworkError, RateLimitError)

From @unboundbytes/sdk to @unboundbytes/sdk-client

Section titled “From @unboundbytes/sdk to @unboundbytes/sdk-client”

If you need to drop down to the low-level client for custom use cases:

Before (sdk):

import { UnboundBytesClient } from '@unboundbytes/sdk';
const client = new UnboundBytesClient({
auth: { type: 'bearer', token: 'your-token' }
});
const devices = await client.listDevices('ten_abc123');

After (sdk-client):

import { ApiClient } from '@unboundbytes/sdk-client';
const client = new ApiClient({
baseUrl: 'https://api.unboundbytes.com',
accessToken: 'your-token',
refreshEndpoint: '/api/auth/refresh',
});
// Add auth header manually
client.addRequestInterceptor(async (url, init) => {
const headers = new Headers(init.headers);
headers.set('Authorization', `Bearer ${client.getAccessToken()}`);
return { url, init: { ...init, headers } };
});
const devices = await client.get<Device[]>('/v1/devices', {
headers: { 'X-Tenant-ID': 'ten_abc123' }
});

Migration Steps:

  1. Install package: npm install @unboundbytes/sdk-client
  2. Update imports: Change UnboundBytesClient to ApiClient
  3. Update config: Use baseUrl, accessToken, refreshEndpoint instead of auth
  4. Add auth interceptor: Manually add Authorization header via interceptor
  5. Replace method calls: Use get/post/put/delete with manual paths
  6. Add manual headers: Include X-Tenant-ID and other headers as needed
Terminal window
npm install @unboundbytes/sdk

Q: Which package should I use for my Portal application?

Section titled “Q: Which package should I use for my Portal application?”

A: Use @unboundbytes/sdk. It provides high-level methods like listDevices() and createDeployment() that are perfect for Portal UI development.

Q: Which package should I use for my Agent?

Section titled “Q: Which package should I use for my Agent?”

A: Use @unboundbytes/sdk. It supports HMAC authentication and provides methods like sendHeartbeat() and pollCommands() specifically designed for Agent use cases.

Q: When would I ever use @unboundbytes/sdk-client?

Section titled “Q: When would I ever use @unboundbytes/sdk-client?”

A: Only when you need:

  • Custom request/response interceptors
  • Fine-grained control over HTTP requests
  • Minimal bundle size (client-only, no API methods)
  • Custom authentication flows not supported by the high-level SDK

Q: Can I use both packages in the same project?

Section titled “Q: Can I use both packages in the same project?”

A: Yes, but it’s rarely necessary. If you need most of the high-level SDK methods but also need some custom HTTP logic, you can use both. However, this adds bundle size and complexity.

Q: Does @unboundbytes/sdk support token refresh?

Section titled “Q: Does @unboundbytes/sdk support token refresh?”

A: Yes! The high-level SDK uses @unboundbytes/sdk-client internally, so you automatically get token refresh, retry logic, and concurrent request handling.

Q: How do I add custom headers with @unboundbytes/sdk?

Section titled “Q: How do I add custom headers with @unboundbytes/sdk?”

A: The high-level SDK doesn’t expose interceptors. If you need custom headers, you have two options:

  1. Use @unboundbytes/sdk-client directly for fine-grained control
  2. Submit a feature request to add interceptor support to the high-level SDK

A:

  • @unboundbytes/sdk-client: ~15 KB gzipped (HTTP client only)
  • @unboundbytes/sdk: ~45 KB gzipped (includes all API methods + client)

For most applications, the convenience of the high-level SDK outweighs the bundle size difference.

Q: Can I mix and match? Use the high-level SDK for some calls and low-level for others?

Section titled “Q: Can I mix and match? Use the high-level SDK for some calls and low-level for others?”

A: Yes! You can instantiate both clients:

import { UnboundBytesClient } from '@unboundbytes/sdk';
import { ApiClient } from '@unboundbytes/sdk-client';
// High-level for most operations
const sdk = new UnboundBytesClient({
auth: { type: 'bearer', token: 'your-token' }
});
// Low-level for custom operations
const client = new ApiClient({
baseUrl: 'https://api.unboundbytes.com',
accessToken: 'your-token',
});
// Use high-level for standard operations
const devices = await sdk.listDevices('ten_abc123');
// Use low-level for custom operations
const customResponse = await client.get('/v1/custom-endpoint', {
headers: { 'X-Custom-Header': 'value' }
});
ScenarioRecommended Package
Portal web application@unboundbytes/sdk
Agent daemon@unboundbytes/sdk
CLI tools@unboundbytes/sdk
Backend services@unboundbytes/sdk
Custom API gateway@unboundbytes/sdk-client
Third-party SDK wrapper@unboundbytes/sdk-client
Proxy middleware@unboundbytes/sdk-client
Request interception@unboundbytes/sdk-client

Default choice: Use @unboundbytes/sdk unless you have a specific need for low-level HTTP control.