DOC_INDEX
THEME
DOCS/Example Cookbook/HTTP Integration Examples

HTTP Integration Examples

OrchStep examples for HTTP requests, authentication, and batch operations


advanced patterns

Advanced HTTP Patterns

# Example: Advanced HTTP Patterns
# Shows query parameters, custom headers, body from variables,
# timeouts, TLS settings, and complex JSON payloads.
#
# Key patterns:
#   query: { k: v }          - Structured query parameters
#   headers: { K: V }        - Custom request headers
#   body: "@vars.my_object"  - Send a variable as the request body
#   body_json: "{{ ... }}"   - Template-rendered JSON string
#   timeout: 5s              - Request timeout
#   insecure: true           - Skip TLS verification
#
# Try: orchstep run query-params
# Try: orchstep run body-from-variables
# Try: orchstep run complex-payload

name: advanced-http-patterns-demo
desc: "Query params, headers, body from vars, and complex payloads"

defaults:
  api_base: "https://httpbin.org"

tasks:
  # -- Query parameters --
  query-params:
    desc: "Send structured query parameters"
    steps:
      # Method 1: Query parameters in args
      - name: structured_query
        func: http
        args:
          url: "{{ vars.api_base }}/get"
          method: GET
          query:
            page: "1"
            limit: "25"
            filter: "active"
            sort: "created_at"
        outputs:
          status: "{{ result.status_code }}"
          page: "{{ result.data_object.args.page }}"

      # Method 2: Query parameters in URL
      - name: url_query
        func: http
        do: "GET {{ vars.api_base }}/get?foo=bar&baz=qux"
        outputs:
          foo: "{{ result.data_object.args.foo }}"

      # Method 3: Mix URL params with structured params
      - name: mixed_query
        func: http
        args:
          url: "{{ vars.api_base }}/get?existing=value"
          method: GET
          query:
            additional: "param"
        outputs:
          existing: "{{ result.data_object.args.existing }}"
          additional: "{{ result.data_object.args.additional }}"

  # -- Template variables in headers and query --
  templated-request:
    desc: "Use variables in headers, query, and body"
    vars:
      app_version: "2.5.0"
      request_id: "req-abc-123"
      user_id: "12345"
      user_status: "active"
    steps:
      - name: templated_call
        func: http
        args:
          url: "{{ vars.api_base }}/post"
          method: POST
          headers:
            X-App-Version: "{{ vars.app_version }}"
            X-Request-ID: "{{ vars.request_id }}"
          query:
            user: "{{ vars.user_id }}"
            status: "{{ vars.user_status }}"
          body:
            name: "Widget Pro"
            price: "99.99"
        outputs:
          status: "{{ result.status_code }}"

  # -- Body from variables (three approaches) --
  body-from-variables:
    desc: "Build request body from workflow variables"
    vars:
      user_name: "Alice Johnson"
      user_email: "alice@example.com"
    steps:
      # Approach 1: @vars reference - pass a variable object directly
      - name: direct_object_ref
        func: http
        vars:
          request_body:
            name: "{{ vars.user_name }}"
            email: "{{ vars.user_email }}"
            role: "developer"
        args:
          url: "{{ vars.api_base }}/post"
          method: POST
          body: "@vars.request_body"
        outputs:
          status: "{{ result.status_code }}"

      # Approach 2: body_json with template string
      - name: json_template
        func: http
        vars:
          json_body: |
            {
              "name": "{{ vars.user_name }}",
              "email": "{{ vars.user_email }}",
              "department": "engineering"
            }
        args:
          url: "{{ vars.api_base }}/post"
          method: POST
          body_json: "{{ vars.json_body }}"
        outputs:
          status: "{{ result.status_code }}"

      # Approach 3: Build body using step outputs
      - name: get_user_id
        func: shell
        do: echo "usr-67890"
        outputs:
          user_id: "{{ result.output | trim }}"

      - name: post_with_step_data
        func: http
        vars:
          payload:
            user_id: "{{ steps.get_user_id.user_id }}"
            name: "{{ vars.user_name }}"
            timestamp: "2024-06-15T10:00:00Z"
        args:
          url: "{{ vars.api_base }}/post"
          method: POST
          body: "@vars.payload"
        outputs:
          status: "{{ result.status_code }}"

  # -- Complex nested JSON payload --
  complex-payload:
    desc: "Send deeply nested JSON structures"
    steps:
      - name: create_order
        func: http
        args:
          url: "{{ vars.api_base }}/post"
          method: POST
          body:
            customer:
              id: 123
              name: "John Doe"
              email: "john@example.com"
            items:
              - id: 1
                name: "Widget"
                quantity: 5
              - id: 2
                name: "Gadget"
                quantity: 3
            metadata:
              created_at: "2024-01-01T00:00:00Z"
              tags: ["urgent", "wholesale"]
        outputs:
          status: "{{ result.status_code }}"
          customer_id: "{{ result.data_object.json.customer.id }}"
          first_item: '{{ (index result.data_object.json.items 0).name }}'

  # -- Request timeout --
  request-timeout:
    desc: "Set timeout for HTTP requests"
    steps:
      - name: fast_request
        func: http
        args:
          url: "{{ vars.api_base }}/delay/1"
          method: GET
          timeout: 5s       # Request must complete within 5 seconds
        outputs:
          status: "{{ result.status_code }}"

  # -- TLS settings --
  tls-options:
    desc: "Control TLS verification for internal services"
    steps:
      - name: secure_request
        func: http
        args:
          url: "{{ vars.api_base }}/get"
          method: GET
          insecure: false    # Default: verify TLS certificates
        outputs:
          status: "{{ result.status_code }}"

