actualbudget/actual · error
invalid-secret-name
invalid-secret-name
Error message
Unknown secret name
What it means
The POST /secrets endpoint validates that the requested secret name is one of the known entries in the SecretName enum before storing it. If the body's 'name' is absent, misspelled, or not part of the supported set, the server rejects the request with HTTP 400 and reason 'invalid-secret-name'. This prevents arbitrary keys from being written into the secret store.
Source
Thrown at packages/sync-server/src/app-secrets.js:42
// Per-budget-file secrets are managed by file owners
function isBudgetFileOwner(fileId, userId) {
const { granted } = UserService.checkFilePermission(fileId, userId) || {
granted: 0,
};
return granted > 0;
}
function canManagePerBudgetFileSecrets(fileId, userId) {
return isAdmin(userId) || isBudgetFileOwner(fileId, userId);
}
app.post('/', async (req, res) => {
const { name, value } = req.body || {};
const fileId = req.get('X-Actual-File-Id');
const perBudgetFile = fileId != null;
if (!(name in SecretName)) {
res.status(400).send({
status: 'error',
reason: 'invalid-secret-name',
details: 'Unknown secret name',
});
return;
}
if (perBudgetFile) {
if (!isValidFileId(fileId)) {
res.status(400).send({
status: 'error',
reason: 'invalid-file-id',
details: 'invalid fileId',
});
return;
}
if (!canManagePerBudgetFileSecrets(fileId, res.locals.user_id)) {View on GitHub (pinned to d4334cb6e6)
Solutions
- Use an exact SecretName value from packages/sync-server/src/services/secrets-service.js (e.g. the gocardless/pluggy credential names) as the 'name' field.
- Print or fetch the current SecretName enum from the server source version you run and update your integration to match.
- Ensure the request body actually contains 'name' and Content-Type is application/json so express.json() parses it.
- Upgrade or align client and server versions so both agree on the secret name set.
Example fix
// before
await fetch('/secrets', { method: 'POST', body: JSON.stringify({ name: 'pluggy_key', value }) });
// after
await fetch('/secrets', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ name: 'pluggyai_client_secret', value }) }); Defensive patterns
Strategy: validation
Validate before calling
const KNOWN_SECRET_NAMES = ['gocardless_secret_id','gocardless_account_id','pluggyai_client_id','pluggyai_client_secret'];
function isValidSecretName(name) {
return typeof name === 'string' && KNOWN_SECRET_NAMES.includes(name);
} Type guard
function isSecretName(name) {
return typeof name === 'string' && name in SecretName;
} Try / catch
const res = await fetch('/secrets', { method: 'POST', body: JSON.stringify({ name, value }) });
if (res.status === 400) {
const body = await res.json();
if (body.reason === 'invalid-secret-name') throw new Error(`Unknown secret name: ${name}`);
} Prevention
- Source secret names from the SecretName enum of the deployed server version, not from memory
- Keep client and server versions in sync
- Always send Content-Type: application/json so 'name' is parsed
- Lint/check integration constants against the enum when upgrading
When it happens
Trigger: POST /secrets with a body whose 'name' is not a key of SecretName (e.g. typo, unknown provider name, name field missing entirely so 'undefined in SecretName' is false); API client using an outdated secret name after a rename in the SecretName enum.
Common situations: Hand-rolled scripts or integrations posting a secret name guessed from documentation of a different Actual version; provider credentials (e.g. gocardless/pluggy key names) changed between server versions; JSON body missing 'name' because of a content-type/parse issue.
Understand the failure class
Background: Invalid enum value errors: "Unknown type", "Invalid scope", "must be one of" — when a string is not on the library's allowed list — this error's family across 23 libraries.
Related errors
- invalid-file-id
- Unknown payee name normalization: ${String(normalization)}
- `date` is required when adding a transaction
- Amount is invalid, must be an integer: ${trans.amount}
- There is already a filter named ${item.name}
AI-assisted analysis of actualbudget/actual@d4334cb6e6 (2026-08-29).
Data as JSON: /api/errors/b28d027d43cfdcb7.
Report an issue: GitHub.