Skip to main content

Secrets

Securely store and manage sensitive data like API keys, passwords, and certificates.

Why Use Secrets

Your application needs sensitive credentials: API keys for third-party services, database passwords, authentication tokens. Hardcoding them is a security risk, and environment variables alone don't offer rotation or access control.

With Squid secrets, you store credentials securely and access them in your backend code at runtime:

Backend code
@executable()
async sendEmail(to: string, subject: string, body: string): Promise<void> {
// Access the secret securely without exposing it to the client
const apiKey = this.secrets['SENDGRID_API_KEY'];
await sendgrid.send({ to, subject, body, apiKey });
}

No hardcoded values. No exposed credentials. Secrets are injected at runtime.

Overview

Squid secrets provide a secure key-value store for sensitive data. Secrets are encrypted at rest, cached for performance, and accessible from your backend code at runtime via this.secrets.

Squid supports two types of secrets:

TypeDescriptionValueExample use case
Custom secretsUser-defined key-value pairs for credentials from external systemsYou provide the valueStoring a SendGrid API key or DB password
API keysSquid-managed application keys for authenticating with the platformAuto-generated by SquidAuthenticating your backend with Squid

When to use secrets

  • Store third-party API keys and authentication tokens securely
  • Manage database credentials
  • Rotate API keys programmatically on a schedule using schedulers
  • Enable secure API calls through executables without exposing credentials to the client

How it works

  1. Create secrets through the Squid Console or programmatically via the Client SDK
  2. Squid encrypts and stores the secrets securely
  3. In backend services, access secrets via this.secrets['SECRET_NAME']
  4. For programmatic management (rotation, dynamic creation), use squid.admin().secrets()

Quick Start

Prerequisites

  • A Squid application with a backend project
  • The @squidcloud/backend package installed

Step 1: Add a secret in the Squid Console

Navigate to your application in the Squid Console, go to Secrets, and add a new secret with key MY_API_SECRET and your desired value.

Step 2: Access the secret in your backend

Backend code
import { executable, SquidService } from '@squidcloud/backend';

export class ExampleService extends SquidService {
@executable()
async getProtectedData(): Promise<string> {
const apiKey = this.secrets['MY_API_SECRET'];

const response = await fetch('https://api.example.com/data', {
headers: { Authorization: `Bearer ${apiKey}` },
});

return (await response.json()) as string;
}
}

Step 3: Deploy the backend

Deploy your backend so the secret is available at runtime. Run this command from the backend directory:

squid deploy

For more details on deploying, see deploying your backend.

Step 4: Call from the client

Client code
const data = await squid.executeFunction('getProtectedData');
console.log(data);

Authentication and Configuration

Caution

Programmatic secret management via the Client SDK requires your client to be initialized with your application's API key using the apiKey option. Never do this from a user-facing application. Perform secret management only from a secure environment such as in your Squid Backend.

To manage secrets programmatically, initialize the Squid client with your API key:

import { Squid } from '@squidcloud/client';

const squid = new Squid({
appId: 'YOUR_APP_ID',
region: 'us-east-1.aws',
apiKey: 'YOUR_API_KEY', // Required for programmatic secret management
});

Without the API key, any attempt to manage secrets results in an UNAUTHORIZED error.

Core Concepts

Secret entry

All secret operations return a SecretEntry object:

interface SecretEntry {
key: string; // The secret name
value: string; // The secret value (always stored as a string)
lastUpdated: number; // Timestamp in milliseconds since epoch
}

The upsert method accepts string, number, or boolean values, but all values are stored and returned as strings.

Custom secrets vs API keys

Squid separates secrets into two categories because they serve different purposes and have different lifecycle needs.

Custom secrets store values that you control. Use them for credentials that come from external systems, such as a third-party API key, a database password, or an OAuth token. You set the value, and you are responsible for updating it when the credential changes.

API keys are authentication keys that Squid generates and manages for you. Use them when your application needs a secure, unique key for authenticating requests (for example, authenticating your backend with the Squid platform). You don't choose the value; Squid creates it, and you can rotate it by calling upsert again to generate a new one.