authentication

HTTP Authentication Patterns

# Example: HTTP Authentication Patterns
# Shows how to authenticate HTTP requests with different methods:
#
#   Bearer token - auth: { type: bearer, token: "..." }
#   Basic auth   - auth: { type: basic, username: "...", password: "..." }
#   API key      - auth: { type: apikey, key: "...", in: header|query, name: "..." }
#
# Tokens and credentials should come from variables or environment
# variables -- never hardcode secrets in workflow files.
#
# Try: orchstep run bearer-auth
# Try: orchstep run api-key-auth

name: authentication-demo
desc: "Bearer, Basic, and API Key authentication patterns"

defaults:
  api_base: "https://httpbin.org"

tasks:
  # -- Bearer token authentication --
  bearer-auth:
    desc: "Authenticate with a Bearer token (e.g., OAuth2, JWT)"
    vars:
      # In real usage: orchstep run bearer-auth --var api_token=your-token
      api_token: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.example"
    steps:
      - name: call_protected_api
        func: http
        args:
          url: "{{ vars.api_base }}/bearer"
          method: GET
          auth:
            type: bearer
            token: "{{ vars.api_token }}"
        outputs:
          status: "{{ result.status_code }}"
          authenticated: "{{ result.data_object.authenticated }}"

      - name: show_result
        func: shell
        do: |
          echo "Status: {{ steps.call_protected_api.status }}"
          echo "Authenticated: {{ steps.call_protected_api.authenticated }}"

  # -- Basic authentication --
  basic-auth:
    desc: "Authenticate with username and password"
    vars:
      # In real usage: pass via --var or environment variables
      username: "admin"
      password: "secret"
    steps:
      - name: login_to_service
        func: http
        args:
          url: "{{ vars.api_base }}/basic-auth/{{ vars.username }}/{{ vars.password }}"
          method: GET
          auth:
            type: basic
            username: "{{ vars.username }}"
            password: "{{ vars.password }}"
        outputs:
          status: "{{ result.status_code }}"
          authenticated: "{{ result.data_object.authenticated }}"
          user: "{{ result.data_object.user }}"

  # -- API key in header --
  api-key-auth:
    desc: "Authenticate with an API key sent in a header"
    vars:
      # In real usage: orchstep run api-key-auth --var api_key=your-key
      api_key: "sk-prod-abc123def456"
    steps:
      - name: call_with_api_key
        func: http
        args:
          url: "{{ vars.api_base }}/headers"
          method: GET
          auth:
            type: apikey
            key: "{{ vars.api_key }}"
            in: header              # Send key as a header
            name: "X-API-Key"      # Header name
        outputs:
          status: "{{ result.status_code }}"
          api_key_sent: '{{ index result.data_object.headers "X-Api-Key" }}'

  # -- API key in query parameter --
  api-key-query:
    desc: "Authenticate with an API key in the URL query string"
    vars:
      api_key: "pk-query-789xyz"
    steps:
      - name: call_with_query_key
        func: http
        args:
          url: "{{ vars.api_base }}/get"
          method: GET
          auth:
            type: apikey
            key: "{{ vars.api_key }}"
            in: query               # Send key as a query parameter
            name: "api_key"         # Query parameter name
        outputs:
          status: "{{ result.status_code }}"
          api_key_param: "{{ result.data_object.args.api_key }}"

  # -- Combine auth with custom headers --
  auth-with-custom-headers:
    desc: "Use API key auth alongside additional custom headers"
    steps:
      - name: enriched_request
        func: http
        args:
          url: "{{ vars.api_base }}/headers"
          method: GET
          headers:
            X-Request-ID: "req-999-abc"
            X-Client-Version: "3.0.0"
          auth:
            type: apikey
            key: "my-api-key-123"
            in: header
            name: "X-API-Key"
        outputs:
          status: "{{ result.status_code }}"
          request_id: '{{ index result.data_object.headers "X-Request-Id" }}'
          api_key: '{{ index result.data_object.headers "X-Api-Key" }}'

