Skip to content
Back to skills

Output Error Http Client

ASecurity

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

  • 440 stars
  • 0 votes
  • 0 copies
  • 2 views
  • Added May 27, 2026
data-aitypescriptgobashnodeapi

Works with

  • cli
  • api

Security analysis

A100/100

Scanned October 2, 2026

npx -y skills add growthxai/output --skill output-error-http-client --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Output Error Http Client?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Output Error Http Client
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/growthxai-output-error-http-client/badge)](https://www.skillsdirectory.com/skills/growthxai-output-error-http-client)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
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, HTTP calls aren't being properly logged and retried, or paid API costs are missing from cost reports.
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

```typescript
// 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

```typescript
// 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

```typescript
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

```typescript
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

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

### POST Request

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

### PUT Request

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

### DELETE Request

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

### With Query Parameters

```typescript
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.

```typescript
const response = await client.get( url );

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

## Complete Migration Example

### Before (Wrong - using axios)

```typescript
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)

```typescript
import { z, step } from '@outputai/core';
import { createKyClient } from '@outputai/http';
import { credentials } from '@outputai/core/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:

```typescript
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 error;
    }
  }
} );
```

## Missing Cost Tracking for Paid APIs

### Symptom

A client wraps a paid third-party API, but its calls never show up in the `API Costs` section of `npx output workflow cost` — they're traced (so tracing works), but their cost is always $0 or absent from the report.

### Root Cause

`npx output workflow cost` only knows about HTTP spend that was explicitly attached with `addRequestCost` from `@outputai/http`. Tracing a request is automatic; costing it is not — a client that never calls `addRequestCost` will trace correctly and cost nothing, which is expected for free/internal services but a bug for paid ones.

### Solution

Attach cost inside an `afterResponse` hook on the client so every call is costed automatically, rather than at each call site. See `output-dev-http-client-create` (section "Attaching Request Cost (Paid APIs)") for the metered-response and flat-rate patterns, and `output-dev-cost-hooks` for forwarding those costs to an external observability system.

```typescript
// WRONG: paid API, no cost tracking
const client = createKyClient( { prefix: 'https://api.service.com' } );

// CORRECT: cost attached automatically for every call
import { addRequestCost, createKyClient } from '@outputai/http';

const client = createKyClient( {
  prefix: 'https://api.service.com',
  hooks: {
    afterResponse: [
      ( _request, _options, response ) => {
        if ( !response.ok ) return;
        addRequestCost( response, 0.005 );
      }
    ]
  }
} );
```

## Finding axios/fetch Usage

Search your codebase:

```bash
# Find axios imports
grep -rn "from 'axios'\|from \"axios\"" src/

# Find fetch calls
grep -rn "await fetch(" src/

# Find other HTTP libraries
grep -rn "got\|node-fetch\|request\|superagent" src/
```

## Benefits of createKyClient

1. **Tracing**: Requests appear in workflow traces with timing
2. **Automatic Retries**: Configurable retry logic for transient failures
3. **Consistent Errors**: Standardized error format across all requests
4. **Timeout Integration**: Works with step and workflow timeouts
5. **Type Safety**: Full TypeScript support

## Configuration Options

| Option | Description | Default |
|--------|-------------|---------|
| `prefix` | Base URL for all requests | (required) |
| `timeout` | Request timeout in ms | 10000 |
| `retry.limit` | Max retry attempts | 2 |
| `retry.methods` | HTTP methods to retry | ['GET', 'PUT', 'HEAD', 'DELETE', 'OPTIONS', 'TRACE'] |
| `retry.statusCodes` | Status codes to retry | [408, 413, 429, 500, 502, 503, 504] |
| `headers` | Default headers | {} |

## Verification

After migrating to createKyClient:

1. **Run the workflow**: `npx output workflow run <name> --input '<input>'`
2. **Check the trace**: `npx output workflow debug <id> --json`
3. **Verify tracing**: HTTP requests should appear in the step trace
4. **Test retries**: Simulate failures to verify retry behavior

## Related Issues

- For I/O in workflow functions, see `output-error-direct-io`
- For connection issues, see `output-services-check`
- For encrypted secrets management, see `output-dev-credentials`
- For attaching and forwarding request cost, see `output-dev-http-client-create` and `output-dev-cost-hooks`

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.