Contents
Create Booking - Plugixa Boat Rental
POST /pbr/v1/bookings
Create a booking for one boat, checked and priced the way a booking made in the office is.
An API key PRO with Read and write access, or a signed-in user with the plugixa_boatrental_create capability.
Note. A booking made here skips the booking widget’s own rules: it is not held to a booking form, and it is not refused because the marina or the boat is marked inactive. It is still refused for a past or too-distant departure, closed hours, a window the boat does not allow, too many guests, or no free boat.
Request
POST https://your-site.example/wp-json/pbr/v1/bookings
Request body
Content type: application/json
| Field | Type | Required | Description |
|---|---|---|---|
location_id |
integer |
Yes | The marina. Must exist. |
boat_id |
integer |
Yes | The boat. Must exist. |
start |
string |
Yes | Departure, YYYY-MM-DD HH:MM (seconds optional) on the marina’s clock. start_datetime is accepted too, and wins when both are sent. |
end |
string |
Yes | Return, in the same form. end_datetime is accepted too. Must be after the departure. |
guests |
integer |
No | People on board, at least 1 (the default). More than the boat carries is refused. |
unit_id |
integer |
No | One particular unit of the boat, which must be free. Without it, the first free unit is picked for you. |
customer_name |
string |
No | The customer’s name. |
customer_email |
string |
No | Must be an email address when sent. |
customer_phone |
string |
No | The customer’s phone. |
notes |
string |
No | Notes, line breaks kept. |
status |
string |
No | The status to start in, one of the site’s booking statuses. Without it, the site’s default status applies (pending unless changed). |
billing_type |
string |
No | How the booking is priced: hour, half_day, day, week or seat. Without it, the boat’s own unit. |
user_id |
integer |
No | A WordPress customer account to file the booking under. Without it, the booking has no account. |
billing |
object |
No | Billing details as a flat map of plain values. |
meta |
object |
No | Answers to the booking form’s own fields, keyed by field. |
booking_form_id |
integer |
No | The booking form the answers in meta belong to, so each answer keeps its label. |
gateway |
string |
No | Stored as the booking’s payment method. Defaults to offline. |
payment_status and amount_paid cannot be sent: they come from the payments ledger, and sending either is refused. Record money with Record Payment.
A booking is always for one boat: a quantity is checked for being a positive number and otherwise ignored. The total and the line items are worked out from the boat’s prices for the window, the same way the booking widget prices it. The booking is recorded with the source admin, and the usual booking emails and booking.created webhooks follow.
{
"location_id": 2,
"boat_id": 5,
"start": "2026-11-20 10:00",
"end": "2026-11-20 14:00",
"guests": 4,
"customer_name": "Ana Silva",
"customer_email": "ana@example.com",
"customer_phone": "+351 912 345 678",
"status": "confirmed"
}
Responses
201 Created. data is the new booking.
{
"success": true,
"data": {
"id": 412,
"booking_ref": "BOAT-7F3A91",
"language_code": null,
"location_id": 2,
"boat_id": 5,
"unit_id": 9,
"booking_form_id": null,
"user_id": null,
"status": "confirmed",
"billing_type": "hour",
"start_datetime": "2026-11-20 10:00:00",
"end_datetime": "2026-11-20 14:00:00",
"start_utc": "2026-11-20 10:00:00",
"end_utc": "2026-11-20 14:00:00",
"buffer_before": 0,
"buffer_after": 30,
"block_start_utc": "2026-11-20 10:00:00",
"block_end_utc": "2026-11-20 14:30:00",
"quantity": 1,
"guests": 4,
"source": "admin",
"hold_expires_at": null,
"customer_name": "Ana Silva",
"customer_email": "ana@example.com",
"customer_phone": "+351 912 345 678",
"billing": null,
"currency": "EUR",
"subtotal": 380.0,
"discount_total": 0.0,
"tax_total": 0.0,
"deposit_amount": 0.0,
"total": 380.0,
"amount_paid": 0.0,
"payment_method": "offline",
"payment_status": "unpaid",
"notes": null,
"meta": null,
"created_by": 0,
"modified_by": 0,
"created_at": "2026-11-12 09:30:00",
"updated_at": null,
"modified_at": "2026-11-12 09:30:00",
"deleted_at": null,
"items": [
{
"id": 977,
"booking_id": 412,
"type": "rental",
"reference_id": 5,
"label": "1 boat × 4 hours",
"quantity": 4.0,
"unit_price": 95.0,
"total": 380.0,
"tax_rate_id": null,
"meta": null
}
],
"custom_fields": [],
"location_name": "Lisbon Marina",
"boat_name": "Sea Ray 230"
}
}
| Field | Type | Meaning |
|---|---|---|
data.id |
integer |
The new booking’s id |
data.booking_ref |
string |
The reference the customer sees |
data.unit_id |
integer|null |
The unit the booking holds |
data.status |
string |
The status sent, or the site’s default |
data.hold_expires_at |
string|null |
For a pending booking on a site with a hold time: when it stops holding the boat |
data.total |
number |
The priced total |
data.items |
array |
The lines the booking was priced as: type (rental, fee, or a line a Pro app adds), label, quantity, unit_price, total |
data.custom_fields |
array |
The answers in meta, each as key, label and value |
data.source |
string |
Always admin here |
Errors
| Status | Code | When |
|---|---|---|
422 |
missing_fields |
location_id, boat_id, the departure or the return is missing. |
422 |
plugixa_boatrental_invalid_location |
location_id is not an existing marina. |
404 |
not_found |
boat_id is not an existing boat. |
422 |
plugixa_boatrental_derived_field |
payment_status or amount_paid was sent. |
422 |
plugixa_boatrental_invalid_date |
A departure or return is not a real date. |
422 |
plugixa_boatrental_invalid_choice |
status or billing_type is not one of the allowed values. data.allowed lists them. |
422 |
plugixa_boatrental_not_a_number |
boat_id, guests, unit_id or quantity is not a number. |
422 |
plugixa_boatrental_invalid_guests |
guests is below 1. Also plugixa_boatrental_invalid_boat_id and plugixa_boatrental_invalid_quantity. |
422 |
plugixa_boatrental_invalid_email |
customer_email is not an email address. |
422 |
plugixa_boatrental_too_long |
A text field is longer than its column holds. data.max says how long it may be. |
422 |
invalid_range |
The return is not after the departure. |
422 |
too_soon / too_far |
The departure is in the past, closer than the site’s minimum notice, or beyond its booking horizon. |
422 |
location_closed / outside_hours |
The marina is closed that day, or the departure or return is outside its hours. |
422 |
too_many_guests |
More guests than the boat carries. |
422 |
not_a_slot / not_a_day |
A boat with fixed departures was asked for another time, or a full-day boat for more than one day. |
422 |
too_short / too_long / bad_step |
The window breaks the boat’s minimum, maximum or step. |
409 |
no_availability |
No free unit of the boat for that window, or unit_id is taken. |
409 |
busy |
Another booking for the same boat was being written. Try again. |
Plus the errors every endpoint can return.
Examples
curl -s -X POST "https://your-site.example/wp-json/pbr/v1/bookings" \
-H "Authorization: Bearer $PBR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"location_id":2,"boat_id":5,"start":"2026-11-20 10:00","end":"2026-11-20 14:00","guests":4,"customer_name":"Ana Silva","customer_email":"ana@example.com","customer_phone":"+351 912 345 678","status":"confirmed"}'
<?php
$ch = curl_init('https://your-site.example/wp-json/pbr/v1/bookings');
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('PBR_API_KEY'),
'Content-Type: application/json',
],
CURLOPT_POSTFIELDS => json_encode([
'location_id' => 2,
'boat_id' => 5,
'start' => '2026-11-20 10:00',
'end' => '2026-11-20 14:00',
'guests' => 4,
'customer_name' => 'Ana Silva',
'customer_email' => 'ana@example.com',
'customer_phone' => '+351 912 345 678',
'status' => 'confirmed',
]),
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
const response = await fetch('https://your-site.example/wp-json/pbr/v1/bookings', {
method: 'POST',
headers: {
'Authorization': `Bearer ${process.env.PBR_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
location_id: 2,
boat_id: 5,
start: '2026-11-20 10:00',
end: '2026-11-20 14:00',
guests: 4,
customer_name: 'Ana Silva',
customer_email: 'ana@example.com',
customer_phone: '+351 912 345 678',
status: 'confirmed',
}),
});
const { data } = await response.json();
Quick Links
Share