Managing Secrets with Node.js SDK

This guide covers how to manage secrets using the Julep Node.js SDK. Secrets allow you to securely store and use sensitive information like API keys, passwords, and access tokens in your Julep applications.

Installation

Ensure you have the latest version of the Node.js SDK:

npm install @julep/sdk
# or
yarn add @julep/sdk

Authentication

Initialize the client with your API key:

import { Julep } from '@julep/sdk';

const julep = new Julep({ apiKey: 'your_api_key' });

Creating Secrets

Create a new secret:

const secret = await julep.secrets.create({
  name: 'stripe_api_key',
  value: 'sk_test_...',
  description: 'Stripe API key for payment processing',
  metadata: { environment: 'production', owner: 'payments-team' }
});

console.log(`Created secret: ${secret.name}`);

Required Parameters

  • name: The name of the secret (must be a valid identifier)
  • value: The secret value to encrypt and store

Optional Parameters

  • description: A description of what the secret is used for
  • metadata: An object of metadata to associate with the secret

Listing Secrets

List all available secrets:

// List all secrets
const secrets = await julep.secrets.list();
secrets.items.forEach(secret => {
  console.log(`${secret.name}: ${secret.description}`);
});

Pagination

Use pagination to handle large numbers of secrets:

// List with pagination
const secretsPage1 = await julep.secrets.list({ limit: 10, offset: 0 });
const secretsPage2 = await julep.secrets.list({ limit: 10, offset: 10 });

Filtering by Metadata

Filter secrets based on metadata:

// Filter by metadata
const productionSecrets = await julep.secrets.list({
  metadata: { environment: 'production' }
});

// Filter by multiple metadata fields
const teamSecrets = await julep.secrets.list({
  metadata: { 
    environment: 'production',
    owner: 'payments-team'
  }
});

Retrieving Secrets

Get a specific secret by name:

const secret = await julep.secrets.get({ name: 'stripe_api_key' });
console.log(`Secret: ${secret.name}, Created: ${secret.createdAt}`);

// Access the secret value
console.log(`Secret value: ${secret.value}`);

Note: For security, when listing secrets, the .value field will always show “ENCRYPTED”. The actual secret value is only returned when specifically requesting a single secret by name.

Updating Secrets

Update an existing secret:

const updatedSecret = await julep.secrets.update({
  name: 'stripe_api_key',
  value: 'sk_test_new_value...',
  description: 'Updated Stripe API key',
  metadata: { 
    environment: 'production', 
    owner: 'payments-team', 
    rotated: '2025-05-10' 
  }
});

console.log(`Updated secret: ${updatedSecret.name}`);

Partial Updates

You can update specific fields without changing others:

// Update only the description
const updatedSecret = await julep.secrets.update({
  name: 'stripe_api_key',
  description: 'New description for Stripe API key'
});

// Update only the metadata
const updatedMetadata = await julep.secrets.update({
  name: 'stripe_api_key',
  metadata: { lastRotated: '2025-05-10' }
});

Deleting Secrets

Delete a secret when it’s no longer needed:

await julep.secrets.delete({ name: 'stripe_api_key' });
console.log('Secret deleted');

Using Secrets in Tasks

Reference secrets when executing tasks:

import { Julep } from '@julep/sdk';

const julep = new Julep({ apiKey: 'your_api_key' });

// Define a task that uses a secret
const taskDefinition = {
  steps: [
    {
      kind: 'tool_call',
      tool: 'openai',
      operation: 'chat',
      arguments: {
        model: 'gpt-4',
        messages: [
          { role: 'user', content: 'What\'s the weather like?' }
        ]
      },
      secret_name: 'openai_api_key'
    }
  ]
};

// Create and execute the task
const task = await julep.tasks.create({ task: taskDefinition });
const execution = await julep.tasks.execute({ taskId: task.id });

Using Secrets in Expressions

You can reference secrets in expressions:

const taskDefinition = {
  steps: [
    {
      kind: 'transform',
      expression: "$ f'https://api.example.com/v1?api_key={secrets.api_key}&query={input}'",
      input: 'search query',
      output: 'api_url'
    }
  ]
};

Using Multiple Secrets

