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.

01Search
02Price
03Book
04Ticket
05Settle
The Otaip agent pipeline — typed agents, one shared contract, machine speed.

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 confidence field (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.md for the full development workflow
  • Connect framework: See packages/connect/GUIDE.md for multi-supplier adapter setup (Sabre, Amadeus, Navitaire)
  • Adapter status: See docs/architecture/ADAPTER_STATUS.md for what's implemented
  • Demo scripts: Run pnpm --filter demo book to 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/.

POST/api/v1/agents/search
POST/api/v1/agents/price
POST/api/v1/agents/book
POST/api/v1/agents/ticket
GET/api/v1/agents/status
Core agent endpoints — typed, schema-validated, contract-gated.

Developer 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:

ErrorWhen to throw
AgentNotInitializedErrorexecute() called before initialize()
AgentInputValidationErrorBad input — specify field and reason
AgentDataUnavailableErrorExternal data source is down or missing
AgentErrorBase 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):

ScoreMeaning
1.0Exact match or deterministic result
0.7-0.9High-confidence fuzzy match
0.5-0.7Partial match, may need review
0Not 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

  1. 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:*"
      }
    }
    
  2. Add to pnpm-workspace.yaml packages glob if not already covered.

  3. Create tsconfig.json extending ../../tsconfig.base.json.

  4. Add test paths to root vitest.config.ts include array.

Conventions

  • Package naming: @otaip/agents-{domain} for agent packages, @otaip/adapter-{source} for adapters
  • TypeScript: Strict mode, no any without 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 .js extensions 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.

ArtifactRole
agents.manifest.jsonAuthoritative agent roster
agents.graph.jsonNodes + workflow/package edges for navigation
docs/agent-map.htmlStandalone connections page (Apple-style bento; not a marketing landing)
docs/assets/telivity/Brand logos/favicon from telivity.app (navy/teal/Montserrat)
Platform UI → MapSame 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

CommandDescription
pnpm installInstall dependencies
pnpm testRun all tests
pnpm typecheckTypeScript strict check
pnpm lintESLint + Prettier
pnpm gen:manifestRegenerate agent manifest, graph, and docs/agent-map.html
pnpm run data:downloadDownload reference datasets

Requirements: Node 20+, pnpm 9+.