@uluops/ops-sdk
v3.0.5
Published
SDK for the UluOps platform API — execution tracking, analytics, and auth
Downloads
2,965
Maintainers
Readme
UluOps · Operating Intelligence as Infrastructure
@uluops/ops-sdk
Official TypeScript SDK with Zod runtime validation for the UluOps platform API. Track execution runs, manage issues, analyze trends, and integrate agent pipelines into your workflow.
Current version: 3.0.5 | Changelog
Quick Start
Programmatic Usage
import { OpsClient } from '@uluops/ops-sdk';
// Auto-loads credentials from ULUOPS_API_KEY env var, .env file, or ~/.uluops/credentials.json
const client = new OpsClient();
// Or pass an API key explicitly
// const client = new OpsClient({ apiKey: 'ulr_your-api-key-here' });
// Save an execution run
const result = await client.runs.save({
project: 'my-project',
workflowType: 'post-implementation',
agents: [
{ name: 'code-validator', score: 85, decision: 'PASS' },
{ name: 'test-architect', score: 72, decision: 'APPROVED' },
],
recommendations: [
{
agent: 'code-validator',
title: 'Missing error handling',
priority: 'suggested',
filePath: 'src/api/client.ts',
lineNumber: 42,
},
],
});
console.log(`Run #${result.run.runNumber} saved: ${result.correlation.newIssues} new issues`);Search Issues
const issues = await client.issues.search({
query: 'authentication',
status: 'open',
priority: 'critical',
});
for (const issue of issues) {
console.log(`[${issue.severity}] ${issue.title} — ${issue.filePath}:${issue.lineNumber}`);
}Project Analytics
const burndown = await client.analytics.getBurndown({
project: 'my-project',
days: 30,
});
for (const [domain, trend] of Object.entries(burndown.trends)) {
console.log(`${domain}: ${trend.trend} (avg daily change: ${trend.avgDailyChange})`);
}Table of Contents
- Overview
- Features
- Installation
- Authentication
- TypeScript Support
- API Reference
- Environment Variables
- Error Handling
- Advanced Usage
- Troubleshooting
- License
Overview
The UluOps SDK provides programmatic access to the UluOps platform API, enabling you to:
- Track Execution Runs: Save agent, workflow, and pipeline results with scores, recommendations, and analysis
- Manage Issues: Create, search, update, and track issues across projects
- Analyze Trends: Get burndown charts, velocity metrics, and taxonomy distribution analytics
- Automate Workflows: Integrate execution tracking into CI/CD and agent pipelines
The SDK covers 75 methods across 7 operation domains with full TypeScript support.
Features
- Full API Coverage: 75 methods across auth, projects, runs, issues, analytics, and taxonomy domains
- Type-Safe: Complete TypeScript definitions with Zod runtime validation
- Dual Authentication: API key (preferred) and JWT session support
- Automatic Retries: Exponential backoff for transient errors (502, 503, 504, 429, network failures)
- Error Hierarchy: Typed errors for precise error handling
- Subpath Exports: Import only what you need (
@uluops/ops-sdk/types,@uluops/ops-sdk/errors)
Installation
# npm
npm install @uluops/ops-sdk
# yarn
yarn add @uluops/ops-sdk
# pnpm
pnpm add @uluops/ops-sdk
# bun
bun add @uluops/ops-sdkRequirements:
- Node.js 18.0.0 or higher
- TypeScript 5.0+ (for TypeScript users)
Dependencies:
@uluops/sdk-core— Shared HTTP client, auth strategies, and utilities (installed automatically)zod— Runtime schema validation (installed automatically)
Authentication
The SDK supports two authentication methods. To get an API key, visit the UluOps Dashboard or create one programmatically via client.auth.createApiKey().
API Key Authentication (Recommended)
API keys provide persistent authentication without session management. Keys must start with the ulr_ prefix.
import { OpsClient } from '@uluops/ops-sdk';
const client = new OpsClient({
apiKey: 'ulr_your-api-key-here',
});
// Check authentication status
console.log(client.isAuthenticated()); // true
console.log(client.getAuthType()); // 'api_key'Session-Based Authentication
For interactive applications, use client.login() which automatically configures token auto-refresh:
import { OpsClient } from '@uluops/ops-sdk';
const client = new OpsClient();
// Login — installs session auth with automatic token refresh
const { sessionToken, user } = await client.login(
'[email protected]',
'your-password',
);
// Client is now authenticated — subsequent requests use the session token
const projects = await client.projects.list();
// Logout when done
await client.logout();Note: Prefer
client.login()overclient.auth.login(). The latter only returns the token without installing it, requiring manual client construction.
Credential Priority Chain
The SDK loads credentials in the following order:
- Constructor arguments:
apiKey,sessionToken,email/password - Environment variables:
ULUOPS_API_KEY,ULUOPS_EMAIL,ULUOPS_PASSWORD - Local
.envfile: In the current working directory - Global credentials:
~/.uluops/credentials.json
TypeScript Support
The SDK is written in TypeScript with full type definitions. Import types directly:
// Main client
import { OpsClient, type OpsClientConfig } from '@uluops/ops-sdk';
// Types only
import type {
Project,
Issue,
Run,
AgentPerformance,
Priority,
Status,
Severity,
} from '@uluops/ops-sdk/types';
// Errors only
import {
OpsApiError,
ValidationError,
NotFoundError,
RateLimitError,
} from '@uluops/ops-sdk/errors';
// Config utilities
import { loadCredentials, DEFAULT_BASE_URL } from '@uluops/ops-sdk/config';Package Exports
| Export Path | Contents |
|------------|----------|
| @uluops/ops-sdk | Main OpsClient, OpsHttpClient, auth strategies, config helpers, all types |
| @uluops/ops-sdk/types | All TypeScript types and Zod input schemas |
| @uluops/ops-sdk/types/projects | Project types only |
| @uluops/ops-sdk/types/issues | Issue types only |
| @uluops/ops-sdk/types/runs | Run types only |
| @uluops/ops-sdk/types/analytics | Analytics types only |
| @uluops/ops-sdk/types/enums | Priority, Status, Severity enums |
| @uluops/ops-sdk/types/responses | API response types |
| @uluops/ops-sdk/types/schemas | Zod input validation schemas |
| @uluops/ops-sdk/types/auth | Auth/credential types |
| @uluops/ops-sdk/errors | Error classes and utilities |
| @uluops/ops-sdk/config | Configuration loaders, constants, and input validators |
Granular Type Imports
For minimal bundle size, import only the type modules you need:
import type { Project } from '@uluops/ops-sdk/types/projects';
import type { Issue } from '@uluops/ops-sdk/types/issues';
import type { Run, SaveRunInput } from '@uluops/ops-sdk/types/runs';
import type { BurndownResult } from '@uluops/ops-sdk/types/analytics';
import type { Priority, Status, Severity } from '@uluops/ops-sdk/types/enums';
import type { ApiResponse } from '@uluops/ops-sdk/types/responses';
import type { Credentials } from '@uluops/ops-sdk/config';API Reference
Client Configuration
const client = new OpsClient({
// Authentication (choose one)
apiKey: 'ulr_...', // API key (preferred)
sessionToken: 'jwt-token', // Existing session token
email: '[email protected]', // Email for login
password: 'password', // Password for login
// Connection settings (baseUrl defaults to https://api.uluops.ai/api/v1)
timeout: 30000, // Request timeout in ms (default: 30000)
retries: 3, // Retry count for transient errors (default: 3)
debug: false, // Enable debug logging
// Multi-tenancy
orgSlug: 'my-org', // Org slug (sets X-Org-Slug header on all requests)
// Callbacks
onTokenRefresh: (token) => { /* handle token refresh */ },
onRateLimitApproaching: (info) => {
console.warn(`Rate limit: ${info.remaining}/${info.limit} remaining, resets ${info.reset}`);
},
onRetry: ({ attempt, maxAttempts, error, delayMs }) => {
console.warn(`Retry ${attempt}/${maxAttempts} after ${delayMs}ms: ${error.message}`);
},
});Auth Operations
Manage user authentication, API keys, and sessions.
client.auth.register(input)
Register a new user account.
| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| email | string | Yes | User email address |
| password | string | Yes | User password |
const { user, token } = await client.auth.register({
email: '[email protected]',
password: 'securePassword123',
});client.auth.login(input)
Login with email and password.
| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| email | string | Yes | User email address |
| password | string | Yes | User password |
const { user, token } = await client.auth.login({
email: '[email protected]',
password: 'password123',
});client.auth.logoutAll()
Revoke all active sessions for the current user.
const { sessionsRevoked } = await client.auth.logoutAll();
console.log(`Revoked ${sessionsRevoked} sessions`);client.auth.getMe()
Get the current authenticated user.
const user = await client.auth.getMe();
console.log(user.email, user.role);client.auth.getProfile()
Get detailed user profile.
const { user } = await client.auth.getProfile();client.auth.updateProfile(input)
Update user profile information.
| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| username | string | No | Username (lowercase, 3-30 chars) |
| name | string | No | Display name |
| bio | string | No | User bio |
| avatar | string | No | Avatar image (base64 encoded) |
| avatarMimeType | string | No | Avatar MIME type (e.g., image/png) |
Note: At least one field must be provided.
const { user } = await client.auth.updateProfile({
name: 'John Doe',
bio: 'Software Engineer',
});client.auth.changePassword(input)
Change the current user's password.
| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| currentPassword | string | Yes | Current password |
| newPassword | string | Yes | New password |
await client.auth.changePassword({
currentPassword: 'oldPassword',
newPassword: 'newSecurePassword',
});client.auth.setPassword(password)
Set password for accounts created without one (e.g., OAuth or admin-created).
| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| password | string | Yes | New password |
await client.auth.setPassword('newSecurePassword');client.auth.forgotPassword(email)
Request a password reset email.
await client.auth.forgotPassword('[email protected]');client.auth.resetPassword(input)
Reset password using a reset token.
| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| token | string | Yes | Reset token from email |
| password | string | Yes | New password |
await client.auth.resetPassword({
token: 'reset-token-from-email',
password: 'newSecurePassword',
});client.auth.getAvatar()
Get the current user's avatar as binary data.
const { data, contentType } = await client.auth.getAvatar();
// data: ArrayBuffer, contentType: e.g. 'image/png'client.auth.deleteAvatar()
Delete the current user's avatar.
await client.auth.deleteAvatar();client.auth.listApiKeys()
List all API keys for the current user.
const keys = await client.auth.listApiKeys();
for (const key of keys) {
console.log(key.name, key.prefix, key.createdAt);
}client.auth.createApiKey(input)
Create a new API key.
| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| name | string | No | Key name/description |
const { id, name, key } = await client.auth.createApiKey({ name: 'CI Pipeline' });
console.log('Save this key:', key); // Only shown once!client.auth.revokeApiKey(keyId)
Revoke an API key.
await client.auth.revokeApiKey('key-id-123');client.auth.listSessions()
List all active sessions.
const sessions = await client.auth.listSessions();
for (const session of sessions) {
console.log(session.userAgent, session.createdAt);
}client.auth.revokeSession(sessionId)
Revoke a specific session.
await client.auth.revokeSession('session-id-123');Project Operations
Manage projects.
client.projects.list()
List all projects.
const projects = await client.projects.list();
for (const project of projects) {
console.log(project.id, project.name, project.createdAt);
}client.projects.get(idOrName)
Get a project by ID or name.
const project = await client.projects.get('my-project');
console.log(project.name, project.runCount, project.issueCount);client.projects.create(input)
Create a new project.
| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| name | string | Yes | Project name (unique) |
const project = await client.projects.create({ name: 'new-project' });client.projects.update(idOrName, input)
Update a project.
| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| name | string | No | New project name |
const project = await client.projects.update('my-project', {
name: 'renamed-project',
});client.projects.rename(input)
Rename a project.
| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| oldName | string | Yes | Current project name |
| newName | string | Yes | New project name |
const project = await client.projects.rename({
oldName: 'old-name',
newName: 'new-name',
});client.projects.delete(idOrName, input)
Permanently delete a project.
| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| confirm | boolean | Yes | Must be true |
| confirmationPhrase | string | Yes | Must match project name |
await client.projects.delete('my-project', {
confirm: true,
confirmationPhrase: 'my-project',
});client.projects.softDelete(idOrName, input)
Soft delete a project (can be restored).
await client.projects.softDelete('my-project', {
confirm: true,
confirmationPhrase: 'my-project',
});client.projects.restore(idOrName)
Restore a soft-deleted project.
const project = await client.projects.restore('my-project');client.projects.getSummary(idOrName)
Get project summary with statistics.
const summary = await client.projects.getSummary('my-project');
console.log(`Total runs: ${summary.totalRuns}`);
console.log(`Total issues: ${summary.totalIssues}`);
console.log(`Open issues: ${summary.openIssues}`);client.projects.getTrends(idOrName, query)
Get issue trend data over time.
| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| days | number | No | Number of days (default: 30) |
const trends = await client.projects.getTrends('my-project', { days: 30 });
for (const point of trends) {
console.log(point.date, point.openIssues, point.closedIssues);
}client.projects.listIssues(idOrName, query)
List issues for a project with filters.
| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| status | Status | No | Filter by status |
| priority | Priority | No | Filter by priority |
| severity | Severity | No | Filter by severity |
| agent | string | No | Filter by agent |
| limit | number | No | Max results (default: 50) |
| offset | number | No | Pagination offset |
Filter convention: Passing
'all'for any filter (e.g.,status: 'all') is equivalent to omitting the parameter — the SDK strips'all'values before sending the request. This applies to all query methods across the SDK.
const issues = await client.projects.listIssues('my-project', {
status: 'open',
priority: 'critical',
limit: 10,
});client.projects.listIssuesWithCount(idOrName, query)
List issues with total count for pagination. Same filters as listIssues.
const { issues, count } = await client.projects.listIssuesWithCount('my-project', {
status: 'open',
limit: 10,
});
console.log(`Showing ${issues.length} of ${count} total issues`);Note: Returns both the issues array and the total count for building paginated UIs.
client.projects.bulkUpdateIssueStatus(idOrName, updates)
Bulk update issue statuses.
const results = await client.projects.bulkUpdateIssueStatus('my-project', [
{ issueId: 'issue-1', status: 'completed', reason: 'Fixed' },
{ issueId: 'issue-2', status: 'deferred', reason: 'Not a priority' },
]);client.projects.mergeIssues(idOrName, input)
Merge duplicate issues.
| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| targetIssueId | string | Yes | Issue to merge into |
| sourceIssueIds | string[] | Yes | Issues to merge from |
| strategy | string | No | 'keep_target' or 'keep_highest_priority' |
const result = await client.projects.mergeIssues('my-project', {
targetIssueId: 'issue-1',
sourceIssueIds: ['issue-2', 'issue-3'],
strategy: 'keep_target',
});Run Operations
Save and manage execution runs.
client.runs.save(input, options?)
Save a new execution run. Pass { _skipClientValidation: true } as the second argument to bypass client-side Zod validation (useful when input is already validated by an upstream layer like MCP).
| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| project | string | Yes | Project name or ID |
| workflowType | string | Yes | Workflow type (e.g., 'post-implementation') |
| agents | AgentInput[] | Yes | Array of agent results |
| recommendations | Recommendation[] | Yes | Array of issues/recommendations (use [] for empty) |
| summary | object | No | Summary statistics |
| rawMarkdown | string | No | Raw markdown report |
| idempotencyKey | string | No | Key for duplicate prevention |
| definitionType | string | No | Definition type ('agent', 'command', 'workflow', 'pipeline') |
| definitionName | string | No | Definition name (e.g., 'code-validator') |
| definitionVersion | string | No | Definition version (e.g., '1.2.0') |
| definitionHash | string | No | SHA-256 content hash of the definition |
| definitionId | string | No | Registry definition UUID for direct identity linkage |
| timestamp | string | No | ISO 8601 timestamp override (defaults to server time) |
| definitionMinSubscription | SubscriptionTier | No | Minimum subscription tier required for this definition |
| analysisRecords | AnalysisRecordInput[] | No | Structured analysis records (v1.4.0) |
| analysisSummary | AnalysisSummaryInput \| AnalysisSummaryInput[] | No | Single or per-agent array of analysis summaries (v1.8.1) |
| analysisSummary.explorationMaps | ExplorationMap[] | No | Structural maps from explorer agents (v1.8.0) |
const result = await client.runs.save({
project: 'my-project',
workflowType: 'post-implementation',
agents: [
{
name: 'code-validator',
score: 85,
decision: 'PASS',
summary: 'Code quality is strong — minor naming inconsistencies in utils/',
model: 'sonnet',
tokens: { inputTokens: 1000, outputTokens: 500 },
},
{
name: 'test-architect',
score: 72,
decision: 'APPROVED',
summary: 'Good coverage overall but edge cases missing in auth module',
},
],
recommendations: [
{
agent: 'code-validator',
title: 'Missing error handling in API client',
priority: 'suggested',
severity: 'medium',
filePath: 'src/api/client.ts',
lineNumber: 42,
description: 'Add try-catch for network errors',
failureCode: 'SEM-VAL/M',
},
],
summary: {
averageScore: 78.5,
allGatesPassed: true,
},
});
console.log(`Run #${result.run.runNumber} saved`);
console.log(`Issues: ${result.correlation.newIssues} new, ${result.correlation.recurringIssues} recurring`);client.runs.validate(input, options?)
Preview what a save would do without persisting. Accepts same { _skipClientValidation: true } option as save().
const preview = await client.runs.validate({
project: 'my-project',
workflowType: 'post-implementation',
agents: [{ name: 'code-validator', score: 90, decision: 'PASS' }],
recommendations: [{ agent: 'code-validator', title: 'Unused import', priority: 'backlog', failureCode: 'STR-OMI/L' }],
});
console.log('Would create:', preview.wouldCreate);
console.log('Would update:', preview.wouldUpdate);
console.log('Would regress:', preview.wouldRegress);
console.log('Would observe:', preview.wouldObserve); // recommendations matching observation-status issuesclient.runs.listByProject(projectId, query)
List runs for a project.
| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| workflowType | string | No | Filter by workflow type |
| limit | number | No | Max results |
| offset | number | No | Pagination offset |
const runs = await client.runs.listByProject('my-project', {
workflowType: 'ship',
limit: 10,
});client.runs.getLatest(projectId, workflowType)
Get the latest run for a project.
const latestRun = await client.runs.getLatest('my-project', 'post-implementation');client.runs.get(runId)
Get a run by ID.
const run = await client.runs.get('run-uuid-here');client.runs.getDetails(projectId, runNumber)
Get detailed run information with recommendations.
const details = await client.runs.getDetails('my-project', 5);
console.log(details.agents);
console.log(details.recommendations);client.runs.diff(query)
Compare two runs to see fixed/new issues.
| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| project | string | Yes | Project name or ID |
| baseRun | number | Yes | Base run number |
| compareRun | number | Yes | Compare run number |
| workflowType | string | No | Filter by workflow type |
const diff = await client.runs.diff({
project: 'my-project',
baseRun: 1,
compareRun: 5,
});
console.log('Fixed issues:', diff.fixed.length);
console.log('New issues:', diff.new.length);
console.log('Unchanged:', diff.unchanged.length);client.runs.archive(input)
Archive old runs.
| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| project | string | Yes | Project name or ID |
| beforeRunNumber | number | No | Archive runs before this number |
| beforeDate | string | No | Archive runs before this date |
| keepLast | number | No | Keep last N runs |
| reason | string | No | Archive reason |
const result = await client.runs.archive({
project: 'my-project',
keepLast: 10,
reason: 'Quarterly cleanup',
});
console.log(`Archived ${result.archived} runs`);client.runs.update(input, options?)
Update run metadata (tokens, scores). Accepts { _skipClientValidation: true } option.
const run = await client.runs.update({
project: 'my-project',
runNumber: 5,
agents: [
{ name: 'code-validator', score: 90, tokens: { inputTokens: 1500 } },
],
});client.runs.updateById(runId, input, options?)
Update run metadata by run UUID (alternative to update which uses project+runNumber). Supports post-hoc enrichment with structured analysis data (v1.7.0). Accepts { _skipClientValidation: true } option.
// Basic metadata update
const run = await client.runs.updateById('run-uuid-here', {
agents: [{ name: 'code-validator', score: 92 }],
});
// Enrich with per-agent analysis summaries (v1.7.1)
const run = await client.runs.updateById('run-uuid-here', {
analysisSummary: [
{ agentName: 'epictetus-analyst', decision: 'FACTUAL', score: 82,
categoryScores: [{ name: 'Fact/Judgment Separation', weight: 30, score: 25 }] },
{ agentName: 'epictetus-validator', decision: 'ALIGNED', score: 82 },
],
analysisRecords: [
{ agentName: 'epictetus-analyst', recordType: 'evidence_claim', recordId: 'EC-1',
title: 'Registry overclaim', data: { claim: '...' } },
{ agentName: 'epictetus-forecaster', recordType: 'decay_vector', recordId: 'DV-1',
title: 'Fail-open compounding', data: { timeline: '12-24 months' } },
],
});
// Enrich with explorer structural maps (v1.8.0)
const run = await client.runs.updateById('run-uuid-here', {
analysisSummary: {
agentName: 'bateson-explorer', decision: 'EXPLORED', score: 0,
explorationMaps: [{
metadata: { explorerName: 'bateson-explorer', framework: 'bateson' },
sections: [
{ type: 'topology', label: 'Logical Level Map', entities: [...], relationships: [...] },
{ type: 'agenda', label: 'Inquiry Agenda', questions: [...] },
],
}],
},
});client.runs.delete(runId)
Delete a run.
await client.runs.delete('run-uuid-here');client.runs.getAnalysis(runId)
Get structured analysis records and summaries for a specific run (v0.3.0).
const analysis = await client.runs.getAnalysis('run-uuid-here');
console.log(analysis.records); // Convention inventories, tension maps, decay vectors, etc.
console.log(analysis.summaries); // Per-agent system metrics, epistemic assessmentsclient.runs.getProjectAnalysis(projectId, query)
Get analysis summaries for a project over time (v0.3.0).
| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| agentName | string | No | Filter by agent (e.g., 'nietzsche-analyst') |
| agentType | string | No | Filter by type ('analyst', 'validator', etc.) |
| decision | string | No | Filter by decision ('VITAL', 'FLOWING', etc.) |
| limit | number | No | Max results |
| offset | number | No | Pagination offset |
const { data, total } = await client.runs.getProjectAnalysis('my-project', {
agentName: 'nietzsche-analyst',
limit: 10,
});
// data[0]: { decision, score, categoryScores, systemMetrics, runNumber, runTimestamp, workflowType }
data.forEach(s => console.log(s.decision, s.systemMetrics));client.runs.queryAnalysisRecords(query)
Cross-project query for analysis records with filters (v0.3.0).
| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| recordType | string | No | Filter by type ('convention', 'tension', 'decay_vector') |
| classification | string | No | Filter by classification ('CALCIFIED', 'IMMINENT') |
| agentName | string | No | Filter by agent name |
| agentType | string | No | Filter by agent type |
| severity | string | No | Filter by severity |
// Find all calcified conventions across all projects
const { data, total } = await client.runs.queryAnalysisRecords({
recordType: 'convention',
classification: 'CALCIFIED',
});
// data[0]: { recordType, recordId, title, classification, severity, data: { ... } }
console.log(`Found ${total} records`);client.runs.getAgentRunsAnalysis(agentName, query)
Get analysis summaries with run context for a specific agent. Returns analysis decision, score, category scores, system metrics alongside run metadata (number, timestamp, workflow type).
| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| agentName | string | Yes | Agent name |
| query.project | string | Yes | Project name or ID |
| query.decision | string | No | Filter by decision |
| query.limit | number | No | Max results (1-100, default 20) |
| query.offset | number | No | Pagination offset |
const { items, total } = await client.runs.getAgentRunsAnalysis('epictetus-validator', {
project: 'my-project',
limit: 10,
});
// items[0]: { decision, score, categoryScores, runNumber, runTimestamp, workflowType, snapshotScore, ... }Issue Operations
Track and manage validation issues.
client.issues.create(input)
Create a user-submitted issue.
| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| project | string | Yes | Project name or ID |
| title | string | Yes | Issue title |
| priority | Priority | Yes | 'critical', 'high', 'suggested', 'backlog' |
| severity | Severity | No | 'critical', 'high', 'medium', 'low', 'info' |
| type | IssueType | No | 'bug', 'feature', 'refactor', etc. |
| filePath | string | No | File path where issue exists |
| lineNumber | number | No | Line number |
| description | string | No | Detailed description |
| failureCode | string | No | Taxonomy code (e.g., 'SEM-VAL/H') |
| agent | string | No | Agent name (defaults to 'user-submitted') |
const issue = await client.issues.create({
project: 'my-project',
title: 'Security vulnerability in auth module',
priority: 'critical',
severity: 'critical',
type: 'security',
filePath: 'src/auth/login.ts',
lineNumber: 45,
description: 'SQL injection vulnerability in login query',
failureCode: 'PRA-SEC/C',
});client.issues.search(query)
Search issues across projects.
| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| query | string | Yes | Search query |
| projects | string[] | No | Filter by projects |
| agents | string[] | No | Filter by agents |
| status | Status | No | Filter by status |
| priority | Priority | No | Filter by priority |
| severities | Severity[] | No | Filter by severities |
| failureDomains | string[] | No | Filter by domains ('STR', 'SEM', 'PRA', 'EPI') |
| limit | number | No | Max results |
const issues = await client.issues.search({
query: 'authentication',
status: 'open',
priority: 'critical',
});client.issues.get(issueId)
Get an issue by ID.
const issue = await client.issues.get('issue-uuid-here');client.issues.getDetails(issueId)
Get detailed issue information with occurrences and notes.
const details = await client.issues.getDetails('issue-uuid-here');
console.log('Occurrences:', details.occurrences);
console.log('Notes:', details.notes);
console.log('History:', details.history);client.issues.getByFingerprint(fingerprint, project)
Get an issue by its SHA-256 fingerprint.
const issue = await client.issues.getByFingerprint('abc123...', 'my-project');client.issues.updateStatusByFingerprint(fingerprint, project, input)
Update an issue's status using its fingerprint hash.
| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| fingerprint | string | Yes | SHA-256 fingerprint |
| project | string | Yes | Project name or ID |
| status | Status | Yes | New status |
| reason | string | No | Reason for change |
const result = await client.issues.updateStatusByFingerprint(
'abc123...', 'my-project', { status: 'completed', reason: 'Fixed' }
);client.issues.getHistory(issueId)
Get status change history for an issue.
const history = await client.issues.getHistory('issue-uuid');
for (const entry of history) {
console.log(`${entry.oldStatus} -> ${entry.newStatus} at ${entry.changedAt}`);
}client.issues.updateStatus(issueId, input)
Update an issue's status.
| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| status | Status | Yes | New status |
| reason | string | No | Reason for change |
const issue = await client.issues.updateStatus('issue-uuid', {
status: 'completed',
reason: 'Fixed in PR #123',
});client.issues.update(issueId, input)
Update issue metadata.
| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| title | string | No | New title |
| filePath | string | No | New file path |
| lineNumber | number | No | New line number |
| severity | Severity | No | New severity |
| category | string | No | New category |
| failureCode | string | No | New failure code |
const issue = await client.issues.update('issue-uuid', {
title: 'Updated title',
severity: 'high',
});client.issues.addNote(issueId, input)
Add a note to an issue.
| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| content | string | Yes | Note content |
| noteType | NoteType | No | 'context', 'resolution', 'blocker' |
const note = await client.issues.addNote('issue-uuid', {
content: 'Root cause identified: race condition in async handler',
noteType: 'context',
});client.issues.restore(issueId)
Restore a soft-deleted issue.
const issue = await client.issues.restore('issue-uuid');client.issues.undoLastChange(issueId)
Undo the last status change on an issue.
const issue = await client.issues.undoLastChange('issue-uuid');client.issues.bulkUpdateStatus(updates)
Bulk update issue statuses.
const results = await client.issues.bulkUpdateStatus([
{ issueId: 'issue-1', status: 'completed' },
{ issueId: 'issue-2', status: 'deferred', reason: 'Not a priority' },
]);
// Each result includes per-item success/failure — some items may fail
// while others succeed (e.g., issue not found, invalid status transition)
for (const result of results) {
if (!result.success) {
console.warn(`Failed to update ${result.issueId}: ${result.error}`);
}
}Maximum 100 items per bulk request. The SDK validates this limit client-side via Zod before sending.
client.issues.listByProject(projectId, query)
List issues for a specific project.
const issues = await client.issues.listByProject('my-project', {
status: 'open',
limit: 20,
});Analytics Operations
Get insights and metrics about validation trends.
client.analytics.getAgentPerformance(query)
Get performance metrics by agent.
| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| project | string | No | Filter by project |
| days | number | No | Time window (default: 30) |
const performance = await client.analytics.getAgentPerformance({ days: 30 });
for (const agent of performance) {
console.log(`${agent.name}: avg=${agent.averageScore}, pass=${agent.passRate}`);
}client.analytics.getAgentLifecycle(agentName, query)
Get version trajectory for a specific agent across time.
| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| agentName | string | Yes | Agent name |
| query.project | string | No | Filter by project |
| query.days | number | No | Time window (default: 30) |
const lifecycle = await client.analytics.getAgentLifecycle('code-validator', { days: 90 });
for (const entry of lifecycle) {
console.log(`v${entry.definitionVersion}: avg=${entry.avgScore}, runs=${entry.runs}, pass=${entry.passRate}`);
}client.analytics.getAgentReliability(query)
Get agent reliability statistics (false positive rates, resolution rates).
| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| agent | string | No | Filter by agent |
| days | number | No | Time window (default: 90) |
const { agents } = await client.analytics.getAgentReliability({ days: 90 });
for (const a of agents) {
console.log(`${a.agent}: reliability=${a.reliabilityScore}`);
}client.analytics.getResolutionRates(query)
Get issue resolution rates by project.
const rates = await client.analytics.getResolutionRates();
for (const rate of rates) {
console.log(`${rate.project}: ${rate.resolutionRate * 100}% resolved`);
}client.analytics.getFileHotspots(query)
Get files with the most issues.
| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| project | string | No | Filter by project |
| days | number | No | Time window |
| limit | number | No | Max results |
const hotspots = await client.analytics.getFileHotspots({ limit: 10 });
for (const hotspot of hotspots) {
console.log(`${hotspot.filePath}: ${hotspot.totalIssues} issues`);
}client.analytics.getTaxonomyDistribution(query)
Get issue distribution by failure domain.
const distribution = await client.analytics.getTaxonomyDistribution();
for (const d of distribution) {
console.log(`${d.domain}: ${d.count} issues`);
}
// Output: STR: 50, SEM: 80, PRA: 30, EPI: 20client.analytics.getFullTaxonomy(query)
Get comprehensive taxonomy analytics.
const taxonomy = await client.analytics.getFullTaxonomy();
console.log('By domain:', taxonomy.byDomain);
console.log('By mode:', taxonomy.byMode);
console.log('By severity:', taxonomy.bySeverity);client.analytics.getBurndown(query)
Get burndown time series by failure domain.
| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| project | string | No | Filter by project |
| days | number | No | Time window (default: 30) |
| granularity | string | No | 'daily' or 'weekly' |
const burndown = await client.analytics.getBurndown({ days: 30 });
console.log('Time series:', burndown.timeSeries);
console.log('Trends:', burndown.trends);
// { STR: { trend: 'declining', avgDailyChange: -0.05, netChange: -12, confidence: 'high' }, ... }client.analytics.getVelocity(query)
Get velocity metrics per failure mode.
| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| project | string | No | Filter by project |
| days | number | No | Time window |
| alertThreshold | number | No | Velocity threshold for alerts |
const velocity = await client.analytics.getVelocity({ alertThreshold: 10 });
for (const item of velocity.items) {
console.log(`${item.failureCode}: velocity=${item.velocityPercent}%, alert=${item.alert}`);
}
console.log('Summary:', velocity.summary);client.analytics.getDiscovery(query)
Get discovery timeline (new vs recurring issues).
| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| project | string | No | Filter by project |
| days | number | No | Time window |
| groupBy | string | No | 'day', 'week', 'month' |
const discovery = await client.analytics.getDiscovery({ groupBy: 'week' });
console.log('Timeline:', discovery.timeline);
console.log('Summary:', discovery.summary);
// { totalNew: 8, totalRecurring: 22, newRate: 0.27 }client.analytics.getAgentMatrix(query)
Get agent-taxonomy coverage matrix.
| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| project | string | No | Filter by project |
| days | number | No | Time window (default: 90) |
| minIssues | number | No | Min issues for inclusion |
const matrix = await client.analytics.getAgentMatrix();
console.log('Coverage matrix:', matrix.matrix);
console.log('Blind spots:', matrix.analysis.blindSpots); // Domains not detected
console.log('Single points:', matrix.analysis.singlePoints); // Only one agent detects
console.log('High overlap:', matrix.analysis.highOverlap); // 3+ agents detectclient.analytics.getTrendSummary(query)
Get general trend summary.
const trends = await client.analytics.getTrendSummary();
for (const trend of trends) {
console.log(`${trend.period}: ${trend.newIssues} new, ${trend.resolvedIssues} resolved`);
}client.analytics.getByMetric(metric, query)
Get analytics by specific metric name.
Available metrics: agent_performance, resolution_rates, cross_project_patterns, file_hotspots, regression_analysis, trend_summary, cost_analysis, taxonomy_distribution.
import { isValidMetric, ANALYTICS_METRICS } from '@uluops/ops-sdk';
import type { AnalyticsMetric } from '@uluops/ops-sdk';
// Guard user input before calling
if (isValidMetric(userInput)) {
const data = await client.analytics.getByMetric(userInput, { days: 30 });
}
// Enumerate valid metrics
console.log(ANALYTICS_METRICS); // ['agent_performance', 'resolution_rates', ...]
// Or use typed literals directly — IDE autocomplete for valid metrics
const metric: AnalyticsMetric = 'cost_analysis';
const costData = await client.analytics.getByMetric(metric, { days: 30 });client.analytics.listAgents(query)
List agents with summary info (derived from performance data).
const agents = await client.analytics.listAgents();
for (const v of agents) {
console.log(`${v.name}: avg=${v.averageScore}, runs=${v.totalRuns}, pass=${v.passRate}`);
}Taxonomy Operations
Get the failure taxonomy schema.
client.taxonomy.get()
Get the full taxonomy schema with domains, modes, and severities.
const taxonomy = await client.taxonomy.get();
console.log('Domains:', taxonomy.domains);
// [
// { code: 'STR', name: 'Structural', description: '...', modes: [{ code: 'OMI', name: 'Omission', description: '...' }, ...] },
// { code: 'SEM', name: 'Semantic', description: '...', modes: [...] },
// { code: 'PRA', name: 'Pragmatic', description: '...', modes: [...] },
// { code: 'EPI', name: 'Epistemic', description: '...', modes: [...] },
// ]
console.log('Severities:', taxonomy.severities);
// [{ code: 'C', name: 'critical', weight: 10 }, { code: 'H', name: 'high', weight: 5 }, ...]
console.log('Priorities:', taxonomy.priorities);
// ['critical', 'high', 'suggested', 'backlog']Health Check
Check API server status. This endpoint does not require authentication.
import { OpsHttpClient } from '@uluops/ops-sdk';
import type { HealthResponse } from '@uluops/ops-sdk/types';
const http = new OpsHttpClient({ apiKey: 'ulr_...' });
const health = await http.get<HealthResponse>('/health');
console.log(health.status); // 'ok' | 'degraded' | 'unhealthy'
console.log(health.version); // API version
console.log(health.database); // { connected: boolean, latencyMs: number }CLI
For command-line usage, see the dedicated CLI package: @uluops/cli.
Environment Variables
| Variable | Description | Default |
|----------|-------------|---------|
| ULUOPS_API_KEY | API key for authentication | - |
| ULUOPS_EMAIL | Email for session auth | - |
| ULUOPS_PASSWORD | Password for session auth | - |
| ULUOPS_SESSION_TOKEN | Session token for auth | - |
| ULUOPS_BASE_URL | API base URL | https://api.uluops.ai/api/v1 (localhost:3100 when NODE_ENV=development) |
| ULUOPS_DEBUG | Enable debug logging | false |
Create a .env file in your project:
ULUOPS_API_KEY=ulr_your-api-key-hereOr configure globally in ~/.uluops/.env.
Error Handling
The SDK provides typed error classes for precise error handling:
import {
OpsApiError,
ValidationError,
UnauthorizedError,
ForbiddenError,
NotFoundError,
ConflictError,
RateLimitError,
ServiceUnavailableError,
NetworkError,
TimeoutError,
isOpsApiError,
} from '@uluops/ops-sdk/errors';
try {
await client.projects.get('non-existent');
} catch (error) {
if (error instanceof NotFoundError) {
console.log(error.message); // "Project 'non-existent' not found"
console.log(error.details); // { id: 'non-existent' }
} else if (error instanceof UnauthorizedError) {
console.log('Please authenticate');
} else if (error instanceof RateLimitError) {
console.log(`Rate limited. Retry after ${error.details?.retryAfter}s`);
} else if (error instanceof ValidationError) {
console.log('Invalid input:', error.details);
} else if (isOpsApiError(error)) {
console.log(`API error: ${error.code} - ${error.message}`);
}
}Error Classes
| Error | Status | Description |
|-------|--------|-------------|
| ValidationError | 400 | Invalid request data |
| UnauthorizedError | 401 | Authentication required |
| ForbiddenError | 403 | Access denied |
| NotFoundError | 404 | Resource not found |
| ConflictError | 409 | Resource conflict |
| RateLimitError | 429 | Rate limit exceeded |
| PayloadTooLargeError | 413 | Request body too large |
| UnprocessableError | 422 | Semantically invalid request |
| ServiceUnavailableError | 503 | Server unavailable |
| NetworkError | - | Connection error (auto-retried) |
| TimeoutError | - | Request timeout |
| InputValidationError | - | Client-side Zod validation failure |
Automatic Retries
The SDK automatically retries on transient errors (502, 503, 504, 429) and network failures (DNS, connection reset, ECONNREFUSED) with exponential backoff:
const client = new OpsClient({
apiKey: 'ulr_...',
retries: 3, // Number of retry attempts (default: 3)
timeout: 30000, // Request timeout in ms (default: 30000)
});Request Size Limits
The API enforces the following payload limits:
- Request body: 1 MB maximum for most endpoints
raw_markdownfield inruns.save(): up to 100,000 characters- Recommendations array: no hard limit, but very large arrays (1000+) may cause timeouts
- Bulk operations: capped at 100 items per request (e.g.,
bulkUpdateStatus)
Requests exceeding these limits will receive a 413 Payload Too Large or 422 Unprocessable Entity response.
Input Validation
The SDK includes Zod-based runtime validators for all mutating operations. Import them from @uluops/ops-sdk/config:
import { InputValidationError } from '@uluops/ops-sdk/errors';
import { validateCreateProjectInput } from '@uluops/ops-sdk/config';
try {
const validated = validateCreateProjectInput({ name: '' }); // throws
} catch (error) {
if (error instanceof InputValidationError) {
console.log('Validation errors:', error.errors);
// => [{ code: 'too_small', minimum: 1, path: ['name'], message: 'Too small: expected string to have >=1 characters' }]
}
}Available validators: validateRegisterInput, validateLoginInput, validateCreateProjectInput, validateSaveRunInput, validateCreateUserIssueInput, validateUpdateIssueStatusInput, validateBulkStatusUpdateInput, and more.
Advanced Usage
Using the Low-Level HTTP Client
Prefer
OpsClientfor all standard operations. UseOpsHttpClientdirectly only when you need custom endpoints or raw response access.
For advanced use cases, you can use OpsHttpClient directly:
import { OpsHttpClient } from '@uluops/ops-sdk';
const http = new OpsHttpClient({
apiKey: 'ulr_...',
});
// Make raw requests
const data = await http.get<MyType>('/custom/endpoint', { param: 'value' });
const result = await http.post<MyType>('/custom/endpoint', { body: 'data' });
const raw = await http.requestRaw('GET', '/endpoint'); // Without data unwrappingCustom Authentication Strategy
import { OpsHttpClient, createAuthStrategy } from '@uluops/ops-sdk';
// Create auth strategy manually
const authStrategy = createAuthStrategy({
apiKey: 'ulr_...',
// or
sessionToken: 'jwt-token',
});
// Wire the strategy into the HTTP client
const http = new OpsHttpClient();
http.setAuthStrategy(authStrategy);Loading Credentials Programmatically
import { loadCredentials, loadConfig } from '@uluops/ops-sdk/config';
// Load from environment and config files
const credentials = loadCredentials();
console.log(credentials.apiKey);
console.log(credentials.email);
// Load full config
const config = loadConfig();
console.log(config.baseUrl);Troubleshooting
Invalid API Key Format
Error: Invalid API key format. Keys must start with 'ulr_'Ensure your API key starts with the ulr_ prefix. Generate a new key if needed:
const { key } = await client.auth.createApiKey({ name: 'My Key' });
console.log(key); // ulr_abc123...Authentication Errors
UnauthorizedError: Authentication requiredCheck that:
- Your API key is valid and not revoked
- Environment variables are set correctly
- The
.envfile is in the correct location
# Verify environment
echo $ULUOPS_API_KEYRate Limiting
RateLimitError: Rate limit exceeded. Retry after 60 secondsThe SDK automatically retries rate-limited requests. For high-volume operations:
- Batch operations where possible
- Increase retry count:
retries: 5 - Add delays between requests
Debug Mode
Enable debug logging to see request/response details:
const client = new OpsClient({
apiKey: 'ulr_...',
debug: true,
});Or via environment:
ULUOPS_DEBUG=true node app.jsLicense
MIT License - see LICENSE for details.
