| 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:
import { GeminiImageService } from '../../shared/clients/gemini_client.js';
import { parseResumeWithJina } from '../../shared/clients/jina_client.js';
import { JinaClient } from '../clients/jina_client.js';
Critical Import Rules
HTTP Client Import
import { createKyClient } from '@outputai/http';
import axios from 'axios';
Error Types Import
import { FatalError, ValidationError } from '@outputai/core';
class MyCustomError extends Error { ... }
Credentials Import
import { credentials } from '@outputai/credentials';
const apiKey = credentials.require('service.api_key');
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]
}
});
export async (): <> {
response = client.(, {
: { : query }
});
data = response.();
(!data.) {
();
}
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: {
: ,
: [, , , , , ]
}
});
. = ;
}
(: ): <> {
{
response = ..(, {
: {
: .,
input
}
});
response.();
} (: ) {
err = error { ?: ; ?: };
(err. === ) {
();
}
(err. === || err. === ) {
();
}
();
}
}
}
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]
}
});
export async function parseResumeWithJina(base64Pdf: string): Promise<string> {
const response = await client.(, {
: { : base64Pdf },
: {
:
}
});
: {
: {
: ;
?: ;
};
} = response.();
(!data.?.) {
();
}
data..;
}
(): <> {
response = client.(url, {
: {
: ,
: ,
:
}
});
: {
: {
?: ;
?: ;
};
} = response.();
textContent = data.?. || data.?.;
(!textContent) {
();
}
textContent;
}
Example 2: Gemini Client (Class-Based)
import { GoogleGenerativeAI } from '@google/generative-ai';
import { FatalError, ValidationError } from '@outputai/core';
import { credentials } from '@outputai/credentials';
export interface GeminiImageGenerationOptions {
prompt: string;
referenceImages?: Array<{
inlineData: {
mimeType: string;
data: string;
};
}>;
aspectRatio?: string;
resolution?: string;
numberOfImages?: number;
}
export class GeminiImageService {
private readonly client: GoogleGenerativeAI;
private readonly model: string = 'gemini-3-pro-image';
constructor(apiKey = credentials.require('google.api_key')) {
if (!apiKey) {
throw new (
);
}
. = (apiKey);
}
(: ): <[]> {
{ prompt, referenceImages = [], aspectRatio = , resolution = , numberOfImages = } = options;
{
model = ..({ : . });
: <{ : } | { : { : ; : } }> = [];
(referenceImages. > ) {
referenceImages.( parts.(img));
}
finalPrompt = ;
parts.({ : finalPrompt });
result = model.({
: [{ : , parts }],
: {
: ,
: ,
: numberOfImages,
:
}
});
: [] = [];
candidates = result.. || [];
( candidate candidates) {
(candidate.?.) {
( part candidate..) {
(part.?.) {
images.(part..);
}
}
}
}
(images. === ) {
();
}
images;
} (: ) {
err = error { ?: ; ?: };
(err. === ) {
();
}
(err. === || err. === ) {
();
}
();
}
}
}
Error Handling Patterns
HTTP Status Code Handling
const RETRY_STATUS_CODES = [408, 429, 500, 502, 503, 504];
const FATAL_STATUS_CODES = [401, 403, 404];
const client = createKyClient({
retry: {
limit: 3,
statusCodes: RETRY_STATUS_CODES
},
hooks: {
beforeError: [
( { error } ) => {
const status = error.response?.status;
const message = error.message;
if (status && FATAL_STATUS_CODES.includes(status)) {
throw new FatalError(`HTTP ${status} error: ${message}`);
}
throw new ValidationError(`HTTP request failed: ${message}`);
}
]
}
});
Error Type Guidelines
| Status Code | Error Type | Reason |
|---|
| 401, 403 | FatalError | Auth failures won't succeed on retry |
| 404 | FatalError | Resource doesn't exist |
| 408 | ValidationError | Timeout, may succeed on retry |
| 429 | ValidationError | Rate limit, will succeed after wait |
| 500+ | ValidationError | Server errors may be temporary |
Best Practices
1. Use Credentials for API Keys
Prefer @outputai/credentials over process.env for secrets management. See output-dev-credentials skill for details.
import { credentials } from '@outputai/credentials';
const apiKey = credentials.require('service.api_key');
const region = credentials.get('aws.region', 'us-east-1');
2. Document Functions with JSDoc
export async function fetchUserProfile(userId: string): Promise<UserProfile> {
}
3. Use Consistent Timeouts
timeout: 30000
timeout: 60000
4. Consume or Cancel Response Bodies
createKyClient follows Fetch response-body semantics: callers own returned response bodies. Prefer body readers like .json() or .text()
when the payload is needed. If a request only reads metadata such as response.url, response.status, or headers, cancel the
unused body in a finally block.
const response = await client.get( url );
try {
return response.url;
} finally {
await response.body?.cancel();
}
HEAD requests do not have response bodies, so this is only needed for methods that can return one.
5. Export TypeScript Interfaces
export interface ServiceResponse {
data: {
id: string;
content: string;
};
metadata: {
processedAt: string;
};
}
Verification Checklist
Related Skills
output-dev-step-function - Using clients in step functions
output-dev-evaluator-function - Using clients in evaluators
output-dev-folder-structure - Understanding project layout
output-dev-credentials - Encrypted secrets management
output-error-http-client - Troubleshooting HTTP issues
output-error-try-catch - Proper error handling patterns