# What Is the Skill Contract for Job Portal Skills in the AI Job Search Framework?

> Understand the AI Job Search skill contract. Learn how job portal CLIs ensure seamless interchangeability with standardized JSON output and zero dependencies for efficient AI-driven job searching.

- Repository: [Mads Lorentzen/ai-job-search](https://github.com/MadsLorentzen/ai-job-search)
- Tags: deep-dive
- Published: 2026-08-29

---

**The AI Job Search framework enforces a strict portal-skill contract that requires every job portal CLI to expose `search` and `detail` sub-commands with standardized JSON output, specific error handling to stderr, and zero runtime dependencies to ensure seamless interchangeability across the `/scrape` workflow.**

The `MadsLorentzen/ai-job-search` repository establishes a rigorous skill contract for job portal skills that governs how automated scraping tools must behave within the ecosystem. This contract ensures all portal skills remain interchangeable for the `/scrape` workflow while maintaining consistent behavior for both local users and CI pipelines. Every generated CLI must adhere to specifications documented in [`.claude/commands/add-portal.md`](https://github.com/MadsLorentzen/ai-job-search/blob/main/.claude/commands/add-portal.md) and summarized in [`CONTRIBUTING.md`](https://github.com/MadsLorentzen/ai-job-search/blob/main/CONTRIBUTING.md) to pass automated validation.

## Required CLI Commands and Interface

Every portal skill must expose exactly two sub-commands via its CLI entry point to maintain compatibility with the framework's orchestration layer.

### The search Command

The `search` command must accept standardized flags and return paginated job listings. According to [`.claude/commands/add-portal.md`](https://github.com/MadsLorentzen/ai-job-search/blob/main/.claude/commands/add-portal.md), implementations must support:

- `--query` or `-q` for search terms
- `--jobage <days>` to filter by posting recency
- `--page <n>` using **1-indexed** pagination
- `--limit <n>` as a client-side cap on results returned
- `--format json|table|plain` with **json** as the default output format
- `--location` or `-l` (optional, only if the portal supports geographic filtering)

### The detail Command

The `detail <id|url>` command retrieves comprehensive information about a specific job posting. This command accepts a positional identifier argument and supports the same `--format` flag available in the `search` command.

## Standardized JSON Output Schema

Portal skills must emit structured JSON matching a strict schema to ensure downstream parsers can process results uniformly across different job portals.

### Search Results Format

The `search` command must output JSON adhering to this exact structure:

```json
{
  "meta": {
    "count": 42,
    "page": 1
  },
  "results": [
    {
      "id": "12345",
      "title": "Software Engineer",
      "company": "Acme Corp",
      "location": "Copenhagen",
      "date": "2024-08-28",
      "url": "https://example.com/job/12345"
    }
  ]
}

```

Missing values must be set to `null` rather than omitted entirely to maintain schema consistency.

### Error Output Format

Errors are printed **to stderr** as JSON objects containing `error` and `code` fields, and the process must exit with code **1**. No error text is ever written to stdout.

```bash
bun run .agents/skills/example-portal/cli/src/cli.ts detail 12345 --format plain

```

Typical error output:

```json
{
  "error": "Missing required flag --query",
  "code": "MISSING_FLAG"
}

```

## HTTP Conventions and Resilience

Portal skills must implement robust fetching behavior that respects web infrastructure while maintaining reliability under failure conditions.

### User-Agent and Retry Logic

Use an honest user-agent string formatted as `Mozilla/5.0 (compatible; <portal>-cli/1.0)`. When encountering HTTP **429** or **5xx** status codes, implement exponential back-off with jitter for a maximum of approximately six retries. Return empty strings or `null` for 404 responses rather than crashing.

### Credential Management

If a portal requires an API token, read it **only** from an environment variable named `<SERVICE>_API_TOKEN`. Abort with a JSON error using `code: "MISSING_CREDENTIALS"` if the variable is unset. Never expose tokens in documentation, tests, or source code.

## Implementation Constraints

The contract imposes strict technical limitations to ensure portability, security, and maintainability across the skill ecosystem.

### Zero Runtime Dependencies

Skills should default to **zero runtime dependencies**, utilizing only `bun`, native `fetch`, and regex for parsing. Add a parsing library only if markup cannot be handled with regex, and document this architectural decision in the skill's [`SKILL.md`](https://github.com/MadsLorentzen/ai-job-search/blob/main/SKILL.md) file.

### Fault-Tolerant HTML Parsing

Parse each result independently so malformed cards do not break the entire response. The reference implementation in [`.agents/skills/linkedin-search/cli/src/helpers.ts`](https://github.com/MadsLorentzen/ai-job-search/blob/main/.agents/skills/linkedin-search/cli/src/helpers.ts) demonstrates this defensive pattern via the `parseJobCards` function.

### Legal Compliance Warnings

When robots.txt or Terms of Service restrict automated access, the generated [`SKILL.md`](https://github.com/MadsLorentzen/ai-job-search/blob/main/SKILL.md) must include a prominent "⚠️ Personal use only" notice, mirroring the `linkedin-search` skill's approach documented in [`CONTRIBUTING.md`](https://github.com/MadsLorentzen/ai-job-search/blob/main/CONTRIBUTING.md).

## CI Enforcement and Validation

The framework automatically enforces the skill contract for job portal skills through continuous integration. The `cli-checks` job in [`.github/workflows/ci.yml`](https://github.com/MadsLorentzen/ai-job-search/blob/main/.github/workflows/ci.yml) discovers every `.agents/skills/*/cli/package.json` and executes the skill's `typecheck` and `test` scripts on each push. This ensures that new portal implementations maintain compatibility with the contract before merging into the repository.

## Summary

- Every portal skill must implement `search` and `detail <id|url>` sub-commands with specific flags including `--query`, `--jobage`, and `--format`.
- Output must conform to a strict JSON schema with `meta` and `results` fields, while errors go to stderr as JSON with exit code 1.
- Implement exponential back-off with jitter for retries, use honest user-agents, and return null for 404s rather than crashing.
- Maintain zero runtime dependencies where possible, parse HTML defensively using patterns like `parseJobCards`, and never hardcode API credentials.
- Include "Personal use only" warnings when scraping restrictions apply, and validate all implementations via the automated `cli-checks` CI workflow.

## Frequently Asked Questions

### What happens if a portal skill violates the contract?

The CI pipeline will reject the contribution. The `cli-checks` job automatically validates every portal skill against the contract requirements by running `typecheck` and `test` scripts defined in each skill's [`package.json`](https://github.com/MadsLorentzen/ai-job-search/blob/main/package.json), ensuring non-compliant code cannot merge into the repository.

### Why does the contract mandate zero runtime dependencies?

This constraint maximizes portability and minimizes security surface area. By relying only on `bun`, native `fetch`, and regex, skills remain lightweight and avoid dependency drift or supply-chain vulnerabilities while maintaining fast installation times across different environments.

### How should portal skills handle missing API tokens?

Skills must check for the `<SERVICE>_API_TOKEN` environment variable and exit with code 1, printing a JSON error to stderr with `code: "MISSING_CREDENTIALS"`. This standardized error format allows orchestration tools to detect authentication failures programmatically without parsing stdout.

### Where is the portal-skill contract documented?

The complete specification lives in [`.claude/commands/add-portal.md`](https://github.com/MadsLorentzen/ai-job-search/blob/main/.claude/commands/add-portal.md), while [`CONTRIBUTING.md`](https://github.com/MadsLorentzen/ai-job-search/blob/main/CONTRIBUTING.md) provides a concise summary for contributors. Reference implementations demonstrating compliant HTML parsing and CLI structure are available in `.agents/skills/linkedin-search/cli/`.