basic get request

Basic HTTP GET Requests

# Example: Basic HTTP GET Requests
# Shows how to make GET requests with the built-in http function.
#
# The http function supports two syntaxes:
#   - Short form:  do: "GET https://api.example.com/data"
#   - Args form:   args: { url: "...", method: GET }
#
# Response data is available via:
#   result.status_code   - HTTP status code (200, 404, etc.)
#   result.body          - Raw response body as string
#   result.data_object   - Auto-parsed JSON response (if JSON)
#   result.headers       - Response headers map
#   result.url           - Requested URL
#   result.method        - HTTP method used
#
# Try: orchstep run fetch-api-data
# Try: orchstep run get-with-variables

name: basic-get-request-demo
desc: "HTTP GET requests with variable URLs and response parsing"

defaults:
  api_base: "https://httpbin.org"

tasks:
  # -- Simple GET request --
  fetch-api-data:
    desc: "Fetch data from a REST API"
    steps:
      - name: get_users
        func: http
        do: "GET {{ vars.api_base }}/get"
        outputs:
          status: "{{ result.status_code }}"
          url: "{{ result.url }}"
          method: "{{ result.method }}"

      - name: show_result
        func: shell
        do: |
          echo "Status: {{ steps.get_users.status }}"
          echo "URL: {{ steps.get_users.url }}"
          echo "Method: {{ steps.get_users.method }}"

  # -- GET with template variables in the URL --
  get-with-variables:
    desc: "Build URLs dynamically using variables"
    vars:
      resource: "users"
      api_version: "v2"
    steps:
      - name: fetch_resource
        func: http
        do: "GET {{ vars.api_base }}/{{ vars.resource }}"
        outputs:
          status: "{{ result.status_code }}"

  # -- GET using args syntax --
  get-args-syntax:
    desc: "Use explicit args instead of the do shorthand"
    steps:
      - name: fetch_with_args
        func: http
        args:
          url: "{{ vars.api_base }}/get"
          method: GET
        outputs:
          status: "{{ result.status_code }}"

  # -- Parse JSON response --
  parse-json-response:
    desc: "Access fields from a JSON response body"
    steps:
      - name: get_json_data
        func: http
        do: "GET {{ vars.api_base }}/json"
        outputs:
          # Auto-parsed JSON fields via result.data_object
          author: "{{ result.data_object.slideshow.author }}"
          title: "{{ result.data_object.slideshow.title }}"

      - name: display_parsed_data
        func: shell
        do: |
          echo "Author: {{ steps.get_json_data.author }}"
          echo "Title: {{ steps.get_json_data.title }}"

  # -- Access response headers --
  read-response-headers:
    desc: "Read headers from the HTTP response"
    steps:
      - name: get_with_headers
        func: http
        do: "GET {{ vars.api_base }}/response-headers?X-Custom=hello"
        outputs:
          content_type: '{{ index result.headers "Content-Type" }}'
          custom_header: '{{ index result.headers "X-Custom" }}'

      - name: show_headers
        func: shell
        do: |
          echo "Content-Type: {{ steps.get_with_headers.content_type }}"
          echo "X-Custom: {{ steps.get_with_headers.custom_header }}"

