Skip to main content
Product · API, SDK, MCP

The Ferry VN API is a REST API for OTAs and technology partners to search voyages, create bookings, issue tickets and receive webhooks across high-speed ferry operators. Comes with a TypeScript SDK, the ferry CLI and an MCP server for AI agents, all on npm.

operators
15+
business routes open to partners
86
booking statuses
8
webhook event codes you can subscribe to
50+
npm packages: SDK, CLI, MCP
3

Figures as of November 2026

What are the steps to book a ticket through the REST API?

Four steps: search voyages, check the seat map, create a booking, then confirm issuance. Creating a booking only holds seats; money is deducted at the NET price when you confirm issuance.

  • POST /bookings creates the booking; POST /bookings/confirmToIssue/{id} issues tickets
  • Status 2 means requested, status 10 means tickets issued
  • GET /bookings/{id} returns the result; some operators take 5–8 minutes
  • No idempotency key: after a timeout, list bookings before retrying
  • API bookings show up in Orders on the Dashboard
Booking flow
Orders list on the agency Dashboard: booking code, customer, route and departure time, payment status and ticket-issuance status of each order

How does the API handle multi-tier pricing and balances?

Every order has three prices: the operator's base price, the NET price an agency pays its parent, and the customer price. Money is deducted automatically at the NET price, recalculated when you confirm issuance.

  • Percentage surcharges compound by tier: three 10% tiers make 33.1%
  • Vouchers lower only the customer price, never the NET price
  • POST /ticket-price-additions/check and /vouchers/check-aplicability run before booking
  • Spending power is balance plus credit limit, checked up the chain
  • Surcharge rules can also be set on the Dashboard under Phí Và Ưu Đãi
Fares and multi-level markup
Additional ticket price rules list on the Dashboard: rule names per route and ticket type, price increase amounts, descriptions and the organization each rule applies to

How do Ferry webhooks work?

When a business event occurs, Ferry sends a POST to the HTTPS address you registered. Treat a webhook as a signal: once it arrives, call the API for the real data.

  • Event codes such as booking.issuedTicket arrive in the x-event-type header
  • 2,000 ms response limit; unsigned and never retried
  • Return 200 at once, queue, deduplicate, reconcile periodically
  • Filter with 6 comparison operators; configure on the Dashboard or API
  • Four organization.* codes can be subscribed to but are never sent
Webhooks

How do the TypeScript SDK and the ferry CLI work?

Use the TypeScript SDK to call the API from code, or the ferry CLI to search voyages and build bookings from the command line. Both use an API key an agency admin creates on the Dashboard.

  • npm install @ferryvn/sdk; CLI: npm install -g @ferryvn/cli
  • SDK runs on Node.js 20+, Bun, Deno, Workers; CLI needs Node.js 22.12+
  • bookingDrafts checks ticket type, price and free seats before creating
  • CLI builds bookings from CSV, confirms before issuing, exports CSV reports
  • Key shown once, full organization access; the SDK refuses to run in browsers
TypeScript SDK
API Key page under Settings on the Dashboard: list of keys with key name, creator, created and edited times, and an Add new button

What can an AI agent do, and not do, through MCP?

AI agents such as Claude use your API key to search voyages, see prices, read bookings and request cancellations. It is read-only by default; ticket issuance and payment are never exposed to the agent.

  • Three write tools appear only with FERRY_MCP_ENABLE_WRITES=true
  • Every write needs a human to confirm
  • A booking an agent creates stops at the unpaid status
  • Default write cap of 20 calls per process, with an audit log
  • Claude Desktop installs from .mcpb; Claude Code runs npx -y @ferryvn/mcp
MCP for AI agents
How it works

Six steps from the integration checklist.

  1. 01

    Get an API key

    An agency admin creates the key under Cài Đặt, tab API Keys; only organizations with a cooperation contract get one. Keep it on your server and send it in the Authorization: Bearer header.

  2. 02

    Pick an environment

    Production is https://api.ferry.vn, with endpoints under /api/v1/. Ferry gives you the UAT base URL in writing; UAT issues real tickets and charges real money, so only try issuance on voyages agreed in advance.

  3. 03

    Look up voyages and prices

    Search voyages by pier and date, fetch the seat map and remaining seats, and check surcharges and vouchers. Re-fetch the seat map right before you create the booking, because availability changes constantly.

  4. 04

    Create the booking

    POST /bookings returns the booking id, status 2 and a payment_expired_at deadline. No money is charged at this step.

  5. 05

    Confirm issuance

    POST /bookings/confirmToIssue/{id}, then read GET /bookings/{id} until the booking reaches status 10 and download the tickets for your customer.

  6. 06

    Turn on webhooks and reconciliation

    Register an HTTPS address that answers 200 in under 2 seconds, and run a daily reconciliation job before going live. The full checklist is on the go-live page.

