Skip to content

Rate limits

7 min read

The Ferry API throttles call frequency based on the origin IP address of the partner system to safeguard platform stability. This page documents the system-wide global limit, specific tightened rules applied during ticket issuance confirmation, and other operational boundaries your integration must handle.

Ferry enforces a single default rate-limiting policy across all public API endpoints: 50 requests per 100 milliseconds, counted by origin IP address.

The evaluation window is measured in milliseconds (100 ms equals 1/10th of a second). Consequently, 50 requests per 100 ms allows sustained throughput of up to approximately 500 requests per second.

This ceiling is designed to prevent aggressive traffic bursts or denial-of-service spikes. A properly engineered booking integration operating under normal production conditions rarely approaches this threshold.

The diagram below shows the path of a call through the rate-limiting layers and the outcome of each branch:

flowchart TB
CALL["A call from your system"]
IP{"Layer 1 — counted per IP address<br/>50 calls per 100 milliseconds"}
OVER{"Layer 2 — ticket issuance confirmation step?<br/>5 calls per 60 seconds"}
LOCK{"Layer 3 — ticket issuance lock per booking<br/>valid for 600 seconds"}
SOFT{"Layer 4 — ticket issuance soft limit<br/>5 times per 60 seconds"}
OK["Ferry processes normally"]
E429["You get 429<br/>call again with growing backoff"]
LOCKED["You get business error 120100<br/>wait at least 60 seconds then call again"]
SOFTED["You get a business error<br/>that is not 429"]
CALL --> IP
IP -->|"over the IP cap"| E429
IP -->|"within the cap"| OVER
OVER -->|"not the confirmation step"| OK
OVER -->|"confirmation step, over 5 calls in 60 seconds"| E429
OVER -->|"confirmation step, within the cap"| LOCK
LOCK -->|"the booking already has another issuance in flight"| LOCKED
LOCK -->|"the booking is free"| SOFT
SOFT -->|"over 5 times in 60 seconds"| SOFTED
SOFT -->|"within the cap"| OK

When traffic exceeds the allowed threshold, the system returns HTTP 429 (Too Many Requests) accompanied by the timestamp and requested path.

Key operational realities:

  • No customized error message: The response body contains the standard framework message. Your error parser should not expect localized strings in this payload.
  • Ferry does not log 429s as server errors: The platform error filter records logs starting only at status 500. Ferry will not automatically detect that your client is being throttled; monitoring 429 response rates is strictly the partner’s responsibility.

Recommended retry strategy: when receiving HTTP 429, implement randomized exponential backoff (starting at 1 second, doubling per attempt, capped at 30 seconds, up to 5 attempts).

Most endpoints operate under the global policy. Three specific endpoint categories carry distinct rules:

Call groupLimit appliedCounting basisOperational reality
System-wide (default)50 calls / 100 msOrigin IPApplies to lookups, order creation, general management
Ticket issuance confirmation5 calls / 60 secondsOrigin IPTightened to protect upstream ferry operator gateways
Public booking search60 calls / 60 secondsOrigin IPIntended for public end-customer search portals
Real-time SSE event streamExempt from limitsNot applicableReserved for web sessions, not available to API keys

Two mechanisms sharing the same 5-per-60-seconds number

Section titled “Two mechanisms sharing the same 5-per-60-seconds number”

The ticket issuance confirmation step features two distinct protective mechanisms sharing the figure of 5 attempts per 60 seconds:

MechanismCounting basisResponse when exceeded
Network-layer rate limitYour client IP addressHTTP 429 with standard message
Business-layer soft limitSpecific booking codeBusiness error (6-digit code), not 429

The per-booking soft limit allows at most 5 confirmation attempts within 60 seconds on the same booking. This safeguards against users repeatedly clicking confirmation buttons while an initial issuance is in progress.

Retry rules:

  • On HTTP 429: Pause, apply exponential backoff, and retry.
  • On Business error: Query current booking status first; blind retries simply reproduce the same business failure.

Beyond time-based request throttling, partners must account for several structural technical limits:

LimitCeiling valueBehavior when exceeded
Inbound payload sizeMaximum 1 MBRequest rejected at perimeter layer
Page size in paginationMaximum 50 (100 on select APIs)Silently clamped: system returns maximum page size without throwing an error
Booking ticketing lockHeld for 600 secondsSecond concurrent request receives error code 120100
Booking payment lockHeld for 180 seconds, independently of the issuance lockConcurrent payment receives error code 120100
Email verification codes5 attempts / 15 minutes per emailSixth attempt onward is rejected
Webhook receiver timeout2,000 millisecondsEndpoints slower than 2 seconds cause permanent event loss

Two key takeaways:

  • Silent pagination clamping: Requesting 200 records and receiving 50 is normal behavior. Do not assume data is exhausted simply because fewer items arrived; follow the pagination cursor.
  • Webhook timeout is your responsibility: Your webhook receiver must respond within 2 seconds. Ingest payloads into an asynchronous queue and return HTTP 200 immediately.

Five sequential resolution steps:

  1. Reduce parallel worker threads instead of adding concurrency: Limits count by IP address; increasing thread counts saturates the counter faster and extends lockouts.
  2. Apply randomized exponential backoff (jitter): Introduce random jitter into retry delays to prevent synchronized wave storms.
  3. Decouple read and write queues: Prevent background voyage lookups from exhausting the narrow 5-call/60-second limit required for ticketing confirmation.
  4. Log request trace IDs: Record the timestamp, trace ID, and origin IP of every 429 response to assist in reconciliation.
  5. Contact Ferry if using shared egress IPs: Frequent 429 errors under moderate traffic usually indicate an egress IP shared with another active tenant.
MisconceptionRealityConsequence
Quotas are assigned per API keyLimiter evaluates traffic by IP addressTwo partners behind a shared NAT block each other
Limit is 50 calls per 100 secondsUnit is milliseconds (~500 req/s)Designing needlessly sluggish request queues
All rate limits return HTTP 429Per-booking soft limits return business errorsRetry handlers skip business exceptions entirely
Ferry monitors and alerts on 429sServer logs do not record 429 at error levelOutages persist undetected unless monitored locally
When throttled, spawn more threadsMore threads saturate the IP counter fasterThrottling duration increases
Receiving fewer records than requested is a bugSystem silently clamps pages to 50–100 itemsStopping pagination prematurely, missing data
Delayed webhooks reflect Ferry transmission lag2-second timeout is measured at your receiverFixing the wrong side while events continue dropping

Rate limits are not documented inside specific groups on https://api.ferry.vn/docs, as OpenAPI specifications do not reflect dynamic throttling:

Inspection targetGroup in API reference
Ticket issuance confirmation (tightened to 5 calls / 60 s)Bookings
Endpoints handling large payloads (subject to 1 MB cap)Bookings, Transactions
Read-only endpoints using global policyVoyages, Organizations, Customers
PageWhen to read
Errors and how to handle themLearn how to parse HTTP 429 and business error codes
ConventionsUnderstand cursor pagination rules and silent clamping
Booking flowDesign booking workflows around throttled operations
TicketingUnderstand ticketing locks and soft limits during issuance
WebhooksBuild event receivers that respond within 2,000 ms
Go liveAdd rate-limit monitoring to your pre-launch checklist
  • SLA on custom partner throughput: The default 50 calls/100 ms policy is a technical baseline rather than a contracted Service Level Agreement (SLA). Ferry may adjust thresholds based on aggregate cluster load.
  • Presence of Retry-After headers: It remains unconfirmed whether all 429 responses reliably return a Retry-After header; client retry loops must manage backoff intervals independently.