FeatureCustom secretsAPI keys
Value sourceYou provide the valueAuto-generated by Squid
Use caseThird-party credentials, passwords, tokensApplication authentication keys
Create/Updatesecrets.upsert(key, value)secrets.apiKeys.upsert(key)
Batch operationsupsertMany, deleteManyNot available
Force deleteSupported (force parameter)Not supported

In short: if you already have a credential value that needs to be stored securely, use a custom secret. If you need Squid to generate and manage a key for you, use an API key.

Backend access

In Squid backend services, secrets are available as a read-only key-value map via this.secrets:

Backend code
// Access the value directly by key
const apiKey = this.secrets['MY_API_KEY'];
const dbPassword = this.secrets['DB_PASSWORD'];

This map is populated automatically at runtime. No API key or special configuration is needed to read secrets from the backend.

Force delete

When deleting a custom secret, you can pass a force parameter. By default, force is false, which means the deletion fails if the secret is currently used by a connector. This prevents accidentally breaking connector configurations:

const secrets = squid.admin().secrets();

// Default: fails if 'DB_PASSWORD' is used by a connector
await secrets.delete('DB_PASSWORD');

// Force delete regardless of usage
await secrets.delete('DB_PASSWORD', true);

Custom Secrets

All programmatic secret management uses the squid.admin().secrets() client:

const secrets = squid.admin().secrets();

Getting a secret

Retrieve a single secret by name. Returns the SecretEntry or undefined if the secret does not exist:

const secrets = squid.admin().secrets();

const entry = await secrets.get('SECRET_NAME');
if (entry) {
console.log(entry.key); // 'SECRET_NAME'
console.log(entry.value); // 'your_value'
console.log(entry.lastUpdated); // 1692306991724
}

Getting all secrets

Retrieve all custom secrets as a map of SecretEntry objects keyed by secret name:

const secrets = squid.admin().secrets();

const allSecrets = await secrets.getAll();
// {
// 'SECRET_NAME': { key: 'SECRET_NAME', value: 'your_value', lastUpdated: 1692306991724 },
// 'OTHER_SECRET': { key: 'OTHER_SECRET', value: 'other_value', lastUpdated: 1692306991725 }
// }

Creating or updating a secret

Use upsert to create a new secret or update an existing one. The method accepts string, number, or boolean values, but all values are stored and returned as strings:

const secrets = squid.admin().secrets();

const entry = await secrets.upsert('SECRET_NAME', 'your_new_value');
// { key: 'SECRET_NAME', value: 'your_new_value', lastUpdated: 1692306991724 }

To create or update multiple secrets at once, use upsertMany:

const secrets = squid.admin().secrets();

const entries = await secrets.upsertMany([
{ key: 'API_KEY_1', value: 'key-value-1' },
{ key: 'API_KEY_2', value: 'key-value-2' },
]);
// Returns an array of SecretEntry objects

Deleting a secret

Delete a single secret by name:

const secrets = squid.admin().secrets();
await secrets.delete('SECRET_NAME');

Delete multiple secrets at once:

const secrets = squid.admin().secrets();
await secrets.deleteMany(['SECRET_NAME', 'OTHER_SECRET']);

To force deletion of secrets that are in use by connectors, pass true as the second argument:

const secrets = squid.admin().secrets();

// Force delete even if used by a connector
await secrets.delete('SECRET_NAME', true);
await secrets.deleteMany(['SECRET_NAME', 'OTHER_SECRET'], true);

API Keys

Squid API keys are managed through the apiKeys property on the secret client. Unlike custom secrets, Squid auto-generates the key values.

const apiKeys = squid.admin().secrets().apiKeys;

Getting an API key

Retrieve an API key by name. Returns the SecretEntry or undefined if the key does not exist:

const apiKeys = squid.admin().secrets().apiKeys;

const entry = await apiKeys.get('API_KEY_NAME');
if (entry) {
console.log(entry.value); // 'a123b456-cd78-9e90-f123-gh45i678j901'
}

Getting all API keys

Retrieve all API keys as a map of SecretEntry objects:

const apiKeys = squid.admin().secrets().apiKeys;

