Skip to content

Errors and how to handle them

11 min read

When an API call fails, the system responds with standard HTTP status codes accompanied by detailed business error payloads. This page provides a comprehensive reference of error codes partners encounter in production, explains the three concurrent error response formats, and outlines which errors may be safely retried.

The platform error handling layer structures response data based on the origin of the failure. Your partner error parsing logic must handle all three formats:

FormatHTTP statusHow to recognise
Standard HTTP errorPreserves operation status (401, 403, 404…)Contains timestamp and request path, no six-digit error code
Service business errorAlways returns 422Contains a six-digit error code inside the returned error list
Unexpected errorAlways returns 500Generic system message, contains only timestamp and request path

The diagram below shows how API error responses are categorized across the three shapes:

flowchart LR
A["API call fails"] --> B["Shape 1 — Standard HTTP error"]
A --> C["Shape 2 — Business error from service"]
A --> D["Shape 3 — Unexpected failure"]
B --> B1["Has timestamp and path, no 6-digit code"]
C --> C1["Always 422, carries 6-digit code in list"]
D --> D1["Always 500, generic system message"]
B1 --> X["Your parser must handle all 3 shapes"]
C1 --> X
D1 --> X

How to write a resilient error parser: evaluate the HTTP status code first, attempt to parse the six-digit code, and always prepare for scenarios where the code is absent. Never assume every failure shares a single identical schema.

The trap where the error code goes missing

Section titled “The trap where the error code goes missing”

In specific edge cases where a business exception is re-wrapped by a transport handler, the returned error list may be empty. The partner receives an HTTP 422 response with no six-digit error code attached — only the status code, timestamp, and request path.

Mandatory handling rules:

  • Do not assume every 422 response includes an error code. Your fallback branch must handle empty error lists safely.
  • When encountering an HTTP 422 without an error code, do not retry blindly; log the request alongside the trace ID and report it to Ferry.
  • Log both the HTTP status and the six-digit error code to facilitate rapid troubleshooting.

Ferry configures input validation layers to return 422 instead of the conventional 400:

  • The error structure is a flat object keyed by field name, not an array. Each key represents the invalid field, and the value describes the violated constraints. Nested fields use nested object structures.
  • Undeclared fields are silently stripped: If you submit parameters not defined in the API contract, the system discards them silently without throwing an error.

The six HTTP status codes partners encounter during API integration:

StatusRealityShould you retry?
401API key missing, malformed, invalid secret, or revokedNo — stop request loop, verify API key configuration
403Missing permissions or internal authorization service outageRetry exactly once; if persistent, contact Ferry support
422Invalid input data or business constraint failureNo, unless code indicates an in-flight process lock
429Rate limit exceededYes, follow backoff guidelines on the Rate limits page
500Unexpected internal server error on Ferry sideYes, retry with exponential backoff
200 with empty arrayMay represent a swallowed system failure rather than end of dataDo not retry immediately — see the silent failures section

The diagram below shows the decision tree based on HTTP status codes:

flowchart TD
A["Receive API error response"] --> B{"What is the HTTP status code?"}
B -->|401| C["Key missing, wrong, or revoked"]
B -->|403| D["Missing permission or auth system failure"]
B -->|422| E["Bad data or business constraint failure"]
B -->|429| F["Rate limit exceeded"]
B -->|500| G["Unexpected server error on Ferry side"]
C --> C1["Do not retry — check key then stop"]
D --> D1["Retry once, report to Ferry if persistent"]
E --> E1["Do not retry, unless lock is active"]
F --> F1["Retry using exponential backoff"]
G --> G1["Retry with randomized exponential backoff"]

Routes dedicated to sending Zalo ZNS messages are not currently granted to partner API keys. Consequently, invoking these endpoints always yields an HTTP 403, even if your API key is fully valid. Do not build automated partner workflows that depend on calling these endpoints directly.

Alongside HTTP status codes, business errors (typically paired with HTTP 422) return a six-digit code indicating the exact failure reason:

CodeBusiness groupMeaningHandling guidanceRetryable?
110101OperatorLogin to the operator portal failedRetry with backoff; contact Ferry if the failure persistsYes
110102OperatorSession authentication failure with operatorWait for background recovery job, then retryYes
110295OperatorVoyage is sold out or locked at operator levelNotify customer to select another voyage or cabin classNo
120100BookingAnother ticketing process is active for this bookingWait at least 60 seconds for ticketing lock releaseYes
120101BookingCustomer wallet balance insufficient for paymentRequest customer to top up wallet balanceNo
120105BookingBooking input data invalid (passenger/ticket format)Verify passenger details, identity documents, and fixNo
120110BookingBooking state does not allow this operationQuery current booking status before proceedingNo
120120BookingOrganization account balance insufficientTop up agency organization balanceNo
120205BookingBooking-level balance insufficient for debitCheck owning organization balance and top upNo
130205CustomerCustomer account balance insufficientCustomer must top up account balanceNo
130206CustomerCustomer reservation deposit insufficientVerify reservation credit limits and customer balanceNo
140201OrganizationInvalid input for a balance operationCheck and correct the transaction dataNo
140101OrganizationOrganization balance insufficientCheck and top up the organization’s balanceNo
150105OrderDuplicate passenger identity document numberCheck and correct duplicate passenger detailsNo
150102OrderOrder status does not allow this operationQuery order status to verify current phaseNo
160105CancellationBalance insufficient to pay cancellation feeTop up organization balance, then recreate requestNo
160110CancellationDeparture date or time is missing, preventing cancellation creationSend the voyage identifier and trace ID to Ferry for investigationNo
220000NotificationNotification failure with an undetermined causeLog the trace ID and call context; contact FerryNot automatically
220105NotificationNotification input data invalid (collision code)Check context: booking email or cancellation emailNo
220110NotificationState invalid for dispatching notificationsCheck booking or cancellation status before sendingNo