Search voyages and create a draft booking with @ferryvn/sdk

Look up piers, find voyages and a customer, and create a draft booking; no step charges money. The code stops before issuance because UAT books real tickets too.

Open the API reference (Scalar)
import { FerryClient } from '@ferryvn/sdk';

const ferry = new FerryClient({ apiKey: process.env.FERRY_API_KEY, environment: 'uat' });

const [from] = await ferry.locations.find('Rạch Giá');
const [to] = await ferry.locations.find('phu quoc');
const { data: voyages } = await ferry.voyages.search({ date: '2026-11-10', departureId: from.id, destinationId: to.id });

const [customer] = await ferry.customers.search('0901234567');
const { booking, draft } = await ferry.bookingDrafts.create({
  customerId: customer.id,
  ordererName: 'Nguyễn Văn A',
  contactEmail: 'a@example.com',
  depart: {
    voyageId: voyages[0].voyage.id,
    seats: ['A1'],
    passengers: [{ name: 'Nguyen Van A', dateOfBirth: '1990-05-01', socialId: '079090000001', homeTown: 'Cần Thơ' }],
  },
});
draft.estimatedTotalVnd; // tickets + harbor fees − vouchers
Comparison

A comparison of the engineering work when you integrate each operator yourself and when you use the Ferry VN API.
Integrate each operator yourselfFerry VN API
Operator connectionEach operator has its own booking gateway and login session, and you maintain every connection.Ferry maintains the operator connections; you call one REST API under /api/v1/.
Schedules and seatsYou sync each operator's timetable and seat map yourself.Ferry syncs on a schedule, for example every 30 minutes for the next 7 days; you re-fetch the seat map before checkout.
Passengers and ticket typesYou apply each operator's rules for ticket type by age, nationality and field length.The SDK's bookingDrafts applies the operator's rules locally and reports every problem before anything is sent.
Prices and moneyYou build multi-tier pricing and balance reconciliation yourself.NET and customer prices per agency tier; money is deducted automatically when you confirm issuance.
FAQ

Frequently asked questions

How do I get an API key?

An agency admin clicks Thêm mới under Cài Đặt, tab API Keys on the Dashboard; keys cannot be created through the API. Keys go only to organizations with a cooperation contract, show once and never expire.

Can an API key be read-only?

No. A key represents the whole organization with no permission scopes, including creating, editing and deleting sub-organizations, so keep it on your server. To rotate, create a new key, finish deploying it, then delete the old one.

Is the UAT environment a sandbox?

No. UAT connects straight to operator booking gateways, so confirming issuance there still places real orders and charges real money. Early on, stop at the create-booking step and only try issuance on voyages agreed in advance with Ferry.

Are webhooks signed, and are they retried on failure?

Neither: no signature, no retries, and a 2,000 ms response timeout, so a dropped event is lost. Return 200 at once, queue the payload, call the API for the real data and reconcile periodically. The SDK can parse payloads and compare a secret token.

What are the API rate limits?

Ferry counts by source IP, not API key: 50 calls per 100 milliseconds, about 500 per second. Confirm-issuance allows only 5 calls per 60 seconds. On HTTP 429, back off exponentially from 1 to 30 seconds, then retry.

Can I retry a create-booking call that timed out?

Not straight away: there is no idempotency key, so two create calls make two bookings. List bookings by phone number, voyage id and travel date; if one matches, confirm issuance with its id, and create a new one only if none exists.

Can the MCP server issue tickets or take payment for me?

No. Ticket issuance and payment are never exposed to AI agents. The server is read-only by default; write tools must be switched on and each call needs a human to confirm. A booking an agent creates stops at the unpaid status.

Is there an SDK for languages other than TypeScript?

The official SDK is TypeScript only. Other languages call the REST API directly with an API key; look up each endpoint's schema on Scalar at api.ferry.vn/docs. The filtered OpenAPI specification is in the SDK repository on GitHub.

Documentation

Developer documentation

For

Are you an OTA or a technology partner?

This page describes the technical product. To learn how the partnership works, get UAT access and have the Ferry VN engineers alongside you through go-live, see the API partners page.

API partners page

Tell us about your system. A Ferry VN engineer will get in touch to discuss the integration scope.