TypeScript SDK
@enprompta/sdk
Full-featured TypeScript SDK with middleware support, automatic retries, and comprehensive type definitions.
Type Safe
Full TypeScript support with comprehensive type definitions.
Auto Retry
Built-in retry strategies with exponential backoff.
Middleware
Extensible middleware system for custom logic.
Free to start
Creating an API key is free, and the SDK records observability traces on any plan (5,000/month on Free). Serving prompts and the rest of the REST API require a Pro or Enterprise plan — editor seats read/write, viewer seats read-only. View pricing →
On this page
Installation
npm install @enprompta/sdkyarn add @enprompta/sdkpnpm add @enprompta/sdkQuick Start
import { Enprompta } from '@enprompta/sdk'
// Initialize with API key
const client = new Enprompta({
apiKey: process.env.ENPROMPTA_API_KEY
})
// Or with OAuth2 credentials
const client = new Enprompta({
clientId: process.env.ENPROMPTA_CLIENT_ID,
clientSecret: process.env.ENPROMPTA_CLIENT_SECRET
})
// List all prompts
const { data: prompts } = await client.prompts.list()
console.log(prompts)
// Create a new prompt
const prompt = await client.prompts.create({
title: 'Email Assistant',
content: 'Write a professional email about {{topic}} to {{recipient}}',
visibility: 'PRIVATE',
variables: [
{ name: 'topic', type: 'text', required: true },
{ name: 'recipient', type: 'text', required: true }
]
})
// Execute the prompt
const result = await client.prompts.execute(prompt.id, {
variables: {
topic: 'project deadline extension',
recipient: 'the project manager'
},
provider: 'openai',
model: 'gpt-4'
})
console.log(result.output)Authentication
The SDK supports both API key and OAuth2 client credentials authentication.
API Key Authentication
const client = new Enprompta({
apiKey: 'ep_your_api_key',
// Optional: custom base URL
baseUrl: 'https://enprompta.com/api/v1'
})OAuth2 Client Credentials
Create a client in your dashboard under API Keys → OAuth Clients to get a client_id and client_secret (the secret is shown once).
const client = new Enprompta({
clientId: 'your_client_id',
clientSecret: 'your_client_secret',
scopes: ['prompts:read', 'prompts:write', 'executions:write']
})
// Tokens are automatically managed and refreshedWorking with Prompts
List Prompts
// List with pagination
const { data, pagination } = await client.prompts.list({
limit: 20,
visibility: 'PRIVATE',
search: 'email'
})
// Iterate through all pages
for await (const prompt of client.prompts.listAll()) {
console.log(prompt.title)
}Create Prompt
const prompt = await client.prompts.create({
title: 'Code Reviewer',
content: `Review the following {{language}} code and provide feedback:
\`\`\`{{language}}
{{code}}
\`\`\`
Focus on: {{focus_areas}}`,
description: 'AI-powered code review assistant',
category: 'development',
tags: ['code', 'review', 'development'],
visibility: 'TEAM',
teamId: 'team_abc123',
variables: [
{ name: 'language', type: 'select', options: ['javascript', 'python', 'go'] },
{ name: 'code', type: 'text', required: true },
{ name: 'focus_areas', type: 'text', defaultValue: 'best practices, performance' }
]
})Update Prompt
const updated = await client.prompts.update('prompt_abc123', {
title: 'Updated Title',
content: 'New prompt content with {{variable}}',
visibility: 'PUBLIC'
})Delete Prompt
await client.prompts.delete('prompt_abc123')Executions
Execute a Prompt
const result = await client.prompts.execute('prompt_abc123', {
variables: {
topic: 'quarterly review',
tone: 'professional'
},
provider: 'openai',
model: 'gpt-4o-mini',
temperature: 0.7,
maxTokens: 1000
})
console.log(result.output) // Generated text
console.log(result.tokensUsed) // Token count
console.log(result.cost) // Estimated cost
console.log(result.latencyMs) // Response timeList Executions
// Get execution history
const { data: executions } = await client.executions.list({
promptId: 'prompt_abc123',
provider: 'openai',
startDate: '2025-01-01',
endDate: '2025-01-31'
})
// Get aggregated statistics
const stats = await client.executions.getStats({
groupBy: 'day'
})Auto-Instrumentation
The fastest way to get traces: init() patches your provider SDKs so every OpenAI, Anthropic, and Gemini call is traced automatically — no call-site changes. Pass the client you imported via modules so it works under ESM and bundlers. It captures provider, model, prompt, response, tokens, latency, cost, errors, and — as of @enprompta/sdk 1.1.1 — tool-call turns, where the model's tool call is recorded as the output instead of a blank (streaming and non-streaming).
import { init } from '@enprompta/sdk'
import OpenAI from 'openai'
import Anthropic from '@anthropic-ai/sdk'
init({
apiKey: process.env.ENPROMPTA_API_KEY,
modules: { openai: OpenAI, anthropic: Anthropic }, // also: { google: GenerativeModel }
})
// Every call on these clients is now traced — nothing else to change.
const openai = new OpenAI()
await openai.chat.completions.create({
model: 'gpt-4o',
messages: [{ role: 'user', content: 'Hello' }],
})Why modules? In ESM apps and bundlers, the SDK can't reliably reach into your provider package on its own — it may patch a different copy of the module than the one you imported, so traces silently never appear. Passing the client you imported removes that ambiguity. If init() instruments nothing, it logs a warning telling you to use this form.
Framework instrumentation (LangChain, agents)
To capture the whole agent/RAG trace — retrievals, tool calls, and sub-agent steps with their nesting — install the Enprompta instrumentor for your stack and call instrumentFrameworks().
npm install @enprompta/instrument-langchain
# or your stack's instrumentor: @enprompta/instrument-openaiimport { instrumentFrameworks } from '@enprompta/sdk'
instrumentFrameworks({
apiKey: process.env.ENPROMPTA_API_KEY,
frameworks: ['langchain'], // omit to auto-detect installed instrumentors
})
// Your LangChain runs now appear as typed, nested spans in Observability.Sending Traces (manual)
Prefer full control, or calling a provider the auto-instrumenter doesn't patch? Send traces yourself. The simplest flow: fetch a prompt, call your LLM provider, then POST the result to Enprompta.
Agent span trees & sessions (recommended for agents)
For agents and tool loops, record a span tree instead of one flat trace per call: a root span per request with typed child spans (LLM, TOOL, RETRIEVAL, …), grouped by a sessionId so a multi-turn conversation reads as one session. Works in any runtime — no framework instrumentor required, and bundler-safe. Requires @enprompta/sdk 1.1.0+.
import { Enprompta } from '@enprompta/sdk'
const enprompta = new Enprompta({ apiKey: process.env.ENPROMPTA_API_KEY })
// One root span per user question, grouped by session.
const trace = enprompta.trace({
name: 'support-agent',
type: 'AGENT',
sessionId: conversationId, // groups a multi-turn chat into one session
input: question,
})
// Each model call → a typed LLM child span.
const llm = trace.span({ type: 'LLM', name: 'plan', model: 'gpt-4o', input: messages })
const completion = await openai.chat.completions.create({ /* … */ })
llm.end({
output: completion.choices[0].message,
inputTokens: completion.usage?.prompt_tokens,
outputTokens: completion.usage?.completion_tokens,
})
// Each tool run → a TOOL child span (now visible in the trace tree).
const tool = trace.span({ type: 'TOOL', name: 'get_weather', input: args })
tool.end({ output: result })
await trace.end({ output: answer }) // flushes the whole treeThe result is a nested, typed tree in Observability — end-to-end latency per request, which tool was slow, how many turns it took — plus a Sessions view that groups the whole conversation. Spans are typed (LLM / TOOL / RETRIEVAL / AGENT / CHAIN / RERANKER / GUARDRAIL / EMBEDDING / EVALUATOR), following the OpenInference convention.
Fetch prompt → call LLM → send trace
import OpenAI from 'openai'
const openai = new OpenAI()
// 1. Fetch the prompt at runtime (no redeploy needed to change it)
const res = await fetch('https://enprompta.com/api/sdk/prompts/my-prompt?label=prod', {
headers: { Authorization: `Bearer ${process.env.ENPROMPTA_API_KEY}` }
})
const { content, _meta } = await res.json()
// _meta.promptId — link the trace to this prompt
// _meta.promptVersionId — track which version ran
// 2. Call your LLM provider directly
const start = Date.now()
const completion = await openai.chat.completions.create({
model: 'gpt-4o',
messages: [{ role: 'user', content }]
})
const latencyMs = Date.now() - start
const output = completion.choices[0].message.content ?? ''
// 3. Send the trace — fire and forget
fetch('https://enprompta.com/api/ingest/traces', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.ENPROMPTA_API_KEY}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
spans: [{
traceId: crypto.randomUUID(),
spanId: crypto.randomUUID(),
name: 'my-prompt',
spanType: 'LLM',
startTime: new Date(Date.now() - latencyMs).toISOString(),
endTime: new Date().toISOString(),
status: 'SUCCESS',
attributes: {
'llm.provider': 'openai',
'llm.model': 'gpt-4o',
'llm.input.messages': content,
'llm.output.messages': output,
'llm.token_count.prompt': completion.usage?.prompt_tokens,
'llm.token_count.completion': completion.usage?.completion_tokens,
'llm.latency_ms': latencyMs,
'enprompta.prompt_id': _meta.promptId,
'enprompta.prompt_version_id': _meta.promptVersionId,
'enprompta.environment': 'production'
}
}]
})
}).catch(() => {}) // never block your critical pathAlternative: single-trace endpoint
For simple single-turn calls, use the flat POST /api/v1/traces endpoint instead of the spans batch format.
fetch('https://enprompta.com/api/v1/traces', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.ENPROMPTA_API_KEY}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
provider: 'openai',
model: 'gpt-4o',
input: content,
output,
inputTokens: completion.usage?.prompt_tokens,
outputTokens: completion.usage?.completion_tokens,
latencyMs,
environment: 'production',
promptId: _meta.promptId,
metadata: { feature: 'summariser', user_tier: 'pro' }
})
}).catch(() => {})Safety Auto-Scan: When enprompta.prompt_id is set and you have Safety Auto-Scan enabled on the prompt, PII Leakage is evaluated inline (no latency added) and Bias & Prompt Injection run via a 15-minute background cron. Enable toggles under Dashboard > Prompts > [prompt] > Settings.
Teams
// List teams
const { data: teams } = await client.teams.list()
// Create a team
const team = await client.teams.create({
name: 'Engineering',
description: 'Engineering team prompts'
})
// Get team details
const teamDetails = await client.teams.get('team_abc123')
// Update team
await client.teams.update('team_abc123', {
name: 'Engineering Team'
})Webhooks
// Create a webhook
const webhook = await client.webhooks.create({
name: 'Slack Notifications',
url: 'https://your-server.com/webhooks/enprompta',
events: ['prompt.created', 'prompt.updated', 'execution.completed'],
secret: 'whsec_your_webhook_secret'
})
// List webhooks
const { data: webhooks } = await client.webhooks.list()
// Get delivery history
const deliveries = await client.webhooks.getDeliveries('webhook_abc123')Available Events
prompt.createdprompt.updatedprompt.deletedexecution.startedexecution.completedexecution.failedteam.member_addedMiddleware
The SDK includes a powerful middleware system for request/response processing.
Built-in Middleware
import { Enprompta, LoggingMiddleware, CacheMiddleware, RetryMiddleware } from '@enprompta/sdk'
const client = new Enprompta({
apiKey: 'ep_your_api_key',
middleware: [
// Log all requests and responses
new LoggingMiddleware({ level: 'debug' }),
// Cache GET requests for 5 minutes
new CacheMiddleware({ ttl: 300000 }),
// Retry failed requests
new RetryMiddleware({ maxRetries: 3 })
]
})Custom Middleware
import { Middleware, RequestContext, NextFunction } from '@enprompta/sdk'
class TimingMiddleware implements Middleware {
name = 'timing'
priority = 100
async handle(ctx: RequestContext, next: NextFunction) {
const start = Date.now()
const response = await next(ctx)
const duration = Date.now() - start
console.log(`Request took ${duration}ms`)
return response
}
}
const client = new Enprompta({
apiKey: 'ep_your_api_key',
middleware: [new TimingMiddleware()]
})Error Handling
The SDK provides typed error classes for different failure scenarios.
import {
EnpromptaError,
AuthenticationError,
RateLimitError,
ValidationError,
NotFoundError,
NetworkError,
TimeoutError
} from '@enprompta/sdk'
try {
await client.prompts.get('invalid_id')
} catch (error) {
if (error instanceof NotFoundError) {
console.log('Prompt not found')
} else if (error instanceof AuthenticationError) {
console.log('Invalid API key')
} else if (error instanceof RateLimitError) {
console.log(`Rate limited. Retry after ${error.retryAfter}s`)
} else if (error instanceof ValidationError) {
console.log('Validation errors:', error.errors)
} else if (error instanceof NetworkError) {
console.log('Network error:', error.message)
} else if (error instanceof TimeoutError) {
console.log('Request timed out')
} else if (error instanceof EnpromptaError) {
console.log(`API error ${error.code}: ${error.message}`)
}
}Retry Strategies
Built-in retry strategies for handling transient failures.
import { Enprompta, RetryStrategies } from '@enprompta/sdk'
const client = new Enprompta({
apiKey: 'ep_your_api_key',
retry: {
// Built-in strategies
strategy: RetryStrategies.exponential, // 1s, 2s, 4s, 8s...
// strategy: RetryStrategies.linear, // 1s, 2s, 3s, 4s...
// strategy: RetryStrategies.fixed, // Always same delay
// strategy: RetryStrategies.aggressive, // Quick retries
// strategy: RetryStrategies.conservative, // Long delays
// strategy: RetryStrategies.rateLimitAware, // Respects 429
maxRetries: 3,
baseDelay: 1000,
maxDelay: 30000
}
})| Strategy | Delays | Use Case |
|---|---|---|
exponential | 1s, 2s, 4s, 8s... | General purpose (default) |
linear | 1s, 2s, 3s, 4s... | Predictable backoff |
fixed | 1s, 1s, 1s... | Consistent retry timing |
aggressive | 100ms, 200ms... | Quick recovery |
conservative | 5s, 10s... | Resource-constrained |
rateLimitAware | Uses Retry-After header | Rate limit handling |