/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.
$ npx -y skills add growthxai/output --skill output-dev-http-client-create --agent claude-codeHow 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.mdname: 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 responRead more
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 responThe 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.
Repo: growthxai/output
Other skills on output.
- /llm-output-schema-constraints
Zod schema constraints that Anthropic rejects or silently ignores when sent as structured-output tool definitions via Output.object(). Use when writing or reviewing Zod schemas passed to Output.object(), or debugging structured-output validation errors.
Open skill - /prompt-file-provider-options
Guide to the providerOptions structure in .prompt files — decision tree for where an option goes, common mistakes, per-provider quick reference, and Anthropic prompt caching. Use when writing or reviewing .prompt file frontmatter (provider, model, providerOptions,
Open skill - /validate
Run lint, build, and tests to validate changes are correct
Open skill - /output-build-workflow
Implement an Output SDK workflow from a plan document. Use when the user asks to build, implement, or code a workflow from an existing plan, or after output-plan-workflow has produced a plan and the user is ready to build.
Open skill - /output-credentials-edit
View and edit encrypted credentials in an Output.ai project. Use when adding secrets, updating API keys, verifying credential values, or retrieving a specific credential.
Open skill - /output-credentials-env-vars
Wire encrypted credentials to environment variables using the credential: convention. Use when setting up LLM provider keys (ANTHROPIC_API_KEY, OPENAI_API_KEY) or any env var that should come from encrypted credentials.
Open skill