uatch requests

Batch HTTP Requests with Loops

# Example: Batch HTTP Requests with Loops
# Shows how to make multiple HTTP requests using loop,
# with data transformation via map_in and map_out.
#
# map_in:  JavaScript that runs BEFORE the request to prepare data
# map_out: JavaScript that runs AFTER the response to transform results
# loop:    Iterate over a list, making one request per item
#
# In map_in/map_out, use:
#   spec.<name> = value  - Set a variable for use in the request/outputs
#   vars.<name>          - Read workflow variables
#   result.*             - (map_out only) Access the HTTP response
#
# Try: orchstep run batch-create
# Try: orchstep run process-products

name: batch-http-requests-demo
desc: "HTTP requests with loops, map_in, and map_out"

defaults:
  api_base: "https://httpbin.org"

tasks:
  # -- Loop over items for batch requests --
  batch-create:
    desc: "Create multiple resources in a batch"
    vars:
      resources:
        - { id: 1, action: "create", name: "Web Server" }
        - { id: 2, action: "create", name: "Database" }
        - { id: 3, action: "create", name: "Cache Layer" }
    steps:
      - name: create_resources
        func: http
        loop:
          items: "{{ vars.resources }}"
          as: resource
        args:
          url: "{{ vars.api_base }}/post"
          method: POST
          body:
            id: "{{ loop.resource.id }}"
            action: "{{ loop.resource.action }}"
            name: "{{ loop.resource.name }}"
        outputs:
          status: "{{ result.status_code }}"

  # -- map_in: Transform data before sending --
  computed-request:
    desc: "Use map_in to compute values before the request"
    vars:
      first_name: "John"
      last_name: "Doe"
    steps:
      - name: build_and_send
        func: http
        map_in: |
          // Compute derived fields in JavaScript
          spec.full_name = vars.first_name + " " + vars.last_name;
          spec.username = vars.first_name.toLowerCase() + vars.last_name.toLowerCase();
        args:
          url: "{{ vars.api_base }}/post"
          method: POST
          body:
            name: "{{ vars.full_name }}"
            username: "{{ vars.username }}"
        outputs:
          posted_name: "{{ result.data_object.json.name }}"
          posted_username: "{{ result.data_object.json.username }}"

  # -- map_out: Transform the response --
  transform-response:
    desc: "Use map_out to extract and compute from response data"
    steps:
      - name: get_and_transform
        func: http
        args:
          url: "{{ vars.api_base }}/post"
          method: POST
          body:
            user:
              first_name: "Alice"
              last_name: "Johnson"
              age: 30
        map_out: |
          // Extract and transform response fields
          var user = result.data_object.json.user;
          spec.display_name = user.first_name + " " + user.last_name;
          spec.age_group = user.age < 18 ? "minor" : user.age < 65 ? "adult" : "senior";
          spec.initials = user.first_name.charAt(0) + user.last_name.charAt(0);
        outputs:
          display_name: "{{ vars.display_name }}"
          age_group: "{{ vars.age_group }}"
          initials: "{{ vars.initials }}"

  # -- Loop + map_in + map_out combined --
  process-products:
    desc: "Process a product catalog: compute totals per item"
    vars:
      products:
        - { name: "Widget", price: 9.99, quantity: 5 }
        - { name: "Gadget", price: 19.99, quantity: 3 }
        - { name: "Doohickey", price: 29.99, quantity: 2 }
    steps:
      - name: process_each_product
        func: http
        loop:
          items: "{{ vars.products }}"
          var: product
        map_in: |
          // Calculate line item total before sending
          spec.item_total = (vars.product.price * vars.product.quantity).toFixed(2);
          spec.item_name = vars.product.name.toUpperCase();
        args:
          url: "{{ vars.api_base }}/post"
          method: POST
          body:
            name: "{{ vars.item_name }}"
            price: "{{ vars.product.price }}"
            quantity: "{{ vars.product.quantity }}"
            total: "{{ vars.item_total }}"
        map_out: |
          // Build a summary line from the response
          var data = result.data_object.json;
          spec.summary = data.name + ": $" + data.total;
        outputs:
          summary: "{{ vars.summary }}"

  # -- Aggregate data from batch responses --
  aggregate-inventory:
    desc: "Post items and aggregate totals from responses"
    steps:
      - name: post_inventory
        func: http
        args:
          url: "{{ vars.api_base }}/post"
          method: POST
          body:
            items:
              - { name: "Apple", quantity: 5, price: 1.20 }
              - { name: "Banana", quantity: 3, price: 0.80 }
              - { name: "Orange", quantity: 7, price: 1.50 }
        map_out: |
          // Aggregate totals from the response
          var items = result.data_object.json.items;
          var totalCost = 0;
          var totalQty = 0;
          var names = [];

          for (var i = 0; i < items.length; i++) {
            totalCost += items[i].quantity * items[i].price;
            totalQty += items[i].quantity;
            names.push(items[i].name);
          }

          spec.total_cost = totalCost.toFixed(2);
          spec.total_quantity = totalQty;
          spec.item_list = names.join(", ");
        outputs:
          total_cost: "{{ vars.total_cost }}"
          total_qty: "{{ vars.total_quantity }}"
          items: "{{ vars.item_list }}"

      - name: show_totals
        func: shell
        do: |
          echo "Inventory Summary"
          echo "Items: {{ steps.post_inventory.items }}"
          echo "Total Quantity: {{ steps.post_inventory.total_qty }}"
          echo "Total Cost: ${{ steps.post_inventory.total_cost }}"

  # -- Loop with error handling --
  resilient-batch:
    desc: "Process batch with error handling per item"
    vars:
      endpoints:
        - "/post"
        - "/status/404"
        - "/post"
    steps:
      - name: call_endpoints
        func: http
        loop:
          items: "{{ vars.endpoints }}"
          as: path
          on_error: continue    # Continue loop even if one request fails
        args:
          url: "{{ vars.api_base }}{{ loop.path }}"
          method: GET
        outputs:
          status: "{{ result.status_code }}"

