Dokploy/dokploy · warning · TRPCError
BAD_REQUEST
BAD_REQUEST
Error message
Credentials must be re-entered when changing the provider type
What it means
mergeDnsProviderConfig merges an incoming provider config with stored secrets: when a sensitive field arrives as the mask placeholder (DNS_SECRET_MASK) and the provider type is changing, it cannot backfill the old provider's secret, so it throws BAD_REQUEST. This prevents silently storing the mask or copying secrets between incompatible providers.
Source
Thrown at packages/server/src/services/dns-provider.ts:42
): DnsProviderConfig => {
const masked: Record<string, unknown> = { ...config };
for (const field of SENSITIVE_FIELDS[config.providerType]) {
if (masked[field]) {
masked[field] = DNS_SECRET_MASK;
}
}
return masked as DnsProviderConfig;
};
export const mergeDnsProviderConfig = (
incoming: DnsProviderConfig,
existing: DnsProviderConfig,
): DnsProviderConfig => {
const merged: Record<string, unknown> = { ...incoming };
for (const field of SENSITIVE_FIELDS[incoming.providerType]) {
if (merged[field] === DNS_SECRET_MASK) {
if (incoming.providerType !== existing.providerType) {
throw new TRPCError({
code: "BAD_REQUEST",
message:
"Credentials must be re-entered when changing the provider type",
});
}
merged[field] = (existing as Record<string, unknown>)[field];
}
}
return merged as DnsProviderConfig;
};
const isUniqueNameViolation = (error: unknown) =>
error instanceof Error && error.message.includes("dns_provider_org_name_idx");
export const createDnsProvider = async (
input: z.infer<typeof apiCreateDnsProvider>,
organizationId: string,
) => {View on GitHub (pinned to 546686ea35)
Solutions
- Re-enter the credentials (API key/token/secret) for the new provider type when switching it
- Or keep the same providerType if you only meant to update non-secret fields
- If building a client: omit sensitive fields entirely instead of sending the mask value when changing providerType
Example fix
// before (client sends mask while changing type)
{ providerType: "route53", apiKey: "********" }
// after (send real secret, or omit field)
{ providerType: "route53", apiKey: "AKIA..." } Defensive patterns
Strategy: validation
Validate before calling
const changingType = input.providerType !== existing.providerType;
const masked = SENSITIVE_FIELDS[input.providerType].some((f) => input[f] === DNS_SECRET_MASK);
if (changingType && masked) throw new Error("Re-enter credentials when changing provider type"); Type guard
const isMaskedConfig = (cfg: Record<string, unknown>) => Object.values(cfg).some((v) => v === DNS_SECRET_MASK);
Try / catch
try { await updateDnsProviderConfig(input); } catch (e) { if (e instanceof TRPCError && e.code === "BAD_REQUEST") { /* prompt user for fresh credentials */ } } Prevention
- Omit masked secret fields instead of echoing them from GET responses
- Always re-enter credentials when switching provider type
- UI: clear secret inputs when the provider dropdown changes
When it happens
Trigger: Updating a DNS provider while changing providerType (e.g. Cloudflare → Route53) and leaving masked credential fields as-is (the UI sends the mask for unchanged secrets). Secrets for the new provider type must be re-entered.
Common situations: Editing a DNS provider in the UI and switching the provider dropdown without re-typing API credentials; API clients PATCHing providerType plus masked fields copied from a GET response.
Understand the failure class
Background: BAD_REQUEST error code: request rejected as invalid (HTTP 400) - causes and fixes across libraries — this error's family across 8 libraries.
Related errors
AI-assisted analysis of Dokploy/dokploy@546686ea35 (2026-08-27).
Data as JSON: /api/errors/97705fb6f37f8961.
Report an issue: GitHub.