The first two digits identify the business range:

RangeGroupPartner exposure
11xxxxOperatorYes, during ticket issuance
12xxxxBookingYes, during creation and confirmation
13xxxxCustomerYes, data and balances
14xxxxOrganizationYes, child organizations and credit limits
15xxxxOrderYes, pricing and cancellation
16xxxxCancellationYes, cancellation requests
19xxxxTicket trackingNo partner route
21xxxxInvoiceRoute group is locked
22xxxxNotificationsYes, booking notifications
80xxxxBankCalled by banks, not partners
90xxxxAuthorizationNever exposed as a code; response is 403
91xxxxInternal ledgerNot partner business

For 120100, waiting at least 60 seconds does not guarantee lock release: the issuance lock lasts 600 seconds and the payment lock 180 seconds. Confirmation also has a soft limit of 5 calls per 60 seconds per booking; this produces a business error rather than the HTTP 429 rate limit. Keep polling status instead of sending confirmation calls in a burst.

Three places where error codes cause confusion

Section titled “Three places where error codes cause confusion”

Three error codes in the notification sending range are shared between booking notifications and cancellation request notifications:

  • 220000: Unknown error
  • 220105: Invalid input data
  • 220110: Invalid state

When receiving code 220105, you cannot determine whether the error originated from booking mailer or cancellation mailer by code number alone; you must inspect the context of the originating API call.

Different “insufficient balance” codes

Section titled “Different “insufficient balance” codes”

Multiple error codes signify insufficient funds, but the entity responsible for topping up differs:

CodeMeaningWho must top up
120205Balance at booking level is insufficient to debitThe organization owning the booking
130205Customer balance is insufficientThat specific customer
160105Insufficient balance to open cancellation requestThe organization submitting the request
120101Customer balance insufficient at booking stepThe customer
120120Organization balance insufficient at booking stepThe agency organization
130206Reservation deposit balance insufficientThe customer

Note: code 160105 returns as an object containing both code and message, rather than a raw number. None of the insufficient balance codes should be retried automatically, as balances do not replenish without manual deposits.

The error-code labelling helper only registers six groups

Section titled “The error-code labelling helper only registers six groups”

The default descriptive error label helper only registers 6 groups (primarily booking and operator ranges). Codes outside these groups frequently return the fallback label “unknown”. Therefore, partner systems should classify errors based on the numeric code rather than textual labels.

A category of system issues that do not produce standard 4xx or 5xx codes:

  1. Empty array instead of system failure: List query APIs that encounter internal database or authorization faults may catch exceptions and return HTTP 200 with an empty array [] instead of raising HTTP 500.
  2. Corrupted pagination cursor falls back to first page: If a cursor string fails decoding, the system returns the first page of results without throwing an error.
  3. Invalid sorting parameter replaced silently: Only supported sorting fields are honored; invalid parameters are replaced by default sorting on _id.

Defensive practices:

  • Set up alerts for queries that normally yield data: an unexpected empty array may indicate system downtime rather than no matching records.
  • Never use an empty list result as sole justification to purge or invalidate local records.
  • Log pagination cursors to detect loops that repeatedly return the first page.
MisconceptionRealityConsequence
Every 422 carries an error codeEdge cases leave the error list emptyParser throws on empty data, losing diagnostic insight
Error list is always an arrayIt is a flat object keyed by field nameArray iterators fail to find fields, missing errors
Invalid data returns 400Ferry returns HTTP 422Handlers listening for 400 never trigger
HTTP 403 always means key revokedAuthorization system may be degradedUnnecessarily rotating keys during platform downtime
Empty array means no records existCan represent a swallowed server exceptionAccidentally deleting or cancelling local records
Low balance resolves on retryBalance does not replenish automaticallyUseless retry loops delaying order resolution

Detailed schemas and parameter definitions for each error category can be inspected under corresponding groups at https://api.ferry.vn/docs:

Inspection goalGroup in API reference
Error codes for booking creation and ticketing confirmationBookings
Operator-side errors and session authentication failuresOperator
Error codes for order price recalculation and cancellationOrder
Organization balance and credit limit error codesOrganizations
Customer balance and customer profile error codesCustomers
Notification mailer error codesBooking mailer
Read-only call for diagnosing authorization healthVoyage
PageWhen to read
ConventionsUnderstand cursor pagination, sorting, and data conventions
AuthenticationHandle 401 errors and manage API key lifecycles
Rate limitsHandle 429 errors and distinguish IP limits from booking locks
Booking flowUnderstand the context generating code 120100 and balance codes
PaymentsGuidance on topping up balances to resolve funding errors
Ticket issuanceHandle partial ticketing failures with ferry operators
Cancellation and refundReview cancellation workflows and 16xxxx codes
Go liveReview the pre-production integration checklist
ItemStatus
Whether Zalo ZNS routes will open to API keysPending product decision on authorization role mapping.
Edge-case 422 errors with empty error listsUnder review; partners must maintain safe fallback checks.
Expanding descriptive labels beyond six groupsNo roadmap announced; partners should rely on numeric codes.