For tools that require multiple secrets:

const taskDefinition = {
  steps: [
    {
      kind: 'tool_call',
      tool: 'database',
      operation: 'query',
      arguments: {
        query: 'SELECT * FROM users',
        connection: {
          host: '$ secrets.db_host',
          user: '$ secrets.db_username',
          password: '$ secrets.db_password',
          database: '$ secrets.db_name'
        }
      }
    }
  ]
};

Error Handling

Handle common errors when working with secrets:

try {
  const secret = await julep.secrets.get({ name: 'non_existent_secret' });
} catch (error) {
  if (error.code === 'not_found') {
    console.log('Secret not found');
  } else {
    console.error('Error retrieving secret:', error);
  }
}

try {
  // Attempt to create a secret with an invalid name
  const secret = await julep.secrets.create({ name: 'invalid name', value: 'test' });
} catch (error) {
  if (error.code === 'validation_error') {
    console.log(`Validation error: ${error.message}`);
  } else {
    console.error('Error creating secret:', error);
  }
}

try {
  // Attempt to create a duplicate secret
  const secret = await julep.secrets.create({ name: 'existing_secret', value: 'test' });
} catch (error) {
  if (error.code === 'conflict') {
    console.log('Secret already exists');
  } else {
    console.error('Error creating secret:', error);
  }
}

Secret Rotation Example

Implement a secret rotation policy:

import { v4 as uuidv4 } from 'uuid';

/**
 * Safely rotate a secret by creating a new one and verifying it works
 * before deleting the old one.
 */
async function rotateSecret(julep, secretName, newValue) {
  // Create a temporary secret with a random suffix
  const tempName = `${secretName}_rotation_${uuidv4().substring(0, 8)}`;
  
  // Create the new secret
  await julep.secrets.create({
    name: tempName,
    value: newValue,
    description: `Temporary rotation for ${secretName}`,
    metadata: { rotationDate: new Date().toISOString() }
  });
  
  // Here you would test that the new secret works
  // ...
  
  // If tests pass, update metadata on the old secret
  const oldSecret = await julep.secrets.get({ name: secretName });
  const oldMetadata = oldSecret.metadata || {};
  
  await julep.secrets.update({
    name: secretName,
    metadata: {
      ...oldMetadata,
      archived: 'true',
      replacedBy: tempName,
      archivedDate: new Date().toISOString()
    }
  });
  
  // Rename the temporary secret to the standard name
  await julep.secrets.delete({ name: secretName });
  await julep.secrets.update({
    name: tempName,
    newName: secretName,
    description: oldSecret.description,
    metadata: { lastRotated: new Date().toISOString() }
  });
  
  return await julep.secrets.get({ name: secretName });
}

// Example usage
async function rotateStripeKey() {
  const newKey = 'sk_test_new_value_after_rotation';
  try {
    const rotatedSecret = await rotateSecret(julep, 'stripe_api_key', newKey);
    console.log(`Rotated secret: ${rotatedSecret.name}`);
  } catch (error) {
    console.error('Error rotating secret:', error);
  }
}

Working with Async/Await

All methods in the Node.js SDK return Promises, making them compatible with async/await:

async function manageSecrets() {
  try {
    // Create a secret
    const secret = await julep.secrets.create({
      name: 'database_password',
      value: 'complex-password-123',
      description: 'Production database password'
    });
    
    // List all secrets
    const secrets = await julep.secrets.list();
    
    // Create task using the secret
    const task = await julep.tasks.create({
      steps: [
        {
          kind: 'tool_call',
          tool: 'database',
          operation: 'query',
          secret_name: 'database_password'
        }
      ]
    });
    
    return { secret, secrets, task };
  } catch (error) {
    console.error('Error managing secrets:', error);
    throw error;
  }
}

Best Practices

  1. Use a consistent naming convention for all secrets
  2. Add detailed descriptions and metadata to make secrets discoverable
  3. Implement a rotation policy for sensitive secrets
  4. Log secret operations (creation, updates, deletions) but never log values
  5. Use try/catch blocks to handle potential errors gracefully
  6. Consider automating secret rotation for important credentials
  7. Use metadata to track important dates and ownership information

Next Steps