Otaip · Public docs
Otaip API docs
Getting started and developer guide from the public Otaip repo — cached for one hour.
Building a product on Otaip? Talk to us about Aviare.
Getting started
Getting Started with OTAIP
This guide walks you through installing OTAIP, running your first agent, and wiring up a flight search.
Prerequisites
- Node.js >= 24 (download)
- pnpm 10+ (
corepack enable && corepack prepare pnpm@10.33.0 --activate)
Install and verify
git clone https://github.com/TelivityAI/otaip.git
cd otaip
pnpm install
# Download reference datasets (48K airports, 22 metro area mappings)
pnpm run data:download
# Run all tests
pnpm test
# Type check
pnpm typecheck
1. Your first agent — Airport Code Resolver
Every OTAIP agent implements the same interface: initialize(), execute(), health().
import { AirportCodeResolver } from '@otaip/agents-reference';
const resolver = new AirportCodeResolver();
await resolver.initialize();
// Resolve a multi-airport city
const result = await resolver.execute({ data: { code: 'NYC' } });
console.log(result.data);
// => { airports: [{ iata: 'JFK', name: 'John F Kennedy Intl', ... }, ...], type: 'metro' }
console.log(result.confidence);
// => 0.95
2. Flight search with Duffel adapter
Wire up the Duffel adapter to search for flights:
import { AvailabilitySearch } from '@otaip/agents-search';
import { MockDuffelAdapter } from '@otaip/adapter-duffel';
// Use MockDuffelAdapter for development (no API key needed)
const adapter = new MockDuffelAdapter();
const search = new AvailabilitySearch({ adapters: [adapter] });
await search.initialize();
const result = await search.execute({
data: {
segments: [{ origin: 'LHR', destination: 'CDG', departure_date: '2026-06-15' }],
passengers: { adults: 1, children: 0, infants: 0 },
cabin_class: 'economy',
},
});
console.log(`Found ${result.data.offers.length} offers`);
For real API calls, use DuffelAdapter with your API key:
import { DuffelAdapter } from '@otaip/adapter-duffel';
const adapter = new DuffelAdapter({
apiKey: process.env.DUFFEL_API_KEY!,
});
3. Understanding the agent pattern
All OTAIP agents share these characteristics:
- Typed I/O: Input and output types are defined in each agent's
types.ts - Confidence scores: Every output includes a
confidencefield (0-1) - Health checks:
agent.health()returns{ status: 'healthy' | 'degraded' | 'unhealthy' } - Stateless: Agents don't hold state between executions (by default)
- Composable: Chain agents together — one agent's output feeds another's input
4. Where to go next
- Agent specs: See
agents/specs/for detailed YAML specifications of each agent - Developer guide: See
docs/DEVELOPER_GUIDE.mdfor the full development workflow - Connect framework: See
packages/connect/GUIDE.mdfor multi-supplier adapter setup (Sabre, Amadeus, Navitaire) - Adapter status: See
docs/architecture/ADAPTER_STATUS.mdfor what's implemented - Demo scripts: Run
pnpm --filter demo bookto see a full booking flow
5. Building your own agent
Use the scaffold script:
pnpm tsx scripts/scaffold-agent.ts --stage 10 --id 1 --name "my-agent"
Or follow the reference implementation at packages/agents/reference/src/airport-code-resolver/.
/api/v1/agents/searchFlight + hotel search/api/v1/agents/priceFare + rate pricing/api/v1/agents/bookCreate booking/api/v1/agents/ticketIssue ticket/api/v1/agents/statusAgent run statusDeveloper guide
OTAIP Developer Guide
Build typed, testable agents for the travel industry.
Agent Interface
Every agent implements Agent<TInput, TOutput> from @otaip/core:
import type { Agent, AgentInput, AgentOutput, AgentHealthStatus } from '@otaip/core';
export class MyAgent implements Agent<MyInput, MyOutput> {
readonly id = '0.1';
readonly name = 'My Agent';
readonly version = '0.1.0';
private initialized = false;
async initialize(): Promise<void> {
// Load reference data, validate datasets
this.initialized = true;
}
async execute(input: AgentInput<MyInput>): Promise<AgentOutput<MyOutput>> {
// Core logic — deterministic, no side effects
return {
data: result,
confidence: 1.0,
metadata: { agent_id: this.id },
};
}
async health(): Promise<AgentHealthStatus> {
return { status: 'healthy' };
}
}
Input/Output Wrappers
// Input wraps your domain type with optional metadata
interface AgentInput<T> {
data: T;
metadata?: Record<string, unknown>;
}
// Output wraps your result with confidence and warnings
interface AgentOutput<T> {
data: T;
confidence?: number; // 0-1
metadata?: Record<string, unknown>;
warnings?: string[];
}
// Health check
interface AgentHealthStatus {
status: 'healthy' | 'degraded' | 'unhealthy';
details?: string;
}
File Structure
Every agent follows this layout:
{agent-name}/
types.ts # All input/output/internal types
{logic-module}.ts # Core business logic (pure functions)
index.ts # Agent class (implements Agent interface)
__tests__/
{agent-name}.test.ts # Vitest tests
- types.ts defines the public interface. Other agents depend on these types.
- Logic modules contain pure functions. No state, no I/O.
- index.ts wires types + logic into the Agent lifecycle.
- Tests encode domain knowledge as assertions.
Building a New Agent
Step 1: Write the spec
Create a YAML spec in agents/specs/ that defines inputs, outputs, types, and test cases. The spec is the contract — implement to the spec, not around it.
Step 2: Define types
// types.ts
export interface MyAgentInput {
query: string;
options?: { maxResults?: number };
}
export interface MyAgentOutput {
results: Result[];
totalMatches: number;
}
Step 3: Implement logic
Keep business logic in separate modules as pure functions:
// resolver.ts
export function resolve(query: string, data: Dataset[]): Result[] {
// Pure function — no this, no state, no I/O
}
Step 4: Implement the agent
// index.ts
export class MyAgent implements Agent<MyAgentInput, MyAgentOutput> {
readonly id = 'X.Y';
readonly name = 'My Agent';
readonly version = '0.1.0';
private initialized = false;
private data: Dataset[] = [];
async initialize(): Promise<void> {
this.data = loadDataset(); // Load once
this.initialized = true;
}
async execute(input: AgentInput<MyAgentInput>): Promise<AgentOutput<MyAgentOutput>> {
if (!this.initialized) {
throw new AgentNotInitializedError(this.id);
}
// Validate, execute, return
}
async health(): Promise<AgentHealthStatus> {
if (!this.initialized) {
return { status: 'unhealthy', details: 'Not initialized' };
}
return { status: 'healthy' };
}
}
Step 5: Export from package index
// src/index.ts
export { MyAgent } from './my-agent/index.js';
export type { MyAgentInput, MyAgentOutput } from './my-agent/types.js';
Error Handling
Use the error classes from @otaip/core:
| Error | When to throw |
|---|---|
AgentNotInitializedError | execute() called before initialize() |
AgentInputValidationError | Bad input — specify field and reason |
AgentDataUnavailableError | External data source is down or missing |
AgentError | Base class for custom domain errors |
import { AgentInputValidationError } from '@otaip/core';
if (!data.code) {
throw new AgentInputValidationError(this.id, 'code', 'Airport code is required');
}
Confidence Scores
Every AgentOutput includes an optional confidence field (0-1):
| Score | Meaning |
|---|---|
| 1.0 | Exact match or deterministic result |
| 0.7-0.9 | High-confidence fuzzy match |
| 0.5-0.7 | Partial match, may need review |
| 0 | Not found or no match |
Downstream agents can use confidence to filter, sort, or escalate results.
Adapter Pattern
Distribution adapters connect agents to external data sources:
// Hotel source adapter (lodging domain)
interface HotelSourceAdapter {
readonly id: string;
readonly name: string;
search(input: HotelSearchInput): Promise<RawHotelResult[]>;
}
Adapters are injected at construction time. Mock adapters for tests, live adapters for production:
// In tests
const agent = new HotelSearchAgent({ adapters: [new MockAmadeusAdapter()] });
// In production
const agent = new HotelSearchAgent({
adapters: [
new AmadeusHotelAdapter({ apiKey: process.env.AMADEUS_KEY }),
new HotelbedsAdapter({ apiKey: process.env.HOTELBEDS_KEY }),
],
});
Adding to the Monorepo
New package
-
Create
packages/agents/{domain}/package.json:{ "name": "@otaip/agents-{domain}", "version": "0.1.0", "type": "module", "main": "./dist/index.js", "types": "./dist/index.d.ts", "scripts": { "build": "tsup src/index.ts --format esm --dts" }, "dependencies": { "@otaip/core": "workspace:*" } } -
Add to
pnpm-workspace.yamlpackages glob if not already covered. -
Create
tsconfig.jsonextending../../tsconfig.base.json. -
Add test paths to root
vitest.config.tsinclude array.
Conventions
- Package naming:
@otaip/agents-{domain}for agent packages,@otaip/adapter-{source}for adapters - TypeScript: Strict mode, no
anywithout justification - Financial math: Use string-based decimal arithmetic (no floating point for currency)
- Testing: Vitest. Mock external APIs. Tests must encode domain knowledge.
- ESM: All packages use
"type": "module"with.jsextensions in imports
Agent connections page
OTAIP ships a first-party agent connections view: pick an Orchestrator workflow, see the agent chain, browse the stage bento, inspect neighbors. Edges come from workflows + agent-package workspace deps.
| Artifact | Role |
|---|---|
agents.manifest.json | Authoritative agent roster |
agents.graph.json | Nodes + workflow/package edges for navigation |
docs/agent-map.html | Standalone connections page (Apple-style bento; not a marketing landing) |
docs/assets/telivity/ | Brand logos/favicon from telivity.app (navy/teal/Montserrat) |
| Platform UI → Map | Same graph via GET /api/platform/agent-graph |
Regenerate all three (plus the HTML) with:
pnpm gen:manifest
CI runs pnpm gen:manifest --check so stale artifacts fail the build.
This is not a travel-domain knowledge graph and is unrelated to Graphify / RAG. Do not treat map edges as fare, tax, or lodging domain logic — they only describe how agents are wired in this repo.
Testing
# Run all tests
pnpm test
# Run tests for a specific package
pnpm vitest run packages/agents/lodging/
# Run with verbose output
pnpm vitest run --reporter=verbose
# Typecheck
pnpm typecheck
# Lint
pnpm lint
Test guidelines
- Mock external APIs — never call real APIs from tests
- Test domain edge cases, not just happy paths
- A test that says
expect(result).toBeDefined()is not a test - Include confidence score assertions
- Test error cases (missing input, uninitialized agent)
Quick Reference
| Command | Description |
|---|---|
pnpm install | Install dependencies |
pnpm test | Run all tests |
pnpm typecheck | TypeScript strict check |
pnpm lint | ESLint + Prettier |
pnpm gen:manifest | Regenerate agent manifest, graph, and docs/agent-map.html |
pnpm run data:download | Download reference datasets |
Requirements: Node 20+, pnpm 9+.