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

# Job Applications Pipeline

> Build an agent that parses your resume, searches job portals, extracts listings, generates tailored answers with AI, and submits applications automatically.

Automate job applications across multiple postings on any job portal.

This cookbook creates an agent that logs into a job portal, searches for relevant positions based on your resume, extracts job listings, and applies to each one with AI-generated responses tailored to the role.

***

## What you'll build

An agent that:

1. Parses your resume PDF to extract structured information
2. Logs into a job portal using saved credentials
3. Searches for relevant jobs based on your resume
4. Extracts a list of matching job postings
5. For each job: extracts details, generates tailored answers, and submits the application

***

## Prerequisites

* **Skyvern Cloud API key**: Get one at [app.skyvern.com/settings](https://app.skyvern.com/settings) → API Keys

Install the SDK:

<CodeGroup>
  ```bash Python theme={null}
  pip install skyvern
  ```

  ```bash TypeScript theme={null}
  npm install @skyvern/client
  ```
</CodeGroup>

Set your API key:

```bash theme={null}
export SKYVERN_API_KEY="your-api-key"
```

***

## Sample Job Portal

We'll use *Job Stash*, a fake job board website created for agent automation testing.
Change `job_portal_url` to use your job portal's URL.

| Field          | Value                                                        |
| -------------- | ------------------------------------------------------------ |
| URL            | [https://job-stash.vercel.app](https://job-stash.vercel.app) |
| Login email    | [demo@manicule.dev](mailto:demo@manicule.dev)                |
| Login password | helloworld                                                   |

<img src="https://mintcdn.com/skyvern/N6Hpwi5bsL1eZtKa/images/cookbooks/job-stash.png?fit=max&auto=format&n=N6Hpwi5bsL1eZtKa&q=85&s=428111ed3f987ee877b6ac82c07dec8c" alt="Job Stash, a demo job portal" width="3112" height="1976" data-path="images/cookbooks/job-stash.png" />

***

## Step 1: Store credentials

Before defining the agent, store the login email and password Skyvern will use.

This makes sure your passwords are never stored in the shareable agent definition and never sent to LLMs.

<Tabs>
  <Tab title="Cloud UI">
    <Steps>
      <Step title="Open Credentials">
        Go to **Credentials** in the sidebar. Choose **Password** after clicking on **+ Add**.

        <img src="https://mintcdn.com/skyvern/N6Hpwi5bsL1eZtKa/images/cookbooks/credentials.png?fit=max&auto=format&n=N6Hpwi5bsL1eZtKa&q=85&s=822b163d039142238583cf58b870ed28" alt="Creating a password credential on Skyvern" width="3240" height="1976" data-path="images/cookbooks/credentials.png" />
      </Step>

      <Step title="Create a credential">
        Click **Create Credential**. Set the name to `Job Stash`, add login page URL, and enter the username (`demo@manicule.dev`) and password (`helloworld`). Click **Save**.

        <img src="https://mintcdn.com/skyvern/N6Hpwi5bsL1eZtKa/images/cookbooks/js-creds.png?fit=max&auto=format&n=N6Hpwi5bsL1eZtKa&q=85&s=d1d5fd7cefb9b1d7d43299d135703eb7" alt="Fill username and password to add a credential to Skyvern" width="3240" height="1976" data-path="images/cookbooks/js-creds.png" />
      </Step>

      <Step title="Copy the credential ID">
        Copy the credential ID (here: `cred_504284876895871734`). You'll use this when configuring the agent.

        <img src="https://mintcdn.com/skyvern/N6Hpwi5bsL1eZtKa/images/cookbooks/creds-id-copy.png?fit=max&auto=format&n=N6Hpwi5bsL1eZtKa&q=85&s=4bbe358481980ef1d1b17d481b008abf" alt="Fill username and password to add a credential to Skyvern" width="3240" height="1976" data-path="images/cookbooks/creds-id-copy.png" />
      </Step>
    </Steps>
  </Tab>

  <Tab title="API / SDK">
    <CodeGroup>
      ```python Python theme={null}
      import os
      import asyncio
      from skyvern import Skyvern

      async def main():
          client = Skyvern(api_key=os.getenv("SKYVERN_API_KEY"))

          credential = await client.create_credential(
              name="Job Portal",
              credential_type="password",
              credential={
                  "username": "demo@manicule.dev",
                  "password": "helloworld"
              }
          )

          print(f"Credential ID: {credential.credential_id}")
          # Save this ID for your workflow: cred_xxx

      asyncio.run(main())
      ```

      ```typescript TypeScript theme={null}
      import { SkyvernClient } from "@skyvern/client";

      const client = new SkyvernClient({
        apiKey: process.env.SKYVERN_API_KEY,
      });

      const credential = await client.createCredential({
        name: "Job Portal",
        credential_type: "password",
        credential: {
          username: "demo@manicule.dev",
          password: "helloworld",
        },
      });

      console.log(`Credential ID: ${credential.credential_id}`);
      ```

      ```bash cURL theme={null}
      curl -X POST "https://api.skyvern.com/v1/credentials" \
        -H "x-api-key: $SKYVERN_API_KEY" \
        -H "Content-Type: application/json" \
        -d '{
          "name": "Job Portal",
          "credential_type": "password",
          "credential": {
            "username": "demo@manicule.dev",
            "password": "helloworld"
          }
        }'
      ```
    </CodeGroup>
  </Tab>
</Tabs>

***

## Step 2: Create a new agent

<Tabs>
  <Tab title="Cloud UI">
    <Steps>
      <Step title="Create the agent">
        Go to **Agents** → **Create Agent**. Name it "Search and Apply for Jobs Workflow".

        <img src="https://mintcdn.com/skyvern/N6Hpwi5bsL1eZtKa/images/cookbooks/create-workflow.png?fit=max&auto=format&n=N6Hpwi5bsL1eZtKa&q=85&s=7bd26987e4956cd802b189c3fdce455d" alt="Create a new agent from Skyvern Dashboard" width="3240" height="1976" data-path="images/cookbooks/create-workflow.png" />
      </Step>
    </Steps>
  </Tab>

  <Tab title="API / SDK">
    An **agent definition** is a JSON or YAML file that describes your entire agent: its metadata, parameters, and blocks. Create a file called `job-application-workflow.yaml` (or `.json`) with the following template:

    <CodeGroup>
      ```json JSON theme={null}
      {
        "title": "Search and Apply for Jobs Workflow",
        "description": "Search for jobs on any portal, extract listings, generate tailored answers with AI, and submit applications automatically.",
        "proxy_location": "RESIDENTIAL",
        "workflow_definition": {
          "version": 1,
          "parameters": [],
          "blocks": []
        }
      }
      ```

      ```yaml YAML theme={null}
      title: Search and Apply for Jobs Workflow
      description: "Search for jobs on any portal, extract listings, generate tailored answers with AI, and submit applications automatically."
      proxy_location: RESIDENTIAL
      workflow_definition:
        version: 1
        parameters: []    # <-- filled in below
        blocks: []        # <-- filled in Step 3
      ```
    </CodeGroup>

    We'll populate `parameters` and `blocks` in the sections that follow.
  </Tab>
</Tabs>

### Set parameters

Parameters are the inputs your agent accepts. Defining them upfront lets you reuse the same agent against different job portals.

<Tabs>
  <Tab title="Cloud UI">
    On the **Start** node, add the following parameters:

    | Parameter        | Type          | Notes                                       |
    | ---------------- | ------------- | ------------------------------------------- |
    | `resume`         | File URL      | URL to your resume PDF                      |
    | `credentials`    | Credential ID | Select the credential you created in Step 1 |
    | `job_portal_url` | String        | Job portal login URL                        |

    <img src="https://mintcdn.com/skyvern/N6Hpwi5bsL1eZtKa/images/cookbooks/add-params.png?fit=max&auto=format&n=N6Hpwi5bsL1eZtKa&q=85&s=ecfac7b9e0c3fb111b5123bf967e377a" alt="Setting input parameters in an agent in Skyvern" width="3240" height="1976" data-path="images/cookbooks/add-params.png" />
  </Tab>

  <Tab title="API / SDK">
    Replace the empty `parameters` array with the following:

    <CodeGroup>
      ```json JSON theme={null}
      "parameters": [
        { "key": "resume", "parameter_type": "workflow", "workflow_parameter_type": "file_url" },
        {
          "key": "credentials",
          "parameter_type": "workflow",
          "workflow_parameter_type": "credential_id",
          "default_value": "your-credential-id"
        },
        {
          "key": "job_portal_url",
          "description": "URL of the job portal",
          "parameter_type": "workflow",
          "workflow_parameter_type": "string"
        }
      ]
      ```

      ```yaml YAML theme={null}
      parameters:
        - key: resume
          parameter_type: workflow
          workflow_parameter_type: file_url
        - key: credentials
          parameter_type: workflow
          workflow_parameter_type: credential_id
          default_value: your-credential-id     # <-- replace this
        - key: job_portal_url
          description: URL of the job portal
          parameter_type: workflow
          workflow_parameter_type: string
      ```
    </CodeGroup>
  </Tab>
</Tabs>

***

## Step 3: Add agent blocks

The agent chains together several blocks to automate the full job application process:

1. **PDF Parser block**: Extracts structured data from your resume
2. **Login block**: Authenticates to the job portal using stored credentials
3. **Navigation block**: Searches for relevant jobs based on your resume
4. **Extraction block**: Extracts a list of job postings from search results
5. **For loop** (Go to URL + Extraction + Action + Extraction + Text Prompt + Navigation): Iterates over each job to extract details, generates tailored answers using an LLM, and submits the application.

In the Cloud UI, click the **+** button after the Start node to add blocks sequentially. For SDK users, add each block to the `blocks` array in your agent definition file.

### PDF Parser block

The `pdf_parser` block extracts structured information from your resume PDF. Skyvern uses AI to identify your name, contact info, work experience, education, and skills.

<Tabs>
  <Tab title="Cloud UI">
    Add a **PDF Parser** block. Set **File URL** to the `resume` parameter.

    <img src="https://mintcdn.com/skyvern/N6Hpwi5bsL1eZtKa/images/cookbooks/add-block.png?fit=max&auto=format&n=N6Hpwi5bsL1eZtKa&q=85&s=df5d8e942d1ce59f18a81e02e846c797" alt="Adding a block in an agent in Skyvern" width="3240" height="1976" data-path="images/cookbooks/add-block.png" />
  </Tab>

  <Tab title="API / SDK">
    <CodeGroup>
      ```json JSON theme={null}
      {
        "block_type": "pdf_parser",
        "label": "parsed_resume",
        "file_url": "{{ resume }}"
      }
      ```

      ```yaml YAML theme={null}
      - block_type: pdf_parser
        label: parsed_resume
        file_url: "{{ resume }}"
      ```
    </CodeGroup>
  </Tab>
</Tabs>

The parsed data is accessible as `parsed_resume_output` in subsequent blocks.

### Login block

The `login` block authenticates using stored credentials. Skyvern injects the username/password directly into form fields without exposing them to the LLM.

<Tabs>
  <Tab title="Cloud UI">
    Add a **Login** block. Configure it as follows:

    * **URL**: set to the `job_portal_url` parameter
    * **Credential**: Select the `credentials` parameter
    * **Goal**: "Log in using the provided credentials. Handle any cookie consent popups. COMPLETE when on the account dashboard or orders page."
  </Tab>

  <Tab title="API / SDK">
    <CodeGroup>
      ```json JSON theme={null}
      {
        "block_type": "login",
        "label": "login_to_portal",
        "url": "{{ job_portal_url }}",
        "title": "login_to_portal",
        "parameter_keys": ["credentials"],
        "navigation_goal": "Log in using the provided credentials.\nHandle any cookie consent popups.\nCOMPLETE when on the account dashboard or orders page.",
        "max_retries": 0,
        "engine": "skyvern-1.0"
      }
      ```

      ```yaml YAML theme={null}
      - block_type: login
        label: login_to_portal
        url: "{{ job_portal_url }}"
        title: login_to_portal
        parameter_keys:
          - credentials
        navigation_goal: |
          Log in using the provided credentials.
          Handle any cookie consent popups.
          COMPLETE when on the account dashboard or orders page.
        max_retries: 0
        engine: skyvern-1.0
      ```
    </CodeGroup>
  </Tab>
</Tabs>

### Search for jobs block

Navigate to the job search and find relevant positions based on the parsed resume.

<Tabs>
  <Tab title="Cloud UI">
    Add a **Navigation** block. Configure it as follows:

    * **URL**: Leave empty (continues from the current page after login)
    * **Goal**: "Search for a relevant job based on the parsed resume." The block will automatically reference the `parsed_resume_output` from the previous step.
  </Tab>

  <Tab title="API / SDK">
    <CodeGroup>
      ```json JSON theme={null}
      {
        "block_type": "navigation",
        "label": "search_for_job",
        "url": "",
        "title": "search_for_job",
        "engine": "skyvern-1.0",
        "navigation_goal": "Search for a relevant job based on the parsed resume: {{parsed_resume_output}}",
        "max_retries": 0
      }
      ```

      ```yaml YAML theme={null}
      - block_type: navigation
        label: search_for_job
        url: ""
        title: search_for_job
        engine: skyvern-1.0
        navigation_goal: "Search for a relevant job based on the parsed resume: {{parsed_resume_output}}"
        max_retries: 0
      ```
    </CodeGroup>
  </Tab>
</Tabs>

### Extract jobs list block

Extract job postings from the search results. The `data_schema` tells Skyvern exactly what structure to return.

<Tabs>
  <Tab title="Cloud UI">
    Add an **Extraction** block. Configure it as follows:

    * **Goal**: "Extract all visible job profiles: job page url, title, employer, location, and type (full time, part time, etc)"
    * **Data Schema**: Paste the following JSON schema:

    ```json theme={null}
    {
      "jobs": {
        "type": "array",
        "items": {
          "type": "object",
          "properties": {
            "job_page_url": { "type": "string", "description": "Link to the apply page for the job" },
            "title": { "type": "string", "description": "Title of the role offered" },
            "employer": { "type": "string", "description": "Name of the company posting the job profile" },
            "location": { "type": "string", "description": "Where is the job role based. City and country," },
            "type": { "type": "string", "description": "Type for employment: full-time. part-time, contractual, internship etc." }
          },
          "required": ["job_page_url", "title", "employer", "location"]
        }
      }
    }
    ```
  </Tab>

  <Tab title="API / SDK">
    <CodeGroup>
      ```json JSON theme={null}
      {
        "block_type": "extraction",
        "label": "jobs_list_extraction",
        "url": "",
        "title": "jobs_list_extraction",
        "data_extraction_goal": "Extract all visible job profiles: job page url, title, employer, location, and type (full time, part time, etc)",
        "data_schema": {
          "jobs": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "job_page_url": {
                  "type": "string",
                  "description": "Link to the apply page for the job"
                },
                "title": {
                  "type": "string",
                  "description": "Title of the role offered"
                },
                "employer": {
                  "type": "string",
                  "description": "Name of the company posting the job profile"
                },
                "location": {
                  "type": "string",
                  "description": "Where is the job role based. City and country,"
                },
                "type": {
                  "type": "string",
                  "description": "Type for employment: full-time. part-time, contractual, internship etc."
                }
              },
              "required": ["job_page_url", "title", "employer", "location"]
            }
          }
        },
        "max_retries": 0,
        "engine": "skyvern-1.0"
      }
      ```

      ```yaml YAML theme={null}
      - block_type: extraction
        label: jobs_list_extraction
        url: ""
        title: jobs_list_extraction
        data_extraction_goal: "Extract all visible job profiles: job page url, title, employer, location, and type (full time, part time, etc)"
        data_schema:
          jobs:
            type: array
            items:
              type: object
              properties:
                job_page_url:
                  type: string
                  description: Link to the apply page for the job
                title:
                  type: string
                  description: Title of the role offered
                employer:
                  type: string
                  description: Name of the company posting the job profile
                location:
                  type: string
                  description: Where is the job role based. City and country,
                type:
                  type: string
                  description: "Type for employment: full-time. part-time, contractual, internship etc."
              required:
                - job_page_url
                - title
                - employer
                - location
        max_retries: 0
        engine: skyvern-1.0
      ```
    </CodeGroup>
  </Tab>
</Tabs>

The output is accessible as `jobs_list_extraction_output.jobs` in subsequent blocks.

### Apply to jobs loop

Iterate over each job posting and complete the application. This loop contains multiple blocks that work together:

1. **goto\_url**: Navigate to the job page
2. **extraction**: Extract detailed job information
3. **action**: Click the Apply button
4. **extraction**: Extract application form questions
5. **text\_prompt**: Generate tailored answers using AI
6. **navigation**: Fill out and submit the application

<Tabs>
  <Tab title="Cloud UI">
    Add a **For Loop** block. Set the **Loop Variable** to `jobs_list_extraction_output.jobs`. Enable **Continue on Failure** and **Next Loop on Failure**.

    Inside the loop, add the following blocks in order:

    <Steps>
      <Step title="Go to URL block">
        Add a **Go to URL** block. Set **URL** to the `current_value.job_page_url` variable.
      </Step>

      <Step title="Extract job details">
        Add an **Extraction** block. Set **Goal** to: "Extract every detail about the job role present on the page."
      </Step>

      <Step title="Click Apply">
        Add an **Action** block. Set **Goal** to: "Find Apply button and click it. COMPLETE when the job application is visible on the screen."
      </Step>

      <Step title="Extract form questions">
        Add an **Extraction** block. Set **Goal** to: "Extract every question in the job application form as a list."
      </Step>

      <Step title="Generate answers">
        Add a **Text Prompt** block. Set the **Prompt** to reference the parsed resume, job details, and extracted questions so the AI generates tailored answers for each field.
      </Step>

      <Step title="Fill and submit">
        Add a **Navigation** block. Set **Goal** to: "Fill out all of the form fields using the generated answers, including optional fields. COMPLETE when the application form has been successfully submitted."
      </Step>
    </Steps>
  </Tab>

  <Tab title="API / SDK">
    <CodeGroup>
      ```json JSON theme={null}
      {
        "block_type": "for_loop",
        "label": "for_each_job",
        "loop_variable_reference": "{{jobs_list_extraction_output.jobs}}",
        "continue_on_failure": true,
        "next_loop_on_failure": true,
        "complete_if_empty": true,
        "loop_blocks": [
          {
            "block_type": "goto_url",
            "label": "go_to_each_job",
            "url": "{{ current_value.job_page_url }}"
          },
          {
            "block_type": "extraction",
            "label": "extract_job_detail",
            "url": "",
            "title": "extract_job_detail",
            "data_extraction_goal": "Extract every detail about the job role present on the page",
            "max_retries": 0,
            "engine": "skyvern-1.0"
          },
          {
            "block_type": "action",
            "label": "click_apply",
            "url": "",
            "title": "click_apply",
            "navigation_goal": "Find Apply button and click it.\n\nCOMPLETE when the job application is visible on the screen",
            "max_retries": 0,
            "engine": "skyvern-1.0"
          },
          {
            "block_type": "extraction",
            "label": "extract_questions",
            "url": "",
            "title": "extract_questions",
            "data_extraction_goal": "Extract every question in the job application form as a list",
            "max_retries": 0,
            "engine": "skyvern-1.0"
          },
          {
            "block_type": "text_prompt",
            "label": "answer_form_questions",
            "prompt": "Given:\nresume: {{resume}}\nparsed resume: {{parsed_resume_output}}\ninformation about the job profile: {{extract_job_detail_output}}\napplication form questions: {{extract_questions_output}}\n\nYou are applying for {{ current_value.title }} at {{ current_value.employer }}.\n\nWrite thoughtful and impressive answers to each question using the information from resume and parsed resume output."
          },
          {
            "block_type": "navigation",
            "label": "apply_to_job",
            "url": "",
            "title": "apply_to_job",
            "engine": "skyvern-1.0",
            "navigation_goal": "Given:\n{{answer_form_questions_output}}\n\nFill out all of the form fields, including the optional fields.\n\nIf you dont know the answer to an optional question, leave it blank. If you dont know the answer to a required question such as referral name put N/A or something equivalent.\n\nCOMPLETE when the application form has been successfully submitted.\n\nMore context:\nresume: {{resume}}\nperson_information: {{parsed_resume_output}}",
            "max_retries": 0
          }
        ]
      }
      ```

      ```yaml YAML theme={null}
      - block_type: for_loop
        label: for_each_job
        loop_variable_reference: "{{jobs_list_extraction_output.jobs}}"
        continue_on_failure: true
        next_loop_on_failure: true
        complete_if_empty: true
        loop_blocks:
          - block_type: goto_url
            label: go_to_each_job
            url: "{{ current_value.job_page_url }}"

          - block_type: extraction
            label: extract_job_detail
            url: ""
            title: extract_job_detail
            data_extraction_goal: Extract every detail about the job role present on the page
            max_retries: 0
            engine: skyvern-1.0

          - block_type: action
            label: click_apply
            url: ""
            title: click_apply
            navigation_goal: |
              Find Apply button and click it.

              COMPLETE when the job application is visible on the screen
            max_retries: 0
            engine: skyvern-1.0

          - block_type: extraction
            label: extract_questions
            url: ""
            title: extract_questions
            data_extraction_goal: Extract every question in the job application form as a list
            max_retries: 0
            engine: skyvern-1.0

          - block_type: text_prompt
            label: answer_form_questions
            prompt: |
              Given:
              resume: {{resume}}
              parsed resume: {{parsed_resume_output}}
              information about the job profile: {{extract_job_detail_output}}
              application form questions: {{extract_questions_output}}

              You are applying for {{ current_value.title }} at {{ current_value.employer }}.

              Write thoughtful and impressive answers to each question using the information from resume and parsed resume output.

          - block_type: navigation
            label: apply_to_job
            url: ""
            title: apply_to_job
            engine: skyvern-1.0
            navigation_goal: |
              Given:
              {{answer_form_questions_output}}

              Fill out all of the form fields, including the optional fields.

              If you dont know the answer to an optional question, leave it blank. If you dont know the answer to a required question such as referral name put N/A or something equivalent.

              COMPLETE when the application form has been successfully submitted.

              More context:
              resume: {{resume}}
              person_information: {{parsed_resume_output}}
            max_retries: 0
      ```
    </CodeGroup>
  </Tab>
</Tabs>

**Key pattern:** `continue_on_failure: true` ensures that if one application fails, the agent continues to the next job. Inside the loop, `current_value` gives you the current job being processed.

***

## Complete agent definition

<Tabs>
  <Tab title="Cloud UI">
    Here's a summary of the complete agent you've built in the visual editor:

    <Steps>
      <Step title="Create the agent">
        Go to **Agents** → **Create Agent**. Name it "Search and Apply for Jobs Workflow." On the **Start** node, set **Proxy Location** to **Residential** and add the parameters: `resume`, `credentials`, and `job_portal_url`.
      </Step>

      <Step title="Block 1: PDF Parser">
        Add a **PDF Parser** block. Set **File URL** to the `resume` parameter.
      </Step>

      <Step title="Block 2: Login">
        Add a **Login** block. Set **URL** to the `job_portal_url` parameter. Select the `credentials` parameter. Set the goal: "Log in using the provided credentials. Handle any cookie consent popups. COMPLETE when on the account dashboard or orders page."
      </Step>

      <Step title="Block 3: Navigation (search for jobs)">
        Add a **Navigation** block. Leave **URL** empty. Set the goal: "Search for a relevant job based on the parsed resume."
      </Step>

      <Step title="Block 4: Extraction (job listings)">
        Add an **Extraction** block. Set the goal: "Extract all visible job profiles: job page url, title, employer, location, and type." Paste the jobs JSON schema into **Data Schema**.
      </Step>

      <Step title="Block 5: For Loop (apply to each job)">
        Add a **For Loop** block. Set **Loop Variable** to `jobs_list_extraction_output.jobs`. Enable **Continue on Failure** and **Next Loop on Failure**. Inside the loop, add six blocks in order: **Go to URL** (job page), **Extraction** (job details), **Action** (click Apply), **Extraction** (form questions), **Text Prompt** (generate answers), **Navigation** (fill and submit the form).
      </Step>
    </Steps>
  </Tab>

  <Tab title="API / SDK">
    Save this complete definition to `job-application-workflow.yaml` (or `.json`) before running.

    <CodeGroup>
      ```json JSON theme={null}
      {
        "title": "Search and Apply for Jobs Workflow",
        "description": "Search for jobs on any portal, extract listings, generate tailored answers with AI, and submit applications automatically.",
        "proxy_location": "RESIDENTIAL",
        "workflow_definition": {
          "version": 1,
          "parameters": [
            { "key": "resume", "parameter_type": "workflow", "workflow_parameter_type": "file_url" },
            { "key": "credentials", "parameter_type": "workflow", "workflow_parameter_type": "credential_id", "default_value": "your-credential-id" },
            { "key": "job_portal_url", "description": "URL of the job portal", "parameter_type": "workflow", "workflow_parameter_type": "string" }
          ],
          "blocks": [
            {
              "block_type": "pdf_parser",
              "label": "parsed_resume",
              "file_url": "{{ resume }}"
            },
            {
              "block_type": "login",
              "label": "login_to_portal",
              "url": "{{ job_portal_url }}",
              "title": "login_to_portal",
              "parameter_keys": ["credentials"],
              "navigation_goal": "Log in using the provided credentials.\nHandle any cookie consent popups.\nCOMPLETE when on the account dashboard or orders page.",
              "max_retries": 0,
              "engine": "skyvern-1.0"
            },
            {
              "block_type": "navigation",
              "label": "search_for_job",
              "url": "",
              "title": "search_for_job",
              "engine": "skyvern-1.0",
              "navigation_goal": "Search for a relevant job based on the parsed resume: {{parsed_resume_output}}",
              "max_retries": 0
            },
            {
              "block_type": "extraction",
              "label": "jobs_list_extraction",
              "url": "",
              "title": "jobs_list_extraction",
              "data_extraction_goal": "Extract all visible job profiles: job page url, title, employer, location, and type (full time, part time, etc)",
              "data_schema": {
                "jobs": {
                  "type": "array",
                  "items": {
                    "type": "object",
                    "properties": {
                      "job_page_url": { "type": "string", "description": "Link to the apply page for the job" },
                      "title": { "type": "string", "description": "Title of the role offered" },
                      "employer": { "type": "string", "description": "Name of the company posting the job profile" },
                      "location": { "type": "string", "description": "Where is the job role based. City and country," },
                      "type": { "type": "string", "description": "Type for employment: full-time. part-time, contractual, internship etc." }
                    },
                    "required": ["job_page_url", "title", "employer", "location"]
                  }
                }
              },
              "max_retries": 0,
              "engine": "skyvern-1.0"
            },
            {
              "block_type": "for_loop",
              "label": "for_each_job",
              "loop_variable_reference": "{{jobs_list_extraction_output.jobs}}",
              "continue_on_failure": true,
              "next_loop_on_failure": true,
              "complete_if_empty": true,
              "loop_blocks": [
                {
                  "block_type": "goto_url",
                  "label": "go_to_each_job",
                  "url": "{{ current_value.job_page_url }}"
                },
                {
                  "block_type": "extraction",
                  "label": "extract_job_detail",
                  "url": "",
                  "title": "extract_job_detail",
                  "data_extraction_goal": "Extract every detail about the job role present on the page",
                  "max_retries": 0,
                  "engine": "skyvern-1.0"
                },
                {
                  "block_type": "action",
                  "label": "click_apply",
                  "url": "",
                  "title": "click_apply",
                  "navigation_goal": "Find Apply button and click it.\n\nCOMPLETE when the job application is visible on the screen",
                  "max_retries": 0,
                  "engine": "skyvern-1.0"
                },
                {
                  "block_type": "extraction",
                  "label": "extract_questions",
                  "url": "",
                  "title": "extract_questions",
                  "data_extraction_goal": "Extract every question in the job application form as a list",
                  "max_retries": 0,
                  "engine": "skyvern-1.0"
                },
                {
                  "block_type": "text_prompt",
                  "label": "answer_form_questions",
                  "prompt": "Given:\nresume: {{resume}}\nparsed resume: {{parsed_resume_output}}\ninformation about the job profile: {{extract_job_detail_output}}\napplication form questions: {{extract_questions_output}}\n\nYou are applying for {{ current_value.title }} at {{ current_value.employer }}.\n\nWrite thoughtful and impressive answers to each question using the information from resume and parsed resume output."
                },
                {
                  "block_type": "navigation",
                  "label": "apply_to_job",
                  "url": "",
                  "title": "apply_to_job",
                  "engine": "skyvern-1.0",
                  "navigation_goal": "Given:\n{{answer_form_questions_output}}\n\nFill out all of the form fields, including the optional fields.\n\nIf you dont know the answer to an optional question, leave it blank. If you dont know the answer to a required question such as referral name put N/A or something equivalent.\n\nCOMPLETE when the application form has been successfully submitted.\n\nMore context:\nresume: {{resume}}\nperson_information: {{parsed_resume_output}}",
                  "max_retries": 0
                }
              ]
            }
          ]
        }
      }
      ```

      ```yaml YAML theme={null}
      title: Search and Apply for Jobs Workflow
      description: "Search for jobs on any portal, extract listings, generate tailored answers with AI, and submit applications automatically."
      proxy_location: RESIDENTIAL
      workflow_definition:
        version: 1
        parameters:
          - key: resume
            parameter_type: workflow
            workflow_parameter_type: file_url
          - key: credentials
            parameter_type: workflow
            workflow_parameter_type: credential_id
            default_value: your-credential-id # <-- replace this
          - key: job_portal_url
            description: URL of the job portal
            parameter_type: workflow
            workflow_parameter_type: string

        blocks:
          - block_type: pdf_parser
            label: parsed_resume
            file_url: "{{ resume }}"

          - block_type: login
            label: login_to_portal
            url: "{{ job_portal_url }}"
            title: login_to_portal
            parameter_keys:
              - credentials
            navigation_goal: |
              Log in using the provided credentials.
              Handle any cookie consent popups.
              COMPLETE when on the account dashboard or orders page.
            max_retries: 0
            engine: skyvern-1.0

          - block_type: navigation
            label: search_for_job
            url: ""
            title: search_for_job
            engine: skyvern-1.0
            navigation_goal: "Search for a relevant job based on the parsed resume: {{parsed_resume_output}}"
            max_retries: 0

          - block_type: extraction
            label: jobs_list_extraction
            url: ""
            title: jobs_list_extraction
            data_extraction_goal: "Extract all visible job profiles: job page url, title, employer, location, and type (full time, part time, etc)"
            data_schema:
              jobs:
                type: array
                items:
                  type: object
                  properties:
                    job_page_url:
                      type: string
                      description: Link to the apply page for the job
                    title:
                      type: string
                      description: Title of the role offered
                    employer:
                      type: string
                      description: Name of the company posting the job profile
                    location:
                      type: string
                      description: Where is the job role based. City and country,
                    type:
                      type: string
                      description: "Type for employment: full-time. part-time, contractual, internship etc."
                  required:
                    - job_page_url
                    - title
                    - employer
                    - location
            max_retries: 0
            engine: skyvern-1.0

          - block_type: for_loop
            label: for_each_job
            loop_variable_reference: "{{jobs_list_extraction_output.jobs}}"
            continue_on_failure: true
            next_loop_on_failure: true
            complete_if_empty: true
            loop_blocks:
              - block_type: goto_url
                label: go_to_each_job
                url: "{{ current_value.job_page_url }}"

              - block_type: extraction
                label: extract_job_detail
                url: ""
                title: extract_job_detail
                data_extraction_goal: Extract every detail about the job role present on the page
                max_retries: 0
                engine: skyvern-1.0

              - block_type: action
                label: click_apply
                url: ""
                title: click_apply
                navigation_goal: |
                  Find Apply button and click it.

                  COMPLETE when the job application is visible on the screen
                max_retries: 0
                engine: skyvern-1.0

              - block_type: extraction
                label: extract_questions
                url: ""
                title: extract_questions
                data_extraction_goal: Extract every question in the job application form as a list
                max_retries: 0
                engine: skyvern-1.0

              - block_type: text_prompt
                label: answer_form_questions
                prompt: |
                  Given:
                  resume: {{resume}}
                  parsed resume: {{parsed_resume_output}}
                  information about the job profile: {{extract_job_detail_output}}
                  application form questions: {{extract_questions_output}}

                  You are applying for {{ current_value.title }} at {{ current_value.employer }}.

                  Write thoughtful and impressive answers to each question using the information from resume and parsed resume output.

              - block_type: navigation
                label: apply_to_job
                url: ""
                title: apply_to_job
                engine: skyvern-1.0
                navigation_goal: |
                  Given:
                  {{answer_form_questions_output}}

                  Fill out all of the form fields, including the optional fields.

                  If you dont know the answer to an optional question, leave it blank. If you dont know the answer to a required question such as referral name put N/A or something equivalent.

                  COMPLETE when the application form has been successfully submitted.

                  More context:
                  resume: {{resume}}
                  person_information: {{parsed_resume_output}}
                max_retries: 0
      ```
    </CodeGroup>
  </Tab>
</Tabs>

***

## Step 4: Run and monitor

Create the agent from your definition file and execute it.

<Tabs>
  <Tab title="Cloud UI">
    <Steps>
      <Step title="Run the agent">
        Click **Run** in the agent editor. Fill in the parameters:

        * **resume**: URL to your resume PDF
        * **job\_portal\_url**: `https://job-stash.vercel.app`
        * **credentials**: Select the `Job Stash` credential
      </Step>

      <Step title="Monitor the run">
        Watch the run in real time. Each block shows its status as it executes. The for loop will iterate over each job posting, and you can see the AI filling out application forms in the browser recording.
      </Step>
    </Steps>
  </Tab>

  <Tab title="API / SDK">
    <CodeGroup>
      ```python Python theme={null}
      import os
      import asyncio
      from skyvern import Skyvern

      async def main():
          client = Skyvern(api_key=os.getenv("SKYVERN_API_KEY"))

          # Create workflow from YAML file
          workflow = await client.create_workflow(
              yaml_definition=open("job-application-workflow.yaml").read()
          )
          print(f"Created workflow: {workflow.workflow_permanent_id}")

          # Run the workflow
          run = await client.run_workflow(
              workflow_id=workflow.workflow_permanent_id,
              parameters={
                  "resume": "https://your-resume-url.com/resume.pdf",  # <-- replace this
                  "job_portal_url": "https://job-stash.vercel.app"
              }
          )
          print(f"Started run: {run.run_id}")

          # Poll for completion
          while True:
              result = await client.get_run(run.run_id)
              if result.status in ["completed", "failed", "terminated"]:
                  break
              print(f"Status: {result.status}")
              await asyncio.sleep(10)

          print(f"Final status: {result.status}")
          if result.status == "completed":
              print("Job applications submitted successfully")

      asyncio.run(main())
      ```

      ```typescript TypeScript theme={null}
      import { SkyvernClient } from "@skyvern/client";
      import * as fs from "fs";

      async function main() {
        const client = new SkyvernClient({
          apiKey: process.env.SKYVERN_API_KEY,
        });

        // Create workflow from YAML file
        const workflow = await client.createWorkflow({
          body: {
            yaml_definition: fs.readFileSync("job-application-workflow.yaml", "utf-8"),
          },
        });
        console.log(`Created workflow: ${workflow.workflow_permanent_id}`);

        // Run the workflow
        const run = await client.runWorkflow({
          body: {
            workflow_id: workflow.workflow_permanent_id,
            parameters: {
              resume: "https://your-resume-url.com/resume.pdf", // <-- replace this
              job_portal_url: "https://job-stash.vercel.app",
            },
          },
        });
        console.log(`Started run: ${run.run_id}`);

        // Poll for completion
        while (true) {
          const result = await client.getRun(run.run_id);
          if (["completed", "failed", "terminated"].includes(result.status)) {
            console.log(`Final status: ${result.status}`);
            if (result.status === "completed") {
              console.log("Job applications submitted successfully");
            }
            break;
          }
          console.log(`Status: ${result.status}`);
          await new Promise((r) => setTimeout(r, 10000));
        }
      }

      main();
      ```

      ```bash cURL theme={null}
      # Create workflow
      WORKFLOW=$(curl -s -X POST "https://api.skyvern.com/v1/workflows" \
        -H "x-api-key: $SKYVERN_API_KEY" \
        -H "Content-Type: application/json" \
        -d "{\"yaml_definition\": $(cat job-application-workflow.yaml | jq -Rs .)}")

      WORKFLOW_ID=$(echo "$WORKFLOW" | jq -r '.workflow_permanent_id')
      echo "Created workflow: $WORKFLOW_ID"

      # Run workflow (replace resume URL with your own)
      RUN=$(curl -s -X POST "https://api.skyvern.com/v1/run/workflows" \
        -H "x-api-key: $SKYVERN_API_KEY" \
        -H "Content-Type: application/json" \
        -d "{
          \"workflow_id\": \"$WORKFLOW_ID\",
          \"parameters\": {
            \"resume\": \"https://your-resume-url.com/resume.pdf\",
            \"job_portal_url\": \"https://job-stash.vercel.app\"
          }
        }")

      RUN_ID=$(echo "$RUN" | jq -r '.run_id')
      echo "Started run: $RUN_ID"

      # Poll for completion
      while true; do
        RESULT=$(curl -s "https://api.skyvern.com/v1/runs/$RUN_ID" \
          -H "x-api-key: $SKYVERN_API_KEY")
        STATUS=$(echo "$RESULT" | jq -r '.status')
        echo "Status: $STATUS"

        if [[ "$STATUS" == "completed" || "$STATUS" == "failed" || "$STATUS" == "terminated" ]]; then
          echo "Workflow finished with status: $STATUS"
          break
        fi
        sleep 10
      done
      ```
    </CodeGroup>
  </Tab>
</Tabs>

***

## Resources

<CardGroup cols={2}>
  <Card title="Agent Blocks Reference" icon="cube" href="/cloud/building-agents/configure-blocks">
    Complete parameter reference for all block types
  </Card>

  <Card title="Credential Management" icon="key" href="/sdk-reference/credentials/create-credential">
    Securely store and use login credentials
  </Card>

  <Card title="File Operations" icon="file" href="/cloud/building-agents/configure-blocks">
    Upload and parse files in agents
  </Card>

  <Card title="Error Handling" icon="triangle-exclamation" href="/developers/going-to-production/error-handling">
    Handle failures and retries in production
  </Card>
</CardGroup>
