> ## Documentation Index
> Fetch the complete documentation index at: https://jobo.world/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Node Types

> Reference for all five pipeline node types: Input, Liquid, AI, JSON Merge, and Output.

## Input Node

The entry point for data. Receives raw job JSON from the Outbound Feed sync engine. Every pipeline starts with exactly one Input node.

| Property  | Type   | Description                             |
| --------- | ------ | --------------------------------------- |
| `source`  | enum   | `jobs_feed`, `webhook`, `manual`        |
| `filters` | object | Optional filters (same as feed filters) |

***

## Liquid Template

Reshape and remap JSON fields using [Liquid](https://shopify.github.io/liquid/) template syntax. The Liquid node takes the output of its parent node as input and renders a Liquid template against it. Your template **must output valid JSON**.

| Property   | Type   | Required | Description                              |
| ---------- | ------ | -------- | ---------------------------------------- |
| `template` | string | Yes      | Liquid template string that outputs JSON |

### Available Variables

The input data is available via `data` and dot notation:

| Variable            | Type   | Description                                                                                            |
| ------------------- | ------ | ------------------------------------------------------------------------------------------------------ |
| `data`              | object | The full JSON object from the parent node. Access nested fields with dot notation: `data.company.name` |
| `data.title`        | string | Job title                                                                                              |
| `data.description`  | string | Full job description (HTML)                                                                            |
| `data.company`      | object | Company object with `name`, `url`, `logo`                                                              |
| `data.company.name` | string | Company name                                                                                           |
| `data.company.url`  | string | Company website URL                                                                                    |
| `data.company.logo` | string | Company logo URL                                                                                       |
| `data.location`     | object | Location object with `city`, `state`, `country`                                                        |
| `data.apply_url`    | string | Application URL                                                                                        |
| `data.published_at` | string | Publication date                                                                                       |
| `data.tags`         | array  | Array of tag strings                                                                                   |

### Available Filters

All standard [Shopify Liquid filters](https://shopify.github.io/liquid/filters/) are supported, plus these custom filters:

| Filter           | Description                              | Example                                             |
| ---------------- | ---------------------------------------- | --------------------------------------------------- |
| `slugify`        | Converts a string to a URL-safe slug     | `{{ data.title \| slugify }}` → `software-engineer` |
| `truncate_words` | Truncates to a number of words           | `{{ data.description \| truncate_words: 50 }}`      |
| `json_escape`    | Escapes a string for safe JSON embedding | `{{ data.description \| json_escape }}`             |

### Example: Remap Fields

```liquid theme={null}
{
  "jobTitle": "{{ data.title }}",
  "employer": "{{ data.company.name }}",
  "location": "{{ data.location.city }}, {{ data.location.state }}",
  "applyUrl": "{{ data.apply_url }}",
  "postedAt": "{{ data.published_at | date: '%Y-%m-%d' }}"
}
```

### Example: SEO Slug Generation

```liquid theme={null}
{
  "seo_title": "{{ data.title }} at {{ data.company.name }} - {{ data.location.city }}",
  "slug": "{{ data.title | slugify }}-{{ data.company.name | slugify }}-{{ data.id | slice: 0, 8 }}",
  "is_engineering": {% if data.tags contains 'engineering' %}true{% else %}false{% endif %}
}
```

***

## AI (OpenRouter)

Use LLM intelligence to extract, classify, enrich, or rewrite job data. The AI node sends data to any model available on [OpenRouter](https://openrouter.ai/) (GPT-4o, Claude, Gemini, Llama, etc.). Configure a system prompt for behavior and a user prompt with the `{{input}}` placeholder for the data.

### Configuration

| Property       | Type   | Required | Default | Description                                                                                                    |
| -------------- | ------ | -------- | ------- | -------------------------------------------------------------------------------------------------------------- |
| `model`        | string | **Yes**  | —       | OpenRouter model ID (e.g., `google/gemini-2.0-flash-001`, `openai/gpt-4o-mini`, `anthropic/claude-3.5-sonnet`) |
| `systemPrompt` | string | **Yes**  | —       | System instructions for the AI. Should include "respond with valid JSON" for structured output.                |
| `userPrompt`   | string | **Yes**  | —       | The prompt template. Use `{{input}}` to inject the parent node's output as context.                            |
| `outputSchema` | object | No       | —       | Optional JSON Schema to constrain the AI's output structure. Strongly recommended for reliability.             |
| `temperature`  | number | No       | `0.3`   | LLM temperature (0–2). Lower = more consistent, higher = more creative.                                        |

<Info>
  The `{{ input }}` placeholder in the user prompt is replaced with the full
  JSON output from the parent node. This is the primary way to pass data to the
  AI model.
</Info>

### Supported Models

Any model available on [OpenRouter](https://openrouter.ai/models) can be used. Popular choices:

| Model                         | Best For                                   |
| ----------------------------- | ------------------------------------------ |
| `google/gemini-2.0-flash-001` | Fast, cost-effective structured extraction |
| `openai/gpt-4o-mini`          | Balanced quality and speed                 |
| `openai/gpt-4o`               | Highest quality extraction and generation  |
| `anthropic/claude-3.5-sonnet` | Complex reasoning and long descriptions    |

### Example: Extract Skills

**System Prompt:**

```text theme={null}
You are a job data extraction assistant. Always respond with valid JSON.
Extract technical skills, soft skills, and seniority level from the job posting.
```

**User Prompt:**

```text theme={null}
Extract skills and seniority from this job posting:

{{input}}

Respond with:
{
  "technical_skills": ["skill1", "skill2"],
  "soft_skills": ["skill1"],
  "seniority": "senior" | "mid" | "junior" | "lead"
}
```

### Full Configuration Example

```json theme={null}
{
  "model": "google/gemini-2.0-flash-001",
  "systemPrompt": "You are a job data analyst. Extract structured requirements from job descriptions. Always respond with valid JSON.",
  "userPrompt": "Extract requirements and responsibilities from this job:\n\n{{input}}",
  "outputSchema": {
    "type": "object",
    "properties": {
      "requirements": {
        "type": "object",
        "properties": {
          "must_have": { "type": "array", "items": { "type": "string" } },
          "preferred": { "type": "array", "items": { "type": "string" } }
        }
      },
      "responsibilities": {
        "type": "array",
        "items": { "type": "string" }
      }
    }
  },
  "temperature": 0.2
}
```

***

## JSON Merge

Combine two JSON inputs into a single unified object. The JSON Merge node has **two input handles**: `base` (original data) and `patch` (new data, typically AI output). This is essential for preserving original job data while adding AI-generated fields.

### Configuration

| Property             | Type | Required | Default     | Description                                                                                                   |
| -------------------- | ---- | -------- | ----------- | ------------------------------------------------------------------------------------------------------------- |
| `strategy`           | enum | No       | `Deep`      | **Deep** — recursively merges nested objects. **Shallow** — only merges top-level keys.                       |
| `conflictResolution` | enum | No       | `PatchWins` | **PatchWins** — patch values override base on conflict. **BaseWins** — base values are preserved on conflict. |

### Deep Merge Example

<Tabs>
  <Tab title="Base (original data)">
    ```json theme={null}
    {
      "title": "Software Engineer",
      "company": { "name": "Acme" },
      "location": "San Francisco"
    }
    ```
  </Tab>

  <Tab title="Patch (AI output)">
    ```json theme={null}
    {
      "skills": ["Python", "AWS"],
      "seniority": "senior",
      "salary_estimate": "$150k-$180k"
    }
    ```
  </Tab>

  <Tab title="Result (Deep, PatchWins)">
    ```json theme={null}
    {
      "title": "Software Engineer",
      "company": { "name": "Acme" },
      "location": "San Francisco",
      "skills": ["Python", "AWS"],
      "seniority": "senior",
      "salary_estimate": "$150k-$180k"
    }
    ```
  </Tab>
</Tabs>

### Nested Deep Merge Example

When both base and patch have nested objects, deep merge recursively combines them:

<Tabs>
  <Tab title="Base">
    ```json theme={null}
    {
      "title": "SWE",
      "skills": { "hard": ["Python"] }
    }
    ```
  </Tab>

  <Tab title="Patch">
    ```json theme={null}
    {
      "skills": { "hard": ["Python", "Go"], "soft": ["Leadership"] }
    }
    ```
  </Tab>

  <Tab title="Result (Deep, PatchWins)">
    ```json theme={null}
    {
      "title": "SWE",
      "skills": { "hard": ["Python", "Go"], "soft": ["Leadership"] }
    }
    ```
  </Tab>
</Tabs>

<Tip>
  Use **Deep + PatchWins** (the defaults) in most cases. This preserves all
  original fields while letting AI-generated data override specific nested
  values.
</Tip>

***

## Output Node

The exit point for processed data. The final JSON from this node is what gets pushed to your destination. Every pipeline ends with exactly one Output node.

| Property      | Type   | Description                        |
| ------------- | ------ | ---------------------------------- |
| `destination` | enum   | `database`, `webhook`, `file`      |
| `config`      | object | Destination-specific configuration |
