How ai-job-search Handles External API Integrations: A Modular Security-First Approach

The ai-job-search framework isolates every external API interaction within dedicated skill modules and command handlers, using OAuth authentication for Gmail and Notion, public endpoints for job boards, and strict domain allow-listing to prevent unauthorized outbound traffic.

The MadsLorentzen/ai-job-search repository manages external API integrations through a decentralized skill architecture that keeps the core automation logic agnostic of third-party implementation details. Rather than embedding HTTP clients throughout the codebase, each service—whether a job board or productivity suite—lives in its own self-contained CLI tool under specific directories. This design ensures that authentication flows, rate limiting, and data parsing remain encapsulated and maintainable while keeping the primary keyword of external API integrations secure and modular.

Skill-Based Architecture for Job Portals

Each job search integration is implemented as a standalone CLI "skill" located in the .agents/skills/ directory, executed via Bun to fetch and normalize posting data.

The LinkedIn search skill targets the public, unauthenticated jobs-guest Voyager API endpoint. Located in .agents/skills/linkedin-search/cli/main.ts, the TypeScript client constructs a request URL and streams JSON results to stdout.

// .agents/skills/linkedin-search/cli/main.ts (executed by bun)
const response = await fetch(
    `https://www.linkedin.com/voyager/api/jobs-guest/jobs?keywords=${encodeURIComponent(query)}&location=${encodeURIComponent(location)}`
);
const data = await response.json();
console.log(JSON.stringify(data.results));

freehire.me Public REST API

Similarly, the freehire.me integration in .agents/skills/freehire-search/cli/main.ts queries the freehire.me public REST API, retrieving regional and remote job listings via standard HTTP GET requests without requiring API keys.

// .agents/skills/freehire-search/cli/main.ts
const response = await fetch(
    `https://freehire.me/api/v1/jobs?region=${REGION}&remote=${REMOTE}`
);

Productivity Suite Synchronization

Beyond job aggregation, the project integrates with personal productivity tools through Claude command definitions that delegate to authenticated connectors.

Gmail Sync via OAuth 2.0

The /gmail-sync command, defined in .claude/commands/gmail-sync.md, instantiates a GmailConnector using a stored OAuth token to search for status-update messages—such as interview invitations or rejection notices—then proposes state changes to the tracking system only after user approval.


# Conceptual implementation from .claude/commands/gmail-sync.md

gmail = GmailConnector(oauth_token=USER_TOKEN)
messages = gmail.search('subject:"Interview" label:unread')
for msg in messages:
    propose_outcome_update(msg)

Notion MCP Server Integration

The /notion-sync command pushes a read-only view of the local tracking CSV to a Notion database using the official Notion MCP server. According to the command documentation in .claude/commands/notion-sync.md, this one-way sync relies on OAuth authentication managed by the connector rather than manual secret keys.

Security Controls and Local Data Governance

The project implements strict safeguards to prevent unauthorized external calls and to handle sensitive data locally where possible.

Domain Allow-Listing Enforcement

All outbound network requests are gated by security_guards.py, which maintains an explicit allow-list of permitted domains. This module, located at tools/security_guards.py, ensures that the system cannot silently invoke unknown or untrusted APIs, acting as a network-level sandbox for the skill modules. The guards are protected by CI checks to prevent accidental modification of permitted domains.

Local Salary Benchmarking

Unlike the job search skills, the salary_lookup.py utility operates entirely on local datasets (CSV or Excel files) without invoking external APIs. An optional helper script, tools/convert_salary_excel.py, transforms Excel files into JSON for local consumption, ensuring compensation data never leaves the user's environment.

Summary

  • The ai-job-search project isolates external API integrations within modular CLI skills under .agents/skills/ and command definitions in .claude/commands/.
  • LinkedIn and freehire.me integrations use unauthenticated public REST endpoints executed via Bun TypeScript CLIs.
  • Gmail and Notion synchronization rely on OAuth 2.0 connectors to access user data securely without hardcoded secrets.
  • Security guards in tools/security_guards.py enforce a strict domain allow-list, blocking unauthorized outbound traffic.
  • Salary data processing remains entirely local via salary_lookup.py, eliminating external API dependencies for sensitive financial information.

Frequently Asked Questions

Does ai-job-search require API keys for job searching?

No. The LinkedIn and freehire.me skills query public, unauthenticated endpoints that do not require API keys. Only the Gmail and Notion integrations require OAuth authentication to access private user data, managed through secure connectors rather than static credentials.

How does the project prevent unauthorized API calls?

The tools/security_guards.py module implements an explicit domain allow-list that validates every outbound request against permitted hosts. This prevents skill modules from contacting unknown or malicious endpoints, and the file is protected by CI guards to ensure the allow-list cannot be bypassed.

Is the Notion integration bidirectional?

No. According to the command documentation in .claude/commands/notion-sync.md, the Notion sync is one-way and read-only, pushing a view of the local tracking data to Notion without writing changes back to the source CSV.

What runtime executes the job search skill scripts?

The skill modules are written in TypeScript and executed using Bun, as evidenced by the fetch-based HTTP calls in .agents/skills/linkedin-search/cli/main.ts and the CLI entry points throughout the .agents/skills/ directory.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →