Skip to content
Development
Skill

/output-dev-http-client-create

Create shared HTTP clients in src/shared/clients/ for Output SDK workflows. Use when integrating external APIs, creating service wrappers, or standardizing HTTP operations.

From plugin
output
43052 skills11 agents1 command
Install
$ npx -y skills add growthxai/output --skill output-dev-http-client-create --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-dev-http-client-create

Context preview

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

Create shared HTTP clients in src/shared/clients/ for Output SDK workflows. Use when integrating external APIs, creating service wrappers, or standardizing HTTP operations.

SKILL.md

output-dev-http-client-create.SKILL.md
name: output-dev-http-client-create
description: Create shared HTTP clients in src/shared/clients/ for Output SDK workflows. Use when integrating external APIs, creating service wrappers, or standardizing HTTP operations.
allowed-tools: [Read, Write, Edit, Glob]

Creating HTTP Clients

Overview

This skill documents how to create shared HTTP clients for Output SDK workflows. Clients are stored in `src/shared/clients/` and shared across all workflows to ensure consistent error handling, retry logic, and API integration patterns.

When to Use This Skill

  • Integrating a new external API service
  • Creating a reusable HTTP wrapper for a service
  • Standardizing error handling for API calls
  • Moving inline HTTP logic to a shared client

Location Convention

HTTP clients are stored in the shared clients folder:

src/shared/clients/
├── gemini_client.ts     # Google Gemini API client
├── jina_client.ts       # Jina AI client
├── perplexity_client.ts # Perplexity API client
└── {service}_client.ts  # Your new client

**Important**: Clients are shared across ALL workflows. Do NOT create per-workflow HTTP clients.

Other Shared Code Locations

src/shared/
├── clients/     # API clients (this skill)
├── utils/       # Utility functions & helpers
├── services/    # Business logic services
├── steps/       # Shared step definitions (optional)
└── evaluators/  # Shared evaluators (optional)

Import Pattern in Workflows

Use relative imports from workflow files to shared clients:

// CORRECT - Relative path from workflow steps.ts
import { GeminiImageService } from '../../shared/clients/gemini_client.js';
import { parseResumeWithJina } from '../../shared/clients/jina_client.js';

// From shared steps (if used)
import { JinaClient } from '../clients/jina_client.js';

Critical Import Rules

HTTP Client Import

// CORRECT - Use @outputai/http wrapper
import { createKyClient } from '@outputai/http';

// WRONG - Never use axios directly
import axios from 'axios';

Error Types Import

// CORRECT - Import error types from @outputai/core
import { FatalError, ValidationError } from '@outputai/core';

// WRONG - Custom error classes
class MyCustomError extends Error { ... }

Credentials Import

// CORRECT - Use @outputai/credentials for secrets
import { credentials } from '@outputai/credentials';
const apiKey = credentials.require('service.api_key');

// WRONG - Never use process.env for secrets
const apiKey = process.env.SERVICE_API_KEY;

Basic Client Structure

Simple Function-Based Client

import { FatalError, ValidationError } from '@outputai/core';
import { createKyClient } from '@outputai/http';
import { credentials } from '@outputai/credentials';

const API_KEY = credentials.require('service.api_key');
const BASE_URL = 'https://api.service.com';

const client = createKyClient({
  prefix: BASE_URL,
  headers: {
    Authorization: `Bearer ${API_KEY}`,
    Accept: 'application/json'
  },
  timeout: 30000,
  retry: {
    limit: 3,
    statusCodes: [408, 429, 500, 502, 503, 504]
  }
});

/**
 * Fetch data from the service
 *
 * @param query - Search query string
 * @returns Processed response data
 * @throws {FatalError} If authentication fails or resource not found
 * @throws {ValidationError} If temporary error occurs
 */
export async function fetchServiceData(query: string): Promise<ServiceResponse> {
  const response = await client.get('endpoint', {
    searchParams: { q: query }
  });

  const data = await response.json();

  if (!data.results) {
    throw new FatalError('No results returned from service');
  }

  return data;
}

Class-Based Client

import { FatalError, ValidationError } from '@outputai/core';
import { createKyClient } from '@outputai/http';
import { credentials } from '@outputai/credentials';

export interface ServiceOptions {
  model?: string;
  timeout?: number;
}

export class ServiceClient {
  private readonly client: ReturnType<typeof createKyClient>;
  private readonly model: string;

  constructor(apiKey?: string) {
    const key = apiKey ?? credentials.require('service.api_key');

    this.client = createKyClient({
      prefix: 'https://api.service.com',
      headers: {
        Authorization: `Bearer ${key}`,
        'Content-Type': 'application/json'
      },
      timeout: 30000,
      retry: {
        limit: 3,
        statusCodes: [408, 429, 500, 502, 503, 504]
      }
    });

    this.model = 'default-model';
  }

  async process(input: ProcessInput): Promise<ProcessOutput> {
    try {
      const response = await this.client.post('process', {
        json: {
          model: this.model,
          input
        }
      });

      return await response.json();
    } catch (error: unknown) {
      const err = error as { status?: number; message?: string };

      if (err.status === 429) {
        throw new ValidationError(`Rate limit exceeded: ${err.message}`);
      }

      if (err.status === 401 || err.status === 403) {
        throw new FatalError(`Authentication failed: ${err.message}`);
      }

      throw new ValidationError(`Service call failed: ${err.message}`);
    }
  }
}

Real-World Examples

Example 1: Jina Client (Function-Based)

import { FatalError } from '@outputai/core';
import { createKyClient } from '@outputai/http';
import { credentials } from '@outputai/credentials';

const JINA_API_KEY = credentials.require('jina.api_key');
const JINA_BASE_URL = 'https://r.jina.ai';

const client = createKyClient({
  prefix: JINA_BASE_URL,
  headers: {
    Authorization: `Bearer ${JINA_API_KEY}`,
    Accept: 'application/json'
  },
  timeout: 30000,
  retry: {
    limit: 3,
    statusCodes: [408, 413, 429, 500, 502, 503, 504]
  }
});

/**
 * Parse PDF resume using Jina Reader API
 */
export async function parseResumeWithJina(base64Pdf: string): Promise<string> {
  const respon
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