Partner API v1
REST API for partner backends. Read the catalogue, quote a charter, create a reservation or sell event tickets. Customers pay through hosted checkout.
https://naczarter.pl/api/v1
On this page
Access & quick start
This is an authenticated, server-to-server API for partner storefronts. It is separate from browser WebMCP and remote MCP. A storefront is the partner brand whose yacht catalogue and pricing settings apply to the API key.
Request a key, the required scopes and your payment return URLs from NaCzarter. Send Authorization: Bearer <api-key> on every request. For POST and PUT, also send Content-Type: application/json.
Keep the API key on your server. Store it as a secret such as NACZARTER_API_KEY, never in NEXT_PUBLIC_*, browser JavaScript, mobile app bundles or a public repository. Your frontend calls your backend; your backend calls this API.
Keys are issued for one storefront and shown only once at creation. Use only the scopes you need. Scopes do not imply one another: a booking integration needs both read and book; a ticket integration normally needs read and tickets. Coordinate key rotation and revocation with NaCzarter.
// Run on your server, never in a browser component.
const key = process.env.NACZARTER_API_KEY;
if (!key) throw new Error("NACZARTER_API_KEY is not configured");
const response = await fetch("https://naczarter.pl/api/v1/yachts?limit=12", {
headers: { Authorization: `Bearer ${key}`, Accept: "application/json" },
cache: "no-store",
});
if (!response.ok) throw new Error(`Partner API: HTTP ${response.status}`);
const { yachts, total } = await response.json();
// Render selected public fields. Do not send the key to the client.This example only reads the catalogue. The write examples below create real reservations or seat holds when sent with live credentials. Do not assume a test-looking key prefix selects a sandbox; agree a test environment with NaCzarter before testing writes.
Endpoints
All paths below are relative to /api/v1. Requests and responses use JSON. Optional request fields should be omitted when unused, not set to null.
| Method and path | Scope | Purpose |
|---|---|---|
| GET /yachts | read | Storefront yacht catalogue |
| GET /yachts/{slug} | read | Yacht details and translated description |
| GET /availability/{slug} | read | Six-month unavailability window |
| GET /quote | read | Current price and availability for dates |
| POST /bookings | book | Create a reservation and start hosted checkout |
| GET /orders/{id} | read | Order status, with guestToken |
| GET /events | read | Global event catalogue and on-sale ticket pools |
| POST /tickets | tickets | Reserve seats and start hosted PayU checkout |
| GET /tickets/{id}/participants | tickets | Payment flag and crew list, with manifest token |
| PUT /tickets/{id}/participants | tickets | Save crew details for a paid ticket |
Yacht reads and bookings use the key's storefront catalogue and pricing markup. A yacht outside that catalogue returns 404. Events are different: the event list is global public content, not filtered by storefront. Created tickets belong to the key's storefront.
Catalogue & quotes
| Endpoint | Parameters | Response |
|---|---|---|
GET /yachts | limit: 1–50 (default 24); offset: non-negative integer (default 0). | { total, limit, offset, yachts[] } |
GET /yachts/{slug} | Optional locale for the description; falls back to Polish when a translation is missing. | Yacht details, specs, stayRules, priceFrom and full images[] gallery. |
GET /availability/{slug} | No date parameters; rolling six-month window, cached for up to 15 minutes. | { yacht, slug, unavailable: [{ from, to, type }], rules } |
GET /quote | Required slug, from, to (YYYY-MM-DD). Optional oneWay=true; otherwise false. | Current quote, or HTTP 200 with { available: false, error }. |
Catalogue items contain slug, name, model, type, location, capacity, dimensions, priceFrom and up to five image URLs. Type is sailing, motor, houseboat or scooter. Descriptive fields and priceFrom can be null.
priceFrom contains amount, currency, unit and perDay. A week amount is a weekly total; perDay is the normalised daily rate. This is a starting price, not a quote for selected dates. Detail images are objects with url, alt and isMain, not the URL strings returned by the list.
Unavailable ranges have type order, maintenance, blackout or external. Treat them as blocked dates. Rules include minStayDays, maxStayDays and gapDays. The cached calendar is for display; use a fresh quote before booking.
Quote the selected dates
Set YACHT_SLUG from the catalogue and CHARTER_FROM / CHARTER_TO to your requested dates before running this server-side request.
curl --get "https://naczarter.pl/api/v1/quote" \
-H "Authorization: Bearer $NACZARTER_API_KEY" \
--data-urlencode "slug=$YACHT_SLUG" \
--data-urlencode "from=$CHARTER_FROM" \
--data-urlencode "to=$CHARTER_TO" \
--data-urlencode "oneWay=false"{
"available": true,
"slug": "example-yacht",
"from": "2027-06-12",
"to": "2027-06-19",
"days": 7,
"currency": "PLN",
"totalPrice": 4200,
"charterPrice": 3900,
"onSiteFees": 300,
"extrasTotal": 0,
"originalPrice": null,
"discountPercent": null,
"surcharge": null
}Money values are in major currency units, not cents. Show charterPrice as the online price and onSiteFees separately as mandatory fees paid at the port. Preserve that distinction even when showing totalPrice. A surcharge, when present, is { amount, days }; otherwise it is null.
A quote does not hold inventory or lock a price. Booking recalculates both using the same pricing engine and the key's markup. Availability or prices can change between requests; do not promise that an earlier quote guarantees the final checkout amount.
Bookings & payments
POST /bookings requires book. Collect the customer's agreement before submitting. The API creates an unpaid PENDING reservation and starts hosted checkout; submitting the request is not payment.
{
"slug": "example-yacht",
"from": "2027-06-12",
"to": "2027-06-19",
"firstName": "Jan",
"lastName": "Kowalski",
"email": "jan@example.com",
"phone": "+48600100200",
"agreementAccepted": true,
"oneWay": false,
"locale": "en",
"provider": "payu",
"returnUrl": "https://partner.example/booking/status"
}| Fields | Contract |
|---|---|
| slug, from, to | Required yacht slug and YYYY-MM-DD dates. Use the same dates and oneWay choice as the quote. |
| firstName, lastName | Required strings, 1–100 characters each. |
| email, phone | Required valid email (up to 200 characters); phone 5–30 characters containing at least 9 digits. |
| agreementAccepted | Required literal true. Collect agreement from the customer, not an automatic default. |
| oneWay | Optional boolean; defaults to false. |
| locale | Optional: pl, en, de, cs, lt, nl, ru, uk, fr, it, sk, hu. Defaults to the storefront locale, then pl. |
| provider | Optional payu (default) or stripe. |
| returnUrl | Optional HTTPS URL, up to 500 characters, subject to the key’s allowlist. Omit to use the hosted status page. |
Do not send a price, storefront identifier, stored-credit balance or discount amount. The server determines the storefront from the key and computes the amount. No NaCzarter customer login is required for this partner flow.
{
"orderId": "<order-id>",
"guestToken": "<private-guest-token>",
"checkoutUrl": "<hosted-checkout-url>"
}Save orderId and guestToken securely on your backend before redirecting the customer to checkoutUrl. Bind them to that customer's session; do not expose a generic status proxy that accepts arbitrary order identifiers.
201 with checkoutUrl: null and warning: "CHECKOUT_FAILED" means the reservation exists but checkout could not start. Keep the identifiers, show a support path and do not create another booking. Partner API v1 has no separate endpoint to restart checkout.
Return URLs and payment status
A return URL must use HTTPS, have no embedded credentials or fragment, and match an entry on the key's allowlist by exact origin plus path boundary. An allowed https://partner.example/booking accepts that path and its subpaths, not /booking-other. Arrange allowlist changes with NaCzarter before changing your return page.
Booking returns include order_id, payment_provider and payment_type. Stripe success also includes session_id; cancellation includes canceled=1. PayU may return the customer regardless of payment outcome.
A redirect is not proof of payment. Retrieve status through GET /orders/{id}?guestToken=… with a read key for the same storefront. A missing or wrong guest token, or another storefront's order, returns 404.
curl --get "https://naczarter.pl/api/v1/orders/$ORDER_ID" \
-H "Authorization: Bearer $NACZARTER_API_KEY" \
--data-urlencode "guestToken=$GUEST_TOKEN"The response contains orderId, status, createdAt, currency, totalPrice, paidAmount, onSiteFees and yacht (slug, name, from, to; nullable). Inspect the status and recorded amounts; do not equate the total including port fees with the online payment.
Typical states are PENDING, DEPOSIT_PAID, CONFIRMED, COMPLETED, CANCELLED and GHOSTED. Do not label an unknown status as paid. Unpaid API holds become eligible for expiry after about 90 minutes; cleanup is scheduled, not a precise client-side timer.
The order-status endpoint currently accepts the guest token in the query string. Call it only from your backend and redact that parameter from access logs, tracing and error reports. Never put it in a public link, analytics event or shared cache. Keep customer-specific responses private and uncached.
Tickets & crew lists
Discover events and reserve seats
GET /events requires read. It accepts locale (default pl), limit (1–50, default 24) and offset (default 0). Unsupported event locales fall back to pl. The response is { total, limit, offset, locale, events[] }, filtered to future active events with at least one on-sale pool with seats remaining.
Events contain slug, title, excerpt, cover image, dates, location and pools[]. Each pool provides poolId, name, description, price, currency, available seats, maxPerOrder, salesEnd and vessel. Vessel is { slug, name }, { note } or null. Choose a pool from this response; catalogue availability is not a seat reservation.
{
"poolId": "<pool-id-from-events>",
"quantity": 2,
"buyerName": "Jan Kowalski",
"buyerEmail": "jan@example.com",
"buyerPhone": "+48600100200",
"acceptedTerms": true,
"locale": "en",
"returnUrl": "https://partner.example/ticket/status"
}POST /tickets requires tickets. Required fields: poolId, integer quantity (1–50 and no more than the pool's maxPerOrder), buyerName (2–200 characters), valid buyerEmail (up to 200 characters) and acceptedTerms: true. Optional: buyerPhone (5–30 characters), locale (up to 10 characters; use a supported locale) and allowlisted returnUrl.
Success is 201 with ticketId, manifestToken and checkoutUrl. Seats are held before payment. Save both identifiers privately before redirecting. Tickets use hosted PayU checkout; there is no provider selection on this endpoint. The return URL receives ticket_id and payment_provider=payu.
Read payment state and crew details
curl "https://naczarter.pl/api/v1/tickets/$TICKET_ID/participants" \
-H "Authorization: Bearer $NACZARTER_API_KEY" \
-H "X-Manifest-Token: $MANIFEST_TOKEN"Send the saved token in X-Manifest-Token. The ticket must belong to the key's storefront. GET works before payment and returns { ticketId, quantity, paid, complete, participants[] }. Use paid: true to confirm payment; paid: false does not distinguish pending, cancelled or failed payment. There is no separate ticket-status endpoint in v1.
The query-string form manifestToken is also accepted for GET, but prefer the header to keep credentials out of URL logs. Protect participant responses as customer data and never share-cache them. Poll from your backend with backoff; browser return parameters are not payment confirmation.
Save the crew after payment
{
"manifestToken": "<private-manifest-token>",
"participants": [
{
"seatIndex": 1,
"fullName": "Anna Nowak",
"experience": "basic"
}
]
}PUT requires tickets, the saved manifestToken in the body and a paid ticket. An unpaid ticket returns 409. Submit 1–50 participants with unique seatIndex values from 1 to the ticket quantity, and fullName (2–200 characters).
Optional participant fields: dateOfBirth (valid YYYY-MM-DD), phone and iceContactPhone (up to 30 characters), iceContactName (up to 200), experience (none, basic or skipper) and notes (up to 2,000). Collect only necessary information; avoid sensitive personal details in free text.
PUT replaces the fields of each submitted seat, not the whole crew list. Omitted optional fields on a submitted seat are cleared; omitted experience becomes none. Seats absent from the request are unchanged. Send the full intended record for every seat you update. Repeating the same body preserves the same participant values.
The response is { ticketId, quantity, saved, complete }. Here saved is the total number of stored participants, not just the number in this request. Complete means every seat has a participant record; it does not certify optional fields are filled.
Errors & safe retries
Route errors use { "error": "…" }. Inspect the HTTP status before parsing a success response, and handle non-JSON failures from upstream infrastructure. Error messages may be descriptive text rather than stable enum values.
| HTTP status | Meaning | Client action |
|---|---|---|
| 400 | Invalid JSON, fields or return URL. | Correct the request; do not retry unchanged. |
| 401 / 403 | Invalid, expired or revoked key; missing scope or inactive storefront. | Check credentials, scope and access with NaCzarter. |
| 404 | Resource absent, outside the key’s catalogue/storefront, or invalid private token. | Check identifiers and access. Do not probe other identifiers. |
| 409 | Ticket sales window closed, sold out, or crew write before payment. | Refresh availability or payment state before asking the customer to continue. |
| 422 | Booking or ticket validation rejected the operation. | Show a useful validation message and refresh the relevant data. |
| 429 | Request, purchase or pending-hold limit reached. | Back off. Do not bypass limits with more keys or requests. |
| 500 / 502 | Server or payment gateway failure; a write may already have taken effect. | Reconcile before retrying a purchase. |
A quote can return 200 with available: false; check that field before offering checkout. A booking can return 201 with CHECKOUT_FAILED; preserve the existing reservation.
Rate limits
- General requests: configured per key, default 120 per minute.
- Booking attempts: 10 per 10 minutes per key and 5 per day per customer email; additional cap of 3 unpaid API PENDING orders per email.
- Ticket purchases: 20 per 10 minutes per key, plus a shared IP-based purchase limit of 10 per 5 minutes.
These are limits, not guaranteed throughput. Apply bounded exponential backoff with jitter to read requests and honour Retry-After if supplied; v1 does not guarantee that header or rate-limit counters.
Do not automatically repeat purchases
POST /bookings and POST /tickets are not idempotent. There is no supported Idempotency-Key contract in v1. After a timeout, lost response or server error, a hold may exist even if you received no identifier. Disable double submission in your own application and reconcile with NaCzarter before trying again.
If you saved the identifiers, read order status or the ticket's payment flag first. If you did not, v1 has no partner lookup by email or client request ID. A gateway error is not sufficient evidence that a new purchase is safe. Do not assume failed writes are automatically rolled back.
Keep API keys, guest tokens, manifest tokens, checkout URLs and customer details out of public logs and analytics. Authenticate and authorise your own users before proxying purchases, status checks or crew updates. This reference contains no live credentials and sends no API requests on your behalf.