Skip to content
NaCzarter — czarter jachtów Mazury

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.

Server-side catalogue request · read scope
// 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.

Partner API endpoints
Method and pathScopePurpose
GET /yachtsreadStorefront yacht catalogue
GET /yachts/{slug}readYacht details and translated description
GET /availability/{slug}readSix-month unavailability window
GET /quotereadCurrent price and availability for dates
POST /bookingsbookCreate a reservation and start hosted checkout
GET /orders/{id}readOrder status, with guestToken
GET /eventsreadGlobal event catalogue and on-sale ticket pools
POST /ticketsticketsReserve seats and start hosted PayU checkout
GET /tickets/{id}/participantsticketsPayment flag and crew list, with manifest token
PUT /tickets/{id}/participantsticketsSave 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

Catalogue query parameters
EndpointParametersResponse
GET /yachtslimit: 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 /quoteRequired 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.

Read a quote · read scope
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"
Illustrative quote response · not a live offer
{
  "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.

POST /bookings body · illustrative values
{
  "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"
}
Booking request fields
FieldsContract
slug, from, toRequired yacht slug and YYYY-MM-DD dates. Use the same dates and oneWay choice as the quote.
firstName, lastNameRequired strings, 1–100 characters each.
email, phoneRequired valid email (up to 200 characters); phone 5–30 characters containing at least 9 digits.
agreementAcceptedRequired literal true. Collect agreement from the customer, not an automatic default.
oneWayOptional boolean; defaults to false.
localeOptional: pl, en, de, cs, lt, nl, ru, uk, fr, it, sk, hu. Defaults to the storefront locale, then pl.
providerOptional payu (default) or stripe.
returnUrlOptional 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.

HTTP 201 · reservation created
{
  "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.

Read order status · server-side only
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.

POST /tickets body · illustrative values
{
  "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

GET /tickets/{id}/participants · tickets scope
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

PUT /tickets/{id}/participants body
{
  "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.

Partner API error handling
HTTP statusMeaningClient action
400Invalid JSON, fields or return URL.Correct the request; do not retry unchanged.
401 / 403Invalid, expired or revoked key; missing scope or inactive storefront.Check credentials, scope and access with NaCzarter.
404Resource absent, outside the key’s catalogue/storefront, or invalid private token.Check identifiers and access. Do not probe other identifiers.
409Ticket sales window closed, sold out, or crew write before payment.Refresh availability or payment state before asking the customer to continue.
422Booking or ticket validation rejected the operation.Show a useful validation message and refresh the relevant data.
429Request, purchase or pending-hold limit reached.Back off. Do not bypass limits with more keys or requests.
500 / 502Server 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.

Updated 5 September 2026. Describes the current v1 implementation; examples use illustrative identifiers and prices.

All developer documentation