API Package Development Patterns
Foundation package for You.com API - CLI tool and shared utilities used by MCP and AI SDK plugin packages.
For end users: See packages/api/README.md
For universal patterns: See.agents/rules/core.md
When to Use
- Contributing to
@youdotcom-oss/apipackage - Adding new You.com API endpoints
- Understanding how MCP/AI SDK packages consume shared utilities
- Implementing CLI commands
Architecture
Foundation Layer - AI SDK Plugin and LangChain build on this:
@youdotcom-oss/api (Foundation)
├── CLI commands (search, research, contents)
├── API utilities (fetch*, format*, call*)
├── Zod schemas (*Schema)
├── Dry-run request builders (build*Request)
└── Type definitions (api.types.ts)
↓
Used by AI SDK Plugin, LangChain
Note:
@youdotcom-oss/mcpis now a thin STDIO bridge — it no longer imports from this package. Only AI SDK Plugin and LangChain consume API utilities directly.
When adding new APIs:
- Add to API package FIRST (schemas, utils, types)
- THEN expose via AI SDK tools or LangChain tools
- Keep foundation logic in API package
Tech Stack
- Runtime: Bun >= 1.2.21
- Validation: Zod ^4.3.5
- Testing: Bun test
- CLI Binary:
bin/cli.js(compiled fromsrc/cli.ts)
Quick Start
bun --cwd packages/api test
bun --cwd packages/api check
Package-Specific Patterns
CLI Command Pattern
Structure: Each command in src/cli.ts uses shared utilities:
// ✅ CLI wraps shared utility
case 'search': {
const result = await fetchSearchResults({ params, YDC_API_KEY, getUserAgent });
console.log(formatSearchResults(result));
break;
}
// ❌ Don't duplicate logic in CLI
case 'search': {
const response = await fetch(/* ... */); // Wrong - use shared utility
}
Why: CLI is thin wrapper, logic stays in shared utilities for reuse
Verify: grep -c 'await fetch(' src/cli.ts should be 0
Fix: Move fetch logic to src/*/utils.ts, import in CLI
Schema First Design
ALL API endpoints need Zod schemas BEFORE implementation:
// ✅ Define schema first
export const SearchQuerySchema = z.object({
query: z.string().describe('Search query'),
count: z.number().int().min(1).max(20).default(10),
});
// Then use in utility
export const fetchSearchResults = async (params: z.infer<typeof SearchQuerySchema>) => {
const validated = SearchQuerySchema.parse(params);
// ...
};
Verify: Every fetch* function has matching *Schema export
Fix: Create schema in src/*/*.schemas.ts before implementing utility
Shared Utility Export Pattern
Public API (src/main.ts) exports ONLY reusable pieces:
// ✅ Export for consumption by MCP/AI SDK
export * from './search/search.schemas.ts';
export * from './search/search.utils.ts';
export * from './shared/api.types.ts';
// ❌ Don't export CLI-specific code
export * from './cli.ts'; // Wrong - CLI is binary, not library
Verify: grep "from './cli" src/main.ts returns nothing
Fix: Remove CLI exports from main.ts
Error Handling
API utilities throw descriptive errors for better debugging:
// ✅ Throw with context
if (!response.ok) {
const errorData = await response.json();
throw new Error(`Failed to perform search. Error code: ${errorData.code}`);
}
// ✅ Preserve error context in catch blocks
try {
const response = await fetch(url);
return await response.json();
} catch (err: unknown) {
throw new Error(`API request failed: ${err instanceof Error ? err.message : String(err)}`);
}
// ❌ Don't silently fail
if (!response.ok) {
return { error: 'failed' }; // Loses context
}
Verify: Check error messages include context (status codes, error codes, URLs) Fix: Add descriptive error messages with relevant context
User-Agent Pattern
ALL API calls use getUserAgent():
// ✅ Always include User-Agent
const response = await fetch(url, {
headers: {
'X-API-Key': YDC_API_KEY,
'User-Agent': getUserAgent(),
}
});
// ❌ Missing User-Agent
const response = await fetch(url, {
headers: { 'X-API-Key': YDC_API_KEY }
});
Verify: grep -L "getUserAgent()" src/*/utils.ts
Fix: Add getUserAgent() to all fetch calls
Custom Headers Pattern
All fetch and dry-run functions accept an optional customHeaders param:
// ✅ Pass custom headers (e.g., for OAuth user identification)
const results = await fetchSearchResults({
searchQuery: { query: 'AI' },
YDC_API_KEY,
getUserAgent,
customHeaders: { 'X-Custom-Header': 'value' },
})
// customHeaders are spread BEFORE standard headers, so standard headers win on conflict
headers: new Headers({
...customHeaders, // ← custom headers first
'X-API-Key': YDC_API_KEY, // ← standard headers override
'User-Agent': getUserAgent(),
})
Type: CustomHeaders = Record<string, string> (exported from api.types.ts)
Verify: All fetch* and build*Request functions include customHeaders?: CustomHeaders param
Fix: Add param to function signature and spread before standard headers
File Organization
src/
├── search/
│ ├── search.schemas.ts # SearchQuerySchema
│ ├── search.utils.ts # fetchSearchResults()
│ └── tests/
├── research/
│ ├── research.schemas.ts # ResearchQuerySchema
│ ├── research.utils.ts # callResearch()
│ └── tests/
├── contents/
│ ├── contents.schemas.ts # ContentsQuerySchema
│ ├── contents.utils.ts # fetchContents()
│ └── tests/
├── shared/
│ ├── api.constants.ts # API URLs
│ ├── api.types.ts # GetUserAgent, CustomHeaders
│ ├── api-error.schemas.ts # Error response schemas
│ ├── check-response-for-errors.ts # 200-response error detection
│ ├── command-runner.ts # CLI command dispatch
│ ├── dry-run-utils.ts # build*Request() for --dry-run
│ ├── format-search-results-text.ts # Human-readable search formatting
│ ├── generate-command-help.ts # CLI --help generation
│ ├── generate-error-report-link.ts # mailto link for error reports
│ ├── use-get-user-agents.ts # getUserAgent factory
│ └── tests/
├── main.ts # Public API exports
└── cli.ts # CLI entry point
Testing
Test both CLI and utilities:
bun test # All tests
bun test src/search/tests/ # Specific module
API key required - Set in .env:
echo "export YDC_API_KEY=your-key" > .env
source .env
Adding New APIs
Workflow:
- Create
src/{api-name}/{api-name}.schemas.tswith Zod schemas - Create
src/{api-name}/{api-name}.utils.tswithfetch*()functions - Add types to
src/shared/api.types.tsif needed - Export from
src/main.tsfor library consumers - Add CLI command in
src/cli.ts(optional) - Write tests in
src/{api-name}/tests/ - THEN add to MCP or AI SDK plugin packages
Don't skip schema step - Schemas ensure type safety for all consumers
Publishing
Standard workflow via .github/workflows/publish-api.yml
See root AGENTS.md
Related Skills
.agents/rules/core.md- Type over interface, arrow functions.agents/rules/bun.md- Bun APIs (Bun.file, Bun.$).agents/rules/testing.md- Test patterns.claude/skills/ai-sdk-plugin-patterns- Consumes API utilities
Contributing
Package scope: api in commit messages
feat(api): add image search endpoint
fix(api): handle rate limit errors
Converted and distributed by TomeVault — claim your Tome and manage your conversions.