const allKeys = await apiKeys.getAll();
// {
// 'API_KEY_NAME': {
// key: 'API_KEY_NAME',
// value: 'a123b456-cd78-9e90-f123-gh45i678j901',
// lastUpdated: 1692306991724
// }
// }

Creating or rotating an API key

Call upsert with the key name. Squid generates the new value automatically:

const apiKeys = squid.admin().secrets().apiKeys;

const entry = await apiKeys.upsert('API_KEY_NAME');
console.log(entry.value); // New auto-generated key value

Deleting an API key

const apiKeys = squid.admin().secrets().apiKeys;
await apiKeys.delete('API_KEY_NAME');

Error Handling

Common errors

ErrorCauseSolution
UNAUTHORIZEDClient not initialized with a valid API keyPass your API key in the apiKey option when initializing the client
Secret in useDeleting a secret used by a connector with force=falseRemove the connector dependency first, or pass true for force
undefined resultSecret does not existCheck the key name; get returns undefined for missing secrets
Request timeoutServer could not acquire a lock for the operationRetry the operation after a brief delay

Handling errors

const secrets = squid.admin().secrets();

try {
await secrets.upsert('MY_SECRET', 'new-value');
} catch (error) {
if (error.message === 'UNAUTHORIZED') {
console.error('API key is missing or invalid');
} else {
console.error('Failed to update secret:', error.message);
}
}

Best Practices

Security

  1. Never expose secrets on the client. Access secrets in executables or other backend code, not in frontend code.
  2. Restrict API key usage. Only initialize the Squid client with apiKey in secure, server-side environments such as your Squid Backend.
  3. Use connectors for third-party services. Squid connectors handle credential injection automatically, reducing the need to manage secrets manually.

Rotation

  1. Rotate secrets on a schedule. Use schedulers to periodically rotate API keys and credentials.
  2. Check lastUpdated before rotating. Avoid unnecessary rotations by checking the secret's age first.

Operations

  1. Use batch methods for bulk changes. upsertMany and deleteMany are more efficient than individual calls when managing multiple secrets.
  2. Avoid force-deleting by default. The default behavior (force=false) protects against accidentally breaking connector configurations.

Code Examples

Rotating an API key on a schedule

Combine secret management with a scheduler to rotate API keys automatically:

Backend code
import { CronExpression, scheduler, SquidService } from '@squidcloud/backend';

export class KeyRotationService extends SquidService {
@scheduler('rotate-api-key', CronExpression.EVERY_DAY_AT_MIDNIGHT)
async rotateApiKey(): Promise<void> {
const entry = await this.squid.admin().secrets().apiKeys.get('MY_API_KEY');
if (!entry) return;

// Rotate if the key is over 30 days old
const thirtyDaysMs = 30 * 24 * 60 * 60 * 1000;
if (entry.lastUpdated < Date.now() - thirtyDaysMs) {
await this.squid.admin().secrets().apiKeys.upsert('MY_API_KEY');
console.log('API key rotated successfully');
}
}
}

Securely calling a third-party API

Use an executable to keep API keys on the server while allowing the client to trigger the call:

Backend code
import { executable, SquidService } from '@squidcloud/backend';

interface EmailRequest {
to: string;
subject: string;
body: string;
}

export class EmailService extends SquidService {
@executable()
async sendEmail(request: EmailRequest): Promise<{ success: boolean }> {
const apiKey = this.secrets['SENDGRID_API_KEY'];

const response = await fetch('https://api.sendgrid.com/v3/mail/send', {
method: 'POST',
headers: {
Authorization: `Bearer ${apiKey}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
personalizations: [{ to: [{ email: request.to }] }],
from: { email: 'noreply@example.com' },
subject: request.subject,
content: [{ type: 'text/plain', value: request.body }],
}),
});

if (!response.ok) {
throw new Error(`Email send failed: ${response.status}`);
}

return { success: true };
}
}
Client code
const result = await squid.executeFunction('sendEmail', {
to: 'user@example.com',
subject: 'Welcome!',
body: 'Thanks for signing up.',
});

See Also

  • Executables - Call backend functions from the client
  • Schedulers - Run code on a schedule
  • Connectors - Connect to third-party services with managed credentials