rest api methods

REST API Methods (POST, PUT, PATCH, DELETE)

# Example: REST API Methods (POST, PUT, PATCH, DELETE)
# Shows how to use all standard HTTP methods with JSON payloads.
#
# Body can be specified as:
#   body: { ... }         - YAML object (auto-serialized to JSON)
#   body: "text"          - Plain text body
#   body_json: '{"k":"v"}'- JSON string
#
# Try: orchstep run create-user
# Try: orchstep run crud-workflow

name: rest-api-methods-demo
desc: "POST, PUT, PATCH, DELETE with JSON bodies"

defaults:
  api_base: "https://httpbin.org"

tasks:
  # -- POST with JSON body --
  create-user:
    desc: "Create a resource with POST"
    steps:
      - name: create_user
        func: http
        args:
          url: "{{ vars.api_base }}/post"
          method: POST
          body:
            name: "Alice Johnson"
            email: "alice@example.com"
            role: "developer"
            active: true
        outputs:
          status: "{{ result.status_code }}"
          created_name: "{{ result.data_object.json.name }}"

      - name: confirm
        func: shell
        do: |
          echo "Created user: {{ steps.create_user.created_name }}"
          echo "Status: {{ steps.create_user.status }}"

  # -- POST with plain text body --
  post-plain-text:
    desc: "Send plain text content"
    steps:
      - name: send_log
        func: http
        args:
          url: "{{ vars.api_base }}/post"
          method: POST
          body: "2024-01-15T10:30:00Z [INFO] Deployment completed successfully"
          headers:
            Content-Type: "text/plain"
        outputs:
          status: "{{ result.status_code }}"

  # -- POST with JSON string body --
  post-json-string:
    desc: "Send a pre-formed JSON string"
    steps:
      - name: send_order
        func: http
        args:
          url: "{{ vars.api_base }}/post"
          method: POST
          body_json: '{"product": "OrchStep Pro", "quantity": 5, "price": 99.99}'
        outputs:
          status: "{{ result.status_code }}"
          product: "{{ result.data_object.json.product }}"

  # -- PUT to update a resource --
  update-user:
    desc: "Update an existing resource with PUT"
    vars:
      user_id: "usr-12345"
    steps:
      - name: update_user_data
        func: http
        args:
          url: "{{ vars.api_base }}/put"
          method: PUT
          body:
            id: "{{ vars.user_id }}"
            name: "Alice Smith"
            email: "alice.smith@example.com"
        outputs:
          status: "{{ result.status_code }}"

  # -- PATCH for partial update --
  patch-user-email:
    desc: "Partially update a resource with PATCH"
    steps:
      - name: patch_email
        func: http
        args:
          url: "{{ vars.api_base }}/patch"
          method: PATCH
          body:
            email: "newemail@example.com"
        outputs:
          status: "{{ result.status_code }}"

  # -- DELETE a resource --
  delete-user:
    desc: "Remove a resource with DELETE"
    steps:
      - name: delete_resource
        func: http
        args:
          url: "{{ vars.api_base }}/delete?id=usr-12345"
          method: DELETE
        outputs:
          status: "{{ result.status_code }}"

  # -- Full CRUD workflow --
  crud-workflow:
    desc: "Create, read, update, delete in sequence"
    steps:
      - name: create_item
        func: http
        args:
          url: "{{ vars.api_base }}/post"
          method: POST
          body:
            title: "New Feature"
            description: "Add retry with jitter support"
            priority: "high"
        outputs:
          status: "{{ result.status_code }}"

      - name: update_item
        func: http
        args:
          url: "{{ vars.api_base }}/put"
          method: PUT
          headers:
            X-Request-ID: "req-98765"
            X-Client-Version: "2.0.0"
          body:
            title: "New Feature (Updated)"
            status: "in_progress"
        outputs:
          status: "{{ result.status_code }}"
          request_id: '{{ index result.data_object.headers "X-Request-Id" }}'

      - name: delete_item
        func: http
        args:
          url: "{{ vars.api_base }}/delete?id=feat-001"
          method: DELETE
        outputs:
          status: "{{ result.status_code }}"

      - name: summary
        func: shell
        do: |
          echo "CRUD workflow complete"
          echo "Create: {{ steps.create_item.status }}"
          echo "Update: {{ steps.update_item.status }}"
          echo "Delete: {{ steps.delete_item.status }}"