- Developers
- Errors and how to handle them
Errors and how to handle them
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.
Three error shapes existing side by side
Section titled “Three error shapes existing side by side”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:
| Format | HTTP status | How to recognise |
|---|---|---|
| Standard HTTP error | Preserves operation status (401, 403, 404…) | Contains timestamp and request path, no six-digit error code |
| Service business error | Always returns 422 | Contains a six-digit error code inside the returned error list |
| Unexpected error | Always returns 500 | Generic 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 --> XHow 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.
Validation failures return 422, not 400
Section titled “Validation failures return 422, not 400”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.
HTTP status table
Section titled “HTTP status table”The six HTTP status codes partners encounter during API integration:
| Status | Reality | Should you retry? |
|---|---|---|
| 401 | API key missing, malformed, invalid secret, or revoked | No — stop request loop, verify API key configuration |
| 403 | Missing permissions or internal authorization service outage | Retry exactly once; if persistent, contact Ferry support |
| 422 | Invalid input data or business constraint failure | No, unless code indicates an in-flight process lock |
| 429 | Rate limit exceeded | Yes, follow backoff guidelines on the Rate limits page |
| 500 | Unexpected internal server error on Ferry side | Yes, retry with exponential backoff |
| 200 with empty array | May represent a swallowed system failure rather than end of data | Do 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"]The Zalo ZNS group always returns 403
Section titled “The Zalo ZNS group always returns 403”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.
Error codes partners actually encounter
Section titled “Error codes partners actually encounter”Alongside HTTP status codes, business errors (typically paired with HTTP 422) return a six-digit code indicating the exact failure reason:
| Code | Business group | Meaning | Handling guidance | Retryable? |
|---|---|---|---|---|
110101 | Operator | Login to the operator portal failed | Retry with backoff; contact Ferry if the failure persists | Yes |
110102 | Operator | Session authentication failure with operator | Wait for background recovery job, then retry | Yes |
110295 | Operator | Voyage is sold out or locked at operator level | Notify customer to select another voyage or cabin class | No |
120100 | Booking | Another ticketing process is active for this booking | Wait at least 60 seconds for ticketing lock release | Yes |
120101 | Booking | Customer wallet balance insufficient for payment | Request customer to top up wallet balance | No |
120105 | Booking | Booking input data invalid (passenger/ticket format) | Verify passenger details, identity documents, and fix | No |
120110 | Booking | Booking state does not allow this operation | Query current booking status before proceeding | No |
120120 | Booking | Organization account balance insufficient | Top up agency organization balance | No |
120205 | Booking | Booking-level balance insufficient for debit | Check owning organization balance and top up | No |
130205 | Customer | Customer account balance insufficient | Customer must top up account balance | No |
130206 | Customer | Customer reservation deposit insufficient | Verify reservation credit limits and customer balance | No |
140201 | Organization | Invalid input for a balance operation | Check and correct the transaction data | No |
140101 | Organization | Organization balance insufficient | Check and top up the organization’s balance | No |
150105 | Order | Duplicate passenger identity document number | Check and correct duplicate passenger details | No |
150102 | Order | Order status does not allow this operation | Query order status to verify current phase | No |
160105 | Cancellation | Balance insufficient to pay cancellation fee | Top up organization balance, then recreate request | No |
160110 | Cancellation | Departure date or time is missing, preventing cancellation creation | Send the voyage identifier and trace ID to Ferry for investigation | No |
220000 | Notification | Notification failure with an undetermined cause | Log the trace ID and call context; contact Ferry | Not automatically |
220105 | Notification | Notification input data invalid (collision code) | Check context: booking email or cancellation email | No |
220110 | Notification | State invalid for dispatching notifications | Check booking or cancellation status before sending | No |
The first two digits identify the business range:
| Range | Group | Partner exposure |
|---|---|---|
11xxxx | Operator | Yes, during ticket issuance |
12xxxx | Booking | Yes, during creation and confirmation |
13xxxx | Customer | Yes, data and balances |
14xxxx | Organization | Yes, child organizations and credit limits |
15xxxx | Order | Yes, pricing and cancellation |
16xxxx | Cancellation | Yes, cancellation requests |
19xxxx | Ticket tracking | No partner route |
21xxxx | Invoice | Route group is locked |
22xxxx | Notifications | Yes, booking notifications |
80xxxx | Bank | Called by banks, not partners |
90xxxx | Authorization | Never exposed as a code; response is 403 |
91xxxx | Internal ledger | Not 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”A real code collision in the 22xxxx range
Section titled “A real code collision in the 22xxxx range”Three error codes in the notification sending range are shared between booking notifications and cancellation request notifications:
220000: Unknown error220105: Invalid input data220110: 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:
| Code | Meaning | Who must top up |
|---|---|---|
120205 | Balance at booking level is insufficient to debit | The organization owning the booking |
130205 | Customer balance is insufficient | That specific customer |
160105 | Insufficient balance to open cancellation request | The organization submitting the request |
120101 | Customer balance insufficient at booking step | The customer |
120120 | Organization balance insufficient at booking step | The agency organization |
130206 | Reservation deposit balance insufficient | The 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.
Silent failures
Section titled “Silent failures”A category of system issues that do not produce standard 4xx or 5xx codes:
- 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. - 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.
- 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.
Common misconceptions
Section titled “Common misconceptions”| Misconception | Reality | Consequence |
|---|---|---|
| Every 422 carries an error code | Edge cases leave the error list empty | Parser throws on empty data, losing diagnostic insight |
| Error list is always an array | It is a flat object keyed by field name | Array iterators fail to find fields, missing errors |
| Invalid data returns 400 | Ferry returns HTTP 422 | Handlers listening for 400 never trigger |
| HTTP 403 always means key revoked | Authorization system may be degraded | Unnecessarily rotating keys during platform downtime |
| Empty array means no records exist | Can represent a swallowed server exception | Accidentally deleting or cancelling local records |
| Low balance resolves on retry | Balance does not replenish automatically | Useless retry loops delaying order resolution |
Where to look in Scalar
Section titled “Where to look in Scalar”Detailed schemas and parameter definitions for each error category can be inspected under corresponding groups at https://api.ferry.vn/docs:
| Inspection goal | Group in API reference |
|---|---|
| Error codes for booking creation and ticketing confirmation | Bookings |
| Operator-side errors and session authentication failures | Operator |
| Error codes for order price recalculation and cancellation | Order |
| Organization balance and credit limit error codes | Organizations |
| Customer balance and customer profile error codes | Customers |
| Notification mailer error codes | Booking mailer |
| Read-only call for diagnosing authorization health | Voyage |
Read next
Section titled “Read next”| Page | When to read |
|---|---|
| Conventions | Understand cursor pagination, sorting, and data conventions |
| Authentication | Handle 401 errors and manage API key lifecycles |
| Rate limits | Handle 429 errors and distinguish IP limits from booking locks |
| Booking flow | Understand the context generating code 120100 and balance codes |
| Payments | Guidance on topping up balances to resolve funding errors |
| Ticket issuance | Handle partial ticketing failures with ferry operators |
| Cancellation and refund | Review cancellation workflows and 16xxxx codes |
| Go live | Review the pre-production integration checklist |
Open questions
Section titled “Open questions”| Item | Status |
|---|---|
| Whether Zalo ZNS routes will open to API keys | Pending product decision on authorization role mapping. |
| Edge-case 422 errors with empty error lists | Under review; partners must maintain safe fallback checks. |
| Expanding descriptive labels beyond six groups | No roadmap announced; partners should rely on numeric codes. |