Skip to content
Development
Skill

/output-error-http-client

Fix HTTP client misuse in Output SDK steps. Use when seeing untraced requests, missing error details, axios-related errors, or when HTTP calls aren't being properly logged and retried.

From plugin
output
43052 skills11 agents1 command
Install
$ npx -y skills add growthxai/output --skill output-error-http-client --agent claude-code

How it fires

How this skill gets triggered: by you, by Claude, or both.

  • Fires itselfAuto-invocation. Claude auto-loads it when your prompt matches the work.Auto-invocation is when the right skill fires by itself at the right moment, driven by a FLOW.md router and a hook, instead of you invoking it by name. It is the difference between a skill being installed and a skill actually getting used.Read the full definition →
  • You can call itInvoke it directly when you want it.
  • Slash command/output-error-http-client

Context preview

The summary Claude sees to decide when to auto-load this skill.

Fix HTTP client misuse in Output SDK steps. Use when seeing untraced requests, missing error details, axios-related errors, or when HTTP calls aren't being properly logged and retried.

SKILL.md

output-error-http-client.SKILL.md
name: output-error-http-client
description: Fix HTTP client misuse in Output SDK steps. Use when seeing untraced requests, missing error details, axios-related errors, or when HTTP calls aren't being properly logged and retried.
allowed-tools: [Bash, Read]

Fix HTTP Client Misuse

Overview

This skill helps diagnose and fix issues caused by using axios, fetch, or other HTTP clients directly instead of Output SDK's `createKyClient` from `@outputai/http`. The Output SDK client provides tracing, automatic retries, and better error handling.

When to Use This Skill

You're seeing:

  • Untraced HTTP requests (not appearing in workflow traces)
  • Missing error details for failed requests
  • axios-related errors or import issues
  • Retries not working for HTTP failures
  • Inconsistent timeout behavior

Root Cause

Using axios, fetch, or other HTTP clients directly bypasses Output SDK's:

  • **Request/response tracing**: Calls aren't logged in workflow traces
  • **Automatic retries**: Failed requests aren't retried
  • **Error standardization**: Error formats may be inconsistent
  • **Timeout handling**: Timeouts may not integrate with step timeouts

Symptoms

Using axios Directly

// WRONG: Using axios
import axios from 'axios';

export const fetchData = step( {
  name: 'fetchData',
  fn: async input => {
    const response = await axios.get( 'https://api.example.com/data' );
    return response.data;
  }
} );

Using fetch Directly

// WRONG: Using fetch
export const fetchData = step( {
  name: 'fetchData',
  fn: async input => {
    const response = await fetch( 'https://api.example.com/data' );
    return response.json();
  }
} );

Solution

Use `createKyClient` from `@outputai/http`:

Basic Usage

import { z, step } from '@outputai/core';
import { createKyClient } from '@outputai/http';

export const fetchData = step( {
  name: 'fetchData',
  inputSchema: z.object( {
    endpoint: z.string()
  } ),
  outputSchema: z.object( {
    data: z.unknown()
  } ),
  fn: async input => {
    const client = createKyClient( {
      prefix: 'https://api.example.com'
    } );

    const data = await client.get( input.endpoint ).json();
    return { data };
  }
} );

With Full Configuration

import { createKyClient } from '@outputai/http';

const client = createKyClient( {
  prefix: 'https://api.example.com',
  timeout: 30000,  // 30 second timeout
  retry: {
    limit: 3,      // Retry up to 3 times
    methods: [ 'GET', 'POST' ],  // Which methods to retry
    statusCodes: [ 408, 500, 502, 503, 504 ]  // Which status codes trigger retry
  },
  headers: {
    'Authorization': `Bearer ${apiKey}`,
    'Content-Type': 'application/json'
  }
} );

HTTP Methods

GET Request

const data = await client.get( 'users/123' ).json();

POST Request

const result = await client.post( 'users', {
  json: {
    name: 'John',
    email: 'john@example.com'
  }
} ).json();

PUT Request

const updated = await client.put( 'users/123', {
  json: {
    name: 'John Updated'
  }
} ).json();

DELETE Request

await client.delete( 'users/123' );

With Query Parameters

const data = await client.get( 'search', {
  searchParams: {
    q: 'query',
    limit: 10
  }
} ).json();

Metadata-Only Responses

When code only reads metadata from a non-`HEAD` response, such as `response.url`, `response.status`, or headers, cancel the unused body. Reading a body with `.json()`, `.text()`, etc. already consumes it.

const response = await client.get( url );

try {
  return response.url;
} finally {
  await response.body?.cancel();
}

Complete Migration Example

Before (Wrong - using axios)

import axios from 'axios';
import { step } from '@outputai/core';

export const createUser = step( {
  name: 'createUser',
  fn: async input => {
    try {
      const response = await axios.post(
        'https://api.example.com/users',
        { name: input.name, email: input.email },
        {
          headers: { 'Authorization': `Bearer ${process.env.API_KEY}` },
          timeout: 30000
        }
      );
      return response.data;
    } catch ( error ) {
      if ( axios.isAxiosError( error ) ) {
        throw new Error( `API Error: ${error.response?.data?.message}` );
      }
      throw error;
    }
  }
} );

After (Correct - using createKyClient)

import { z, step } from '@outputai/core';
import { createKyClient } from '@outputai/http';
import { credentials } from '@outputai/credentials';

export const createUser = step( {
  name: 'createUser',
  inputSchema: z.object( {
    name: z.string(),
    email: z.string().email()
  } ),
  outputSchema: z.object( {
    id: z.string(),
    name: z.string(),
    email: z.string()
  } ),
  fn: async input => {
    const client = createKyClient( {
      prefix: 'https://api.example.com',
      timeout: 30000,
      retry: { limit: 3 },
      headers: {
        'Authorization': `Bearer ${credentials.require( 'service.api_key' )}`
      }
    } );

    const user = await client.post( 'users', {
      json: {
        name: input.name,
        email: input.email
      }
    } ).json();

    return user;
  }
} );

Error Handling

The Ky client provides structured error handling:

import { createKyClient, ky } from '@outputai/http';

export const fetchData = step( {
  name: 'fetchData',
  fn: async input => {
    const client = createKyClient( { prefix: 'https://api.example.com' } );

    try {
      return await client.get( 'data' ).json();
    } catch ( error ) {
      if ( error instanceof ky.HTTPError ) {
        // Access response details
        const status = error.response.status;
        const body = await error.response.json();
        throw new Error( `API returned ${status}: ${body.message}` );
      }
      throw e
Read more
Ships withoutput

The open-source TypeScript framework for building AI workflows and agents. Designed for Claude Code — describe what you want, Claude builds it, with all the best practices already in place. One framework.

Get the whole plugin