# Setting Up Web Search Backends (Brave and Parallel AI) for last30days-skill

> Learn to set up web search backends like Brave and Parallel AI for last30days-skill. Configure API keys for automated web searches and optimize your skill's functionality.

- Repository: [Matt Van Horn/last30days-skill](https://github.com/mvanhorn/last30days-skill)
- Tags: how-to-guide
- Published: 2026-03-25

---

**To enable automated web search in last30days-skill, configure either `PARALLEL_API_KEY` or `BRAVE_API_KEY` in your environment files, with Parallel AI automatically taking precedence over Brave when both are present.**

The **last30days-skill** aggregates recent content from platforms like Reddit, X, and YouTube, and can optionally enrich these results with real-time web search data. Activating this capability requires supplying API credentials for supported search backends in specific configuration files that the skill reads at runtime according to the logic in [`scripts/lib/env.py`](https://github.com/mvanhorn/last30days-skill/blob/main/scripts/lib/env.py).

## How the Backend Selection Works

The skill automatically selects the best available web search provider based on a hardcoded priority system. The `get_web_search_source` function in [`scripts/lib/env.py`](https://github.com/mvanhorn/last30days-skill/blob/main/scripts/lib/env.py) (lines 36-44) checks for API keys in the following order:

1. **Parallel AI** — checked first via `PARALLEL_API_KEY`
2. **Brave Search** — fallback via `BRAVE_API_KEY`  
3. **OpenRouter/Perplexity** — last resort via `OPENROUTER_API_KEY`

If none of these keys are detected, the skill runs without native web search capabilities and relies on the assistant to perform searches instead. The selection logic appears as:

```python
def get_web_search_source(config: dict) -> Optional[str]:
    if config.get('PARALLEL_API_KEY'):
        return 'parallel'          # preferred

    if config.get('BRAVE_API_KEY'):
        return 'brave'             # fallback

    if config.get('OPENROUTER_API_KEY'):
        return 'openrouter'        # last resort

    return None

```

## Configuration Hierarchy and File Locations

The skill loads configuration through three layers of precedence, implemented in `env.get_config()` in [`scripts/lib/env.py`](https://github.com/mvanhorn/last30days-skill/blob/main/scripts/lib/env.py) (lines 15-33):

- **Environment variables** (`os.environ`) — highest priority
- **Project-specific file** `./.claude/last30days.env` — overrides global settings
- **Global config file** `~/.config/last30days/.env` — default location

The function merges these layers, allowing project-specific overrides while maintaining global defaults.

## Step-by-Step Setup Guide

### Parallel AI Configuration

**Parallel AI** offers the preferred search backend with structured objective-based queries.

First, obtain your API key from [parallel.ai](https://parallel.ai) — typically formatted as `sk-par-...`.

Then create the configuration file in your preferred location. For global use across all projects:

```bash
mkdir -p ~/.config/last30days
echo "PARALLEL_API_KEY=sk-par-your_key_here" > ~/.config/last30days/.env
chmod 600 ~/.config/last30days/.env

```

For project-specific overrides:

```bash
mkdir -p .claude
echo "PARALLEL_API_KEY=sk-par-your_key_here" > .claude/last30days.env

```

The `parallel_search.search_web` function in [`scripts/lib/parallel_search.py`](https://github.com/mvanhorn/last30days-skill/blob/main/scripts/lib/parallel_search.py) (lines 26-74) constructs JSON payloads with parameters for `objective`, `max_results`, and `max_chars_per_result`, submitting via HTTP POST and normalizing responses to extract `url`, `title`, and `snippet` fields.

### Brave Search Configuration

**Brave Search** serves as the secondary backend, offering traditional web search with optional LLM context enrichment.

Obtain your API key from the Brave Search Dashboard after enabling a paid subscription — UUID format like `xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx`.

Configure the key globally:

```bash
mkdir -p ~/.config/last30days
cat > ~/.config/last30days/.env <<'EOF'
BRAVE_API_KEY=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
EOF
chmod 600 ~/.config/last30days/.env

```

Optionally enable the LLM-Context endpoint for richer snippets by setting `BRAVE_LLM_CONTEXT=1` as an environment variable. The `brave_search.search_web` function in [`scripts/lib/brave_search.py`](https://github.com/mvanhorn/last30days-skill/blob/main/scripts/lib/brave_search.py) (lines 36-92) constructs GET requests to `https://api.search.brave.com/res/v1/web/search`, supports `freshness` parameters for date ranges, normalizes HTML removal, filters excluded domains, and assigns a default relevance score of `0.6`.

## Verifying Your Setup

Run the diagnostic command to confirm which backend is active:

```bash
python3 scripts/last30days.py --diagnose

```

Examine the output for the web search backend line:

```

Web search backend: parallel

```

If the output shows `none` instead of `parallel` or `brave`, the skill cannot locate your API key in any configuration layer.

## Integration in the Research Pipeline

When executing a research query, the main orchestrator in [`scripts/last30days.py`](https://github.com/mvanhorn/last30days-skill/blob/main/scripts/last30days.py) calls `_search_web`, which delegates to the selected backend based on the `env.get_web_search_source` return value:

```python
elif backend == "brave":
    raw_results = brave_search.search_web(
        topic, from_date, to_date,
        config["BRAVE_API_KEY"],
        depth=depth, use_llm_context=use_llm_ctx,
    )
elif backend == "parallel":
    raw_results = parallel_search.search_web(
        topic, from_date, to_date,
        config["PARALLEL_API_KEY"], depth=depth,
    )

```

The returned dictionaries undergo enrichment with IDs, date confidence scoring, and relevance ranking before incorporation into the final brief.

## Summary

- **Parallel AI** takes precedence over Brave when both `PARALLEL_API_KEY` and `BRAVE_API_KEY` are configured, as enforced by the logic in [`scripts/lib/env.py`](https://github.com/mvanhorn/last30days-skill/blob/main/scripts/lib/env.py).
- Store API keys in `~/.config/last30days/.env` for global use or `.claude/last30days.env` for project-specific overrides.
- The skill automatically detects available backends using `get_web_search_source` and fails gracefully to assistant-performed searches if no keys are present.
- Verify activation using `python3 scripts/last30days.py --diagnose` before running research queries.

## Frequently Asked Questions

### What happens if I configure multiple API keys simultaneously?

The skill strictly follows the priority order defined in `env.get_web_search_source`: **Parallel AI** first, **Brave** second, and **OpenRouter** third. If you provide all three keys, Parallel AI will be selected automatically. To force a specific backend, provide only that backend's API key and remove others from your configuration files.

### Why does my configuration file need 600 permissions?

While the skill will read the file regardless of permissions, setting `chmod 600` on `~/.config/last30days/.env` prevents other system users from reading your sensitive API credentials. This follows security best practices for files containing authentication tokens that grant access to paid search APIs.

### Can I use environment variables instead of files?

Yes. Environment variables take the highest precedence in the configuration hierarchy. You can export `PARALLEL_API_KEY` or `BRAVE_API_KEY` directly in your shell, and `env.get_config()` will prioritize these values over entries in `.env` files. This approach is particularly useful for CI/CD pipelines where writing configuration files is impractical.

### How do I troubleshoot when --diagnose shows "none"?

If the diagnostic output reports `Web search backend: none`, verify three things: first, ensure the key variable name matches exactly (`PARALLEL_API_KEY` or `BRAVE_API_KEY`, case-sensitive); second, confirm the file path is either `~/.config/last30days/.env` or `./.claude/last30days.env`; third, check that the file contains no syntax errors or whitespace issues around the equals sign.