From outputai
Fixes HTTP client misuse in Output SDK steps by replacing axios/fetch with @outputai/http for tracing, retries, and error handling. Use for untraced requests, missing error details, or axios errors.
npx claudepluginhub growthxai/output --plugin outputaiThis skill is limited to using the following tools:
This skill helps diagnose and fix issues caused by using axios, fetch, or other HTTP clients directly instead of Output SDK's `httpClient` from `@outputai/http`. The Output SDK client provides tracing, automatic retries, and better error handling.
Creates shared HTTP clients in src/shared/clients/ for Output SDK workflows. Use for integrating external APIs, reusable service wrappers, or standardizing HTTP operations with consistent error handling and retries.
Configures HTTP clients for API integrations including third-party APIs, webhooks, SDK generation, and OAuth. Generates production-ready code, configs, and best practices on trigger phrases like 'http client config'.
Mandates invoking relevant skills via tools before any response in coding sessions. Covers access, priorities, and adaptations for Claude Code, Copilot CLI, Gemini CLI.
Share bugs, ideas, or general feedback.
This skill helps diagnose and fix issues caused by using axios, fetch, or other HTTP clients directly instead of Output SDK's httpClient from @outputai/http. The Output SDK client provides tracing, automatic retries, and better error handling.
You're seeing:
Using axios, fetch, or other HTTP clients directly bypasses Output SDK's:
// 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;
},
});
// WRONG: Using fetch
export const fetchData = step({
name: 'fetchData',
fn: async (input) => {
const response = await fetch('https://api.example.com/data');
return response.json();
},
});
Use httpClient from @outputai/http:
import { z, step } from '@outputai/core';
import { httpClient } 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 = httpClient({
prefixUrl: 'https://api.example.com',
});
const data = await client.get(input.endpoint).json();
return { data };
},
});
import { httpClient } from '@outputai/http';
const client = httpClient({
prefixUrl: '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',
},
});
const data = await client.get('users/123').json();
const result = await client.post('users', {
json: {
name: 'John',
email: 'john@example.com',
},
}).json();
const updated = await client.put('users/123', {
json: {
name: 'John Updated',
},
}).json();
await client.delete('users/123');
const data = await client.get('search', {
searchParams: {
q: 'query',
limit: 10,
},
}).json();
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;
}
},
});
import { z, step } from '@outputai/core';
import { httpClient } from '@outputai/http';
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 = httpClient({
prefixUrl: 'https://api.example.com',
timeout: 30000,
retry: { limit: 3 },
headers: {
'Authorization': `Bearer ${process.env.API_KEY}`,
},
});
const user = await client.post('users', {
json: {
name: input.name,
email: input.email,
},
}).json();
return user;
},
});
The httpClient provides structured error handling:
import { httpClient, HTTPError } from '@outputai/http';
export const fetchData = step({
name: 'fetchData',
fn: async (input) => {
const client = httpClient({ prefixUrl: 'https://api.example.com' });
try {
return await client.get('data').json();
} catch (error) {
if (error instanceof 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;
}
},
});
Search your codebase:
# 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/
| Option | Description | Default |
|---|---|---|
prefixUrl | 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 | {} |
After migrating to httpClient:
npx output workflow run <name> '<input>'npx output workflow debug <id> --format jsonoutput-error-direct-iooutput-services-checkoutput-dev-credentials