April 15, 2026 β€’ Version: 2026.4.11

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

  1. Config Load Phase: The gateway loads the JSON configuration containing `plugins.entries.firecrawl.config.webFetch.apiKey: "op://openclaw/Firecrawl API key/credential"`.
  2. Secret Resolution Phase: The SecretResolver service processes the 1Password SecretRef and retrieves the plaintext token. The runtime config view correctly displays this as resolved and masked.
  3. Provider Initialization Phase (BUG): When FirecrawlProvider is 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;
      // ...
    }
  4. Request Execution Phase: When web_fetch is invoked, the provider uses this.apiKey, which still contains the raw SecretRef string "op://openclaw/Firecrawl API key/credential" because the snapshot was taken before secret resolution completed.
  5. Firecrawl API Rejection: The Firecrawl API receives the literal op://... string as the API key, resulting in 401 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.

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

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): Promise { const response = await fetch(this.apiEndpoint, { headers: { ‘Authorization’: Bearer ${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 { // Resolve at request time, not construction time const resolvedApiKey = await this.secretResolver.resolve( this.config.webFetch.apiKey );

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 list
    

    If 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 get
    

    Ensure 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 set FIRECRAWL_API_KEY as 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 restart
    

    Not 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 --status
    

    Should 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:latest
    

    Correct - 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. Use op signin --account with a service account for CI/CD environments.
  • SSH Agent Forwarding:
    If 1Password credentials are forwarded via SSH, ensure OP_SESSION environment 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.
  • #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.
  • 401 Unauthorized: Invalid token
    Generic authentication failure. In this context, caused by the literal op://... 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 the op://vault/item/field pattern.

Error Code Reference

Error CodeCategoryDescription
SECRET_REF_001ResolutionSecretRef format validation failed
SECRET_REF_002Resolution1Password CLI not authenticated
SECRET_REF_003ResolutionTarget item or vault not accessible
SECRET_REF_004MaterializationResolved value not propagated to consumer
PLUGIN_AUTH_401AuthenticationPlugin received invalid credentials
PLUGIN_INIT_001InitializationProvider 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.apiKey
  • plugins.entries.google.config.credentials
  • plugins.entries.aws.config.accessKeyId
  • plugins.entries.azure.config.subscriptionKey

Verify these plugins are not experiencing the same issue by testing their respective SecretRef configurations.

Evidence & Sources

This troubleshooting guide was automatically synthesized by the FixClaw Intelligence Pipeline from community discussions.