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:
@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:
| Type | Description | Value | Example use case |
|---|---|---|---|
| Custom secrets | User-defined key-value pairs for credentials from external systems | You provide the value | Storing a SendGrid API key or DB password |
| API keys | Squid-managed application keys for authenticating with the platform | Auto-generated by Squid | Authenticating 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
- Create secrets through the Squid Console or programmatically via the Client SDK
- Squid encrypts and stores the secrets securely
- In backend services, access secrets via
this.secrets['SECRET_NAME'] - For programmatic management (rotation, dynamic creation), use
squid.admin().secrets()
Quick Start
Prerequisites
- A Squid application with a backend project
- The
@squidcloud/backendpackage 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
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
const data = await squid.executeFunction('getProtectedData');
console.log(data);
Authentication and Configuration
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.
| Feature | Custom secrets | API keys |
|---|---|---|
| Value source | You provide the value | Auto-generated by Squid |
| Use case | Third-party credentials, passwords, tokens | Application authentication keys |
| Create/Update | secrets.upsert(key, value) | secrets.apiKeys.upsert(key) |
| Batch operations | upsertMany, deleteMany | Not available |
| Force delete | Supported (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:
// 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
| Error | Cause | Solution |
|---|---|---|
UNAUTHORIZED | Client not initialized with a valid API key | Pass your API key in the apiKey option when initializing the client |
| Secret in use | Deleting a secret used by a connector with force=false | Remove the connector dependency first, or pass true for force |
undefined result | Secret does not exist | Check the key name; get returns undefined for missing secrets |
| Request timeout | Server could not acquire a lock for the operation | Retry 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
- Never expose secrets on the client. Access secrets in executables or other backend code, not in frontend code.
- Restrict API key usage. Only initialize the Squid client with
apiKeyin secure, server-side environments such as your Squid Backend. - Use connectors for third-party services. Squid connectors handle credential injection automatically, reducing the need to manage secrets manually.
Rotation
- Rotate secrets on a schedule. Use schedulers to periodically rotate API keys and credentials.
- Check
lastUpdatedbefore rotating. Avoid unnecessary rotations by checking the secret's age first.
Operations
- Use batch methods for bulk changes.
upsertManyanddeleteManyare more efficient than individual calls when managing multiple secrets. - 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:
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:
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 };
}
}
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