Secrets
Secrets store opaque customer secret material. Novo encrypts native secrets at rest and never returns the secret value after create or rotate.
V1 supports source: 'novo' only. The source field is reserved so future enterprise resolvers can point at customer-owned systems such as Vault, AWS Secrets Manager, or 1Password without changing workspace or MCP bindings.
Methods
| Method | Route |
|---|---|
| novo.secrets.create({...}) | POST /v1/secrets |
| novo.secrets.list({ limit?, cursor?, name?, metadata? }) | GET /v1/secrets |
| novo.secrets.get(id) | GET /v1/secrets/:id |
| novo.secrets.update(id, {...}) | PATCH /v1/secrets/:id |
| novo.secrets.delete(id) | DELETE /v1/secrets/:id |
| novo.secrets.rotate(id, { value }) | POST /v1/secrets/:id/rotate |
Type signature
type Secret = {
id: string;
object: 'secret';
name: string;
source: 'novo' | 'external';
contentType: string | null;
metadata: Metadata;
configVersion: number;
createdAt: string;
updatedAt: string;
};
type SecretsNamespace = {
create(input: {
name: string;
value: string;
source?: 'novo';
contentType?: string | null;
metadata?: Metadata;
}): Promise<Secret>;
list(input?: {
limit?: number;
cursor?: string;
name?: string;
metadata?: Metadata;
}): Promise<Page<Secret>>;
get(secretId: string): Promise<Secret>;
update(secretId: string, input: {
name?: string;
contentType?: string | null;
metadata?: MetadataPatch;
}): Promise<Secret>;
delete(secretId: string): Promise<{ id: string; object: 'secret'; deleted: true }>;
rotate(secretId: string, input: { value: string }): Promise<Secret>;
};
Create and rotate
const stripeKey = await novo.secrets.create({
name: 'stripe-api-key',
value: process.env.STRIPE_API_KEY!,
contentType: 'text/plain',
metadata: { owner: 'payments' },
});
await novo.secrets.rotate(stripeKey.id, {
value: process.env.STRIPE_API_KEY_NEXT!,
});
value is write-only. Responses include id, config metadata, and timestamps, but never the secret value.
List and reconcile
list returns a full Page<Secret> (hasMore, nextCursor, limit). name is an exact match, and metadata is exact key/value containment over the flat metadata object. Use those filters to adopt existing resources after 409 name_conflict or to sweep tenant-owned resources by metadata.
How secrets are used
A Secret by itself does not expose anything to an agent. Access is granted through a destination-specific projection:
- Use a Secret context with
workspace_envto inject a secret as a managed workspace environment variable. - Use Tool server
auth: { type: 'bearer', secretId }to authenticate Novo-to-MCP traffic. - Use HTTP connection
auth: { type: 'bearer', secretId }to authenticate Novo-to-API traffic.
The same Secret can be used in multiple places only when you attach it to each destination explicitly.
Limits and responsibility
Secret values are opaque UTF-8 strings up to 64 KiB. contentType is advisory and does not affect validation. Secret names do not need to be valid environment variable names.
Novo provides encrypted storage, org isolation, projection validation, runtime redaction, and destination boundaries. You are responsible for least privilege, rotation policy, and limiting each secret's blast radius.
Errors
404 secret_not_found: bad ID.400 invalid_request_error: empty value, value over 64 KiB, unsupportedsource, or invalid metadata.409 name_conflict: another Secret in the organization already uses thatname.409 resource_in_use: delete attempted while a tool server, HTTP connection, or secret context still references the Secret. The error includesdetails.inUseBy.