Skip to content

General conventions

6 min read

This page details the foundational technical conventions governing all Ferry APIs. Understanding rules on data formats, cursor pagination, sorting constraints, and duplicate protection helps you build a resilient integration layer and avoid operational pitfalls.

The Ferry platform adheres to the following communication standards:

ItemTechnical specificationIntegration guidance
Prefix and version/api/v1/<resource>All current APIs operate on version v1
Payload size limitMaximum 1 MB per requestOptimize request payloads for large passenger groups
Schema filtering (whitelist)Undeclared request fields are silently strippedMisspelled field names are discarded without error messages
Data validation errorHTTP 422 Unprocessable EntityDistinct from 400; 422 indicates data constraint violations
Request trace identifierx-request-id headerGenerate a unique UUID per call to facilitate log investigations
  • Responses are compressed automatically except for event streams; your HTTP client must accept compressed content.
  • Message language is selected through the language header and defaults to Vietnamese.
  • Ferry generates a trace ID if x-request-id is omitted.
  • x-public, x-internal, and x-admin are OpenAPI documentation labels only; sending them does not change access permissions.
  • API-key responses may omit fields that the Dashboard displays even when you have access to the record. This can be a missing partner-field declaration; report it to Ferry, as there is no client-side workaround.

Composite cursor pagination is the default. Page-number pagination still exists in some legacy groups; that format returns a next-page flag and total record count rather than a cursor.

The diagram below illustrates the cursor pagination workflow between the partner system and Ferry:

sequenceDiagram
autonumber
participant Partner as Partner System
participant Ferry as Ferry API
Partner->>Ferry: Initial request with limit parameter
Ferry-->>Partner: Return record list with nextCursor
loop While nextCursor is present
Partner->>Ferry: Subsequent request with cursor=nextCursor
Ferry-->>Partner: Next page of records with updated nextCursor
end
Note over Partner,Ferry: Stop when nextCursor is null or target count reached

Operational pagination rules:

  1. Record ceiling (limit): Defaults to 10 records per page. The maximum ceiling is 50 records/page (the HTML voyage table allows up to 100). Sending limit=200 silently clamps down to 50 records without throwing an error.
  2. Cursor nature: The nextCursor token encodes pagination state based on sort ordering and record identifiers. Use this token only within your current pagination loop; do not persist cursors in long-term database storage.
  3. Malformed cursor handling: Submitting an invalid cursor silently resets retrieval to the first page rather than returning an error. Your client loop must incorporate safety checks (such as maximum iteration bounds) to avoid infinite loops.

The Ferry platform enforces default sorting by descending record identifier across all collection endpoints.

If your client interface requires alternative presentation orders (such as lowest price first or earliest departure), fetch the complete record set to your server and perform local sorting before rendering.

Endpoints support filtering on predefined attributes documented for each resource (e.g. booking status, date intervals, or partner identifiers). Rely on standard query parameters documented in Scalar rather than custom internal parameters.

Dynamic filters support 13 operators: eq, neq, contains, ncontains, gt, gte, lt, lte, in, nin, between, empty, nempty. Conditions are combined with AND, not OR. Fields must be on the resource’s allow-list; the groups with such lists are bookings, customers, organizations, routes, transactions, users, and locations.

The booking list returns a deprecation warning. Dynamic filters can still be applied, but no replacement group exists at the partner tier. Do not build logic that depends on these filters.

AttributeStandard specificationHandling rule
Search date parameterYYYY-MM-DDSubmit date strings according to local Vietnam time (GMT+7)
Returned timestampsISO-8601 string in UTCConvert to GMT+7 before presenting to end customers
Currency unitVietnamese Dong (VND)Stored and returned as integers without decimal fractions

The most frequent integrator error is directly displaying raw UTC timestamps in end-user interfaces, resulting in departure times showing 7 hours earlier than actual schedule.

Payloads have no separate currency-unit or business-time-zone field. All monetary amounts are VND; bare dates use the operator’s local time, not your server’s time zone. The business day changes at midnight Vietnam time.

Ferry does not support automated idempotency key headers for booking creation calls.

Safe implementation guidelines for partners:

  1. Maintain business reference codes: Generate an external transaction reference ID on your end for every customer order.
  2. Inspect state before retrying: Upon experiencing a connection interruption during booking creation, query the recent bookings list to verify whether the order was recorded before issuing a retry.
  3. Internal ticketing locks: For ticket issuance confirmation calls, Ferry maintains internal booking-level concurrency locks to prevent duplicate balance deductions or simultaneous double issuance.

A confirmation retried while work is running usually returns an in-progress error, not a replay of the previous result. Webhooks also have no deduplication key; deduplicate using the resource identifier in the event. See Webhooks.

When querying lists (voyages, orders, transactions), responses may return an empty array [] accompanied by HTTP 200.

Important considerations:

  • An empty list may indicate no matching records exist (sailings sold out or not yet opened), but can also occur if backend database connections encounter transient interruptions.
  • Do not use a single empty query response as justification to purge locally cached synchronization data. If an empty list appears unexpectedly, retry after several seconds or verify with alternative queries.
MisconceptionRealityCorrect practice
Requesting sailings sorted by cheapest fareThe system always returns records sorted by ID descendingFetch data to your server and sort locally in your app
Requesting 200 records per pageThe system clamps page size to a maximum of 50Iterate through subsequent pages using cursor pagination
Automatically retrying booking creation on network blipsProduces duplicate bookings and holds ghost seatsQuery recent bookings before deciding whether to retry
Presenting API timestamps directly to customersTimestamps are formatted in UTCConvert timestamps to Vietnam local time (GMT+7)
Treating every HTTP 422 as a payload-format errorStatus 422 also covers business failures, insufficient funds, and work already in progressInspect the code and message; do not retry automatically

This guide relates to common query parameters present across most endpoint groups on the Scalar UI — Ferry VN API:

Parameter in ScalarDescription and convention
limitDesired record count per page (defaults to 10, maximum 50)
cursorCursor string obtained from nextCursor on previous page
x-request-idOptional header carrying a client-generated request correlation ID
GuideRecommended next reading
Authentication with an API keyHeader format specifications and authentication error codes
Bookings: the life cycle of one bookingApplying duplicate-order prevention to the booking workflow
Fares and multi-level markupCurrency rounding conventions and fare structure details
Errors and how to handle themComplete error code reference and safe retry patterns