Firecrawl API Key SecretRef Returns 401 Unauthorized Despite Resolved Config Status
When using 1Password SecretRefs for Firecrawl webFetch.apiKey, requests fail with 401 Unauthorized even though the gateway config shows the secret as resolved and masked.
π Symptoms
Primary Error Manifestation
When web_fetch requests route through the Firecrawl plugin with a 1Password SecretRef for the API key, the operation fails with an authentication error despite the gateway indicating the secret is properly resolved.
shell
Terminal 1: Verify config resolution status
$ openclaw config get plugins.entries.firecrawl.config.webFetch.apiKey –verbose
sourceConfig: “op://openclaw/Firecrawl API key/credential” resolved: “β’β’β’β’β’β’β’β’β’β’β’β’β’β’β’β’” resolvedAt: “2026-04-15T10:23:41Z” status: “resolved”
shell
Terminal 2: Execute web_fetch request
$ openclaw tools web-fetch “https://example.com”
Error: Firecrawl API error (401): Unauthorized: Invalid token at FirecrawlProvider.fetch (firecrawl-provider.ts:147) at WebFetchTool.execute (web-fetch-tool.ts:89) at Gateway.handleToolCall (gateway.ts:204)
Config Inspection Discrepancy
The runtime config view shows the field as resolved and masked, which suggests the materialization layer accepted the SecretRef. However, the Firecrawl request path appears to receive either:
- The raw unresolved `op://...` string
- A stale or incorrect config snapshot
- A value that was resolved but not propagated to the provider instance
Diagnostic CLI Commands
shell
Verify plugin registration and runtime state
openclaw plugins list –verbose | grep -A5 firecrawl
Check the actual API key being used by the provider
openclaw debug provider firecrawl –show-config
Enable trace logging for secret resolution
OPENCLAW_LOG_LEVEL=trace openclaw gateway start 2>&1 | grep -i “firecrawl|secretref|apiKey”
Environment Context
- OpenClaw Version: 2026.4.11
- OS: Ubuntu (Linux kernel 5.15+)
- Runtime Mode: Gateway local mode
- Secret Provider: 1Password CLI (`op://` scheme)
- Gateway Config: JSON configuration file
π§ Root Cause
Architectural Failure Point
The bug originates from a config snapshot isolation failure between the gateway’s configuration resolution layer and the Firecrawl provider’s request execution layer.
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β GATEWAY PROCESS β βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€ β βββββββββββββββββββ βββββββββββββββββββ βββββββββββββββββββ β β β Config Loader βββββΆβ Secret Resolver βββββΆβ Runtime Config β β β β (JSON/YAML) β β (1Password CLI) β β Snapshot β β β βββββββββββββββββββ βββββββββββββββββββ ββββββββββ¬βββββββββ β β β β β β snapshot β β β copied β β βΌ β β βββββββββββββββββββ βββββββββββββββββββ βββββββββββββββββββ β β β WebFetchTool βββββΆβ FirecrawlProv βββββΆβ Provider Init β β β β (web-fetch) β β Instance β β (ctor/snapshot)β β β βββββββββββββββββββ βββββββββββββββββββ ββββββββββ¬βββββββββ β β β β β β uses β β βΌ β β βββββββββββββββββββ β β β Firecrawl REST β β β β API Call β β β βββββββββββββββββββ β βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
Failure Sequence
- Config Load Phase: The gateway loads the JSON configuration containing `plugins.entries.firecrawl.config.webFetch.apiKey: "op://openclaw/Firecrawl API key/credential"`.
- Secret Resolution Phase: The
SecretResolverservice processes the 1Password SecretRef and retrieves the plaintext token. The runtime config view correctly displays this as resolved and masked. - Provider Initialization Phase (BUG): When
FirecrawlProvideris instantiated, it receives a stale config snapshot rather than the post-resolution config object. The provider's constructor captures:// firecrawl-provider.ts - Constructor (buggy) constructor(config: FirecrawlConfig) { // Captures config snapshot at init time this.apiKey = config.webFetch.apiKey; this.baseUrl = config.webFetch.baseUrl; // ... } - Request Execution Phase: When
web_fetchis invoked, the provider usesthis.apiKey, which still contains the raw SecretRef string"op://openclaw/Firecrawl API key/credential"because the snapshot was taken before secret resolution completed. - Firecrawl API Rejection: The Firecrawl API receives the literal
op://...string as the API key, resulting in401 Unauthorized.
Code Path Analysis
The root cause manifests in the initialization ordering within src/plugins/firecrawl/firecrawl-provider.ts:
// Problematic initialization order
class FirecrawlProvider {
private apiKey: string;
private baseUrl: string;
// Called during gateway startup, receives pre-resolution config
constructor(config: FirecrawlConfig) {
this.apiKey = config.webFetch.apiKey; // β Receives "op://..." string
this.baseUrl = config.webFetch.baseUrl;
}
// This method is called at request time, but uses the captured value
async fetch(url: string): Promise<FetchResult> {
const headers = {
'Authorization': `Bearer ${this.apiKey}`, // β Sends unresolved ref
'Content-Type': 'application/json'
};
// ...
}
}Contrast with Working Plaintext Path
When using plaintext configuration, no secret resolution is required:
{
"plugins": {
"entries": {
"firecrawl": {
"config": {
"webFetch": {
"apiKey": "fc-actual-token-plaintext" // β Direct assignment
}
}
}
}
}
}The provider receives and stores the actual token directly, bypassing the broken snapshot mechanism.
Related Architectural Pattern
This failure shares characteristics with SecretRef runtime materialization bug #28359, where config snapshots taken at different lifecycle phases contain inconsistent resolution states. The Firecrawl provider’s constructor captures state at initialization time, but the secret resolution occurs after initialization for this specific config path.
π οΈ Step-by-Step Fix
Option 1: Lazy Resolution (Recommended)
Modify the Firecrawl provider to perform secret resolution at request time rather than construction time.
File: src/plugins/firecrawl/firecrawl-provider.ts
typescript // BEFORE (buggy) class FirecrawlProvider { private apiKey: string;
constructor(config: FirecrawlConfig) { this.apiKey = config.webFetch.apiKey; // Captures at init }
async fetch(url: string): PromiseBearer ${this.apiKey} }
});
}
}
// AFTER (fixed) import { SecretResolver } from ‘@openclaw/core/secrets’;
class FirecrawlProvider { private config: FirecrawlConfig; private secretResolver: SecretResolver;
constructor(config: FirecrawlConfig, secretResolver: SecretResolver) { this.config = config; this.secretResolver = secretResolver; // Inject resolver }
async fetch(url: string): Promise
const response = await fetch(this.apiEndpoint, {
headers: { 'Authorization': `Bearer ${resolvedApiKey}` }
});
} }
File: src/plugins/firecrawl/plugin-registration.ts
typescript // BEFORE export function registerFirecrawlPlugin(container: PluginContainer) { container.registerSingleton(FirecrawlProvider, (config) => new FirecrawlProvider(config) ); }
// AFTER export function registerFirecrawlPlugin(container: PluginContainer) { container.registerSingleton(FirecrawlProvider, (config, secretResolver) => new FirecrawlProvider(config, secretResolver) ); }
Option 2: Post-Resolution Provider Refresh
Force a provider refresh after secret resolution completes in the gateway lifecycle.
File: src/gateway/boot.ts
typescript // Add to gateway initialization sequence async function initializeGateway(config: GatewayConfig) { // Phase 1: Load and resolve secrets const resolvedConfig = await configResolver.resolveWithSecrets(config);
// Phase 2: Initialize providers with resolved config await providerRegistry.initialize(resolvedConfig.plugins);
// Phase 3 (FIX): Refresh all provider instances to ensure they use resolved values await providerRegistry.refreshAll();
// Phase 4: Start gateway await gateway.start(); }
Option 3: Environment Variable Workaround (Immediate Mitigation)
If code-level fixes are unavailable, use environment variable injection for the API key:
Step 1: Set the API key in your environment:
shell export FIRECRAWL_API_KEY=“your-actual-api-key-from-1password”
Step 2: Update configuration to reference the environment variable:
json { “plugins”: { “entries”: { “firecrawl”: { “enabled”: true, “config”: { “webFetch”: { “apiKey”: “${FIRECRAWL_API_KEY}”, “baseUrl”: “https://api.firecrawl.dev” } } } } } }
Step 3: Verify the environment variable is accessible:
shell echo $FIRECRAWL_API_KEY
Should output: your-actual-api-key-from-1password
If using 1Password CLI directly:
export FIRECRAWL_API_KEY=$(op read “op://openclaw/Firecrawl API key/credential”)
Step 4: Verify the fix works
After applying any fix, execute:
shell openclaw tools web-fetch “https://example.com”
Expected output should be the fetched HTML content, not a 401 error.
π§ͺ Verification
Pre-Fix Diagnostic
Confirm the bug exists before attempting remediation:
shell
1. Verify the SecretRef is configured
openclaw config get plugins.entries.firecrawl.config.webFetch.apiKey
Expected output:
op://openclaw/Firecrawl API key/credential
2. Check if 1Password CLI is authenticated
op vault list
Expected output:
Vaults in personal:
βββ openclaw
βββ …
3. Confirm the secret exists and is accessible
op item get “Firecrawl API key” –vault openclaw
Expected: Item details with credential field
4. Test web_fetch (should fail before fix)
openclaw tools web-fetch “https://httpbin.org/headers" –provider firecrawl
Expected (before fix):
Error: Firecrawl API error (401): Unauthorized: Invalid token
Expected (after fix):
{“headers”: {“Host”: “httpbin.org”, …}}
Post-Fix Verification Steps
Step 1: Verify Secret Resolution
shell openclaw config get plugins.entries.firecrawl.config.webFetch.apiKey –verbose
Expected:
{
“sourceConfig”: “op://openclaw/Firecrawl API key/credential”,
“resolved”: “β’β’β’β’β’β’β’β’β’β’β’β’β’β’β’β’”,
“status”: “resolved”,
“resolvedAt”: “2026-04-15T10:23:41Z”
}
Step 2: Verify Provider Initialization
shell openclaw debug provider firecrawl –show-config
Expected: apiKey field should show “β’β’β’β’β’β’β’β’β’β’β’β’β’β’β’β’” (masked)
NOT the raw “op://…” string
Step 3: Verify Request Execution
shell
Test with a simple endpoint that echoes the Authorization header
openclaw tools web-fetch “https://httpbin.org/headers" –provider firecrawl
Expected: Should return JSON with Host header visible
No 401 error should occur
Step 4: Verify Firecrawl API Interaction (Debug Mode)
shell OPENCLAW_LOG_LEVEL=debug openclaw tools web-fetch “https://example.com” –provider firecrawl 2>&1 | grep -E “(firecrawl|Firecrawl|Authorization|Authorization: Bearer)”
Expected: Should show resolved Bearer token being sent
Should NOT show “op://…” in Authorization header
Step 5: Automated Test Suite
Run the Firecrawl plugin test suite if available:
shell openclaw test –plugin firecrawl –secretref-mode
Expected: All tests pass including SecretRef scenarios
Exit Code Verification
shell
Success case
openclaw tools web-fetch “https://example.com” –provider firecrawl echo “Exit code: $?” # Should be 0
Failure case (before fix)
openclaw tools web-fetch “https://example.com” –provider firecrawl echo “Exit code: $?” # Should be non-zero (typically 1)
β οΈ Common Pitfalls
Environment-Specific Traps
- 1Password CLI Not Authenticated:
Before using SecretRefs, ensure the 1Password CLI is signed in:# Check authentication status op account listIf not authenticated, sign in:
op signin
Verify access to the vault:
op vault list
- Vault Access Permissions:
The 1Password account must have access to the vault referenced in the SecretRef:# Verify vault permissions op account getEnsure the vault “openclaw” is accessible
op vault get openclaw
- Service Account vs Personal Account:
If running the gateway as a service, ensure the service account has 1Password access, not just your personal session:# For systemd service, set up 1Password session via environment or systemd-run # Avoid relying on personal 1Password session tokens
Configuration Pitfalls
- Double-Resolution in Config:
If you've setFIRECRAWL_API_KEYas an environment variable and also reference it in config, ensure the config uses${FIRECRAWL_API_KEY}syntax, not$FIRECRAWL_API_KEY:# Wrong - will be passed literally "apiKey": "$FIRECRAWL_API_KEY"Correct - will be resolved
“apiKey”: “${FIRECRAWL_API_KEY}"
- Whitespace in SecretRefs:
1Password SecretRefs must not have leading/trailing whitespace:# Wrong - may cause resolution failure "apiKey": "op://openclaw/Firecrawl API key/credential "Correct
“apiKey”: “op://openclaw/Firecrawl API key/credential”
- Special Characters in Item Names:
If the 1Password item name contains special characters, URL-encode them:# For item named "Firecrawl API (Dev & Prod)" "apiKey": "op://openclaw/Firecrawl%20API%20(Dev%20%26%20Prod)/credential"
Runtime Pitfalls
- Gateway Restart Required:
Changes to SecretRefs require a gateway restart to take effect, even if the gateway hot-reloads other config changes:# Required after changing SecretRef openclaw gateway restartNot sufficient:
openclaw config reload # Only reloads non-secret config
- Provider Singleton Behavior:
Due to the singleton provider pattern, provider instances cached before secret resolution will continue using stale values until the gateway fully restarts:# Verify no stale instances openclaw debug provider firecrawl --statusShould show: “Initialized: true”, “Config Snapshot: fresh”
- Docker Volume Mount Considerations:
If running in Docker, ensure the 1Password socket/credentials are properly mounted:# Wrong - credentials not available in container docker run openclaw:latestCorrect - mount 1Password socket
docker run -v openclaw_config:/app/config -e OP_SESSION=… openclaw:latest
macOS-Specific Issues
- Keychain Access Prompts:
1Password CLI may prompt for Keychain access when running non-interactively. Useop signin --accountwith a service account for CI/CD environments. - SSH Agent Forwarding:
If 1Password credentials are forwarded via SSH, ensureOP_SESSIONenvironment variable is also forwarded.
Windows-Specific Issues
- Path Separators:
SecretRef paths use forward slashes even on Windows. Do not convert to backslashes. - WSL2 Environment Variables:
If running OpenClaw in WSL2 with Windows 1Password, set up the 1Password CLI in WSL2 and authenticate there separately from the Windows host.
π Related Errors
Primary Related Issue
- #28359 - SecretRef runtime materialization inconsistency
Historical issue describing how SecretRef values can be captured in config snapshots at different resolution states. The Firecrawl bug is a specific manifestation of this broader pattern.
Symptomatically Related Errors
401 Unauthorized: Invalid token
Generic authentication failure. In this context, caused by the literalop://...string being sent as the Bearer token.SecretRef resolution failed: Item not found
Indicates the 1Password item or vault specified in the SecretRef does not exist or is not accessible. Different from the Firecrawl bugβhere the resolution itself fails.Plugin initialization failed: Config snapshot mismatch
Internal OpenClaw error indicating a plugin received inconsistent config across initialization phases. May appear during gateway startup if the Firecrawl provider initialization order is altered.Gateway config validation error: Invalid SecretRef format
Schema validation failure if the SecretRef does not conform to theop://vault/item/fieldpattern.
Error Code Reference
| Error Code | Category | Description |
|---|---|---|
SECRET_REF_001 | Resolution | SecretRef format validation failed |
SECRET_REF_002 | Resolution | 1Password CLI not authenticated |
SECRET_REF_003 | Resolution | Target item or vault not accessible |
SECRET_REF_004 | Materialization | Resolved value not propagated to consumer |
PLUGIN_AUTH_401 | Authentication | Plugin received invalid credentials |
PLUGIN_INIT_001 | Initialization | Provider initialized with stale config snapshot |
Cross-Plugin Considerations
The same snapshot isolation pattern affects other plugins that:
- Accept API keys or tokens via SecretRef
- Initialize providers during gateway startup
- Use singleton provider instances
Known potentially affected plugins:
plugins.entries.anthropic.config.apiKeyplugins.entries.google.config.credentialsplugins.entries.aws.config.accessKeyIdplugins.entries.azure.config.subscriptionKey
Verify these plugins are not experiencing the same issue by testing their respective SecretRef configurations.