Contents
REST API - Plugixa Car Parking
Everything the admin app, the booking form and the account page do, they do over
REST, under the namespace plugixa-car-parking/v1. Every rule is enforced at
the route, not in the page.
This is the API the plugin’s own screens use. It is not a separately versioned public API, and the plugin makes no promise that routes, parameters or response shapes stay the same between versions. Pin the plugin version you build against, and test after updating.
Authentication
There are two kinds of route.
Admin routes are for staff. The plugin adds no authentication of its own: it relies on whatever WordPress provides, then checks the capabilities of the user WordPress identified.
| Caller | How |
|---|---|
| The admin app | The logged-in cookie plus a REST nonce in the X-WP-Nonce header, the normal WordPress way |
| A script or another system | Any method WordPress accepts for REST requests, such as an application password for an account that holds the capabilities below |
Public routes are open to visitors, because the booking form and the account page run for people who are not signed in. They are protected differently. See Public routes.
Capabilities
The plugin defines six capabilities a role can be given, and one it works out by itself.
| Capability | What it allows |
|---|---|
plugixa_carparking_view |
Read records: every list, detail screen, the dashboard and the calendar |
plugixa_carparking_create |
Create records, and record a payment |
plugixa_carparking_edit |
Change records, refund a payment, run a mass update |
plugixa_carparking_delete |
Move records to the trash, restore them, delete them permanently, run a mass delete |
plugixa_carparking_export |
Download the bookings CSV |
plugixa_carparking_manage_settings |
Read and save the plugin’s settings |
plugixa_carparking_manage |
Open the admin app. Never granted directly: anyone who holds one of the six above has it. |
Activation grants all of them to the Administrator role and to nobody else. A
user with the WordPress manage_options capability is always treated as holding
every one. The plugin has no screen for assigning them to other roles. They are
ordinary WordPress capabilities, so any role editor plugin can grant them.
Two settings can be changed only by a user who also has manage_options:
Erase all data when the plugin is deleted (delete_data_on_uninstall) and
custom_css. A settings manager without it can save everything else.
A caller who fails the check gets the code plugixa_carparking_forbidden and
the message “You are not allowed to do that.”, with status 401 when not signed
in and 403 when signed in. The verdict of every check passes through the
plugixa_carparking_user_can filter. See
Hooks reference.
Request and response shape
Send request bodies as JSON with Content-Type: application/json.
A successful admin response wraps its payload. A list adds meta:
{
"success": true,
"data": [],
"meta": { "total": 42, "pages": 1 }
}
A refusal is a standard WordPress REST error with a code, a readable message
and an HTTP status.
| Code | Status | When |
|---|---|---|
plugixa_carparking_forbidden |
401 or 403 | The capability check failed |
plugixa_carparking_conflict |
409 | Somebody else changed the record while you were editing it |
plugixa_carparking_<entity>_in_use |
409 | A listener on one of the ..._delete_blockers filters refused the trash. Nothing in version 1.0.0 attaches one. |
plugixa_carparking_rate_limited |
429 | A public route’s rate limit was reached. The response carries a Retry-After header. |
plugixa_carparking_rejected |
400 | The honeypot field of the booking route was filled in |
plugixa_carparking_captcha_failed |
403 | A CAPTCHA attached through the filter refused the request |
rest_error |
403, 404, 409 or 422 | A public route refused the request. message is a sentence for the customer. |
Edit conflicts
Every record carries modified_at. When an update sends back the modified_at
it read, the plugin compares it with the stored one and answers 409
plugixa_carparking_conflict if they differ: “Somebody else changed this record
while you were editing it. Reload to see their version before saving yours.”
The check is opt-in. An update that sends no modified_at is not checked. Two
saves inside the same second cannot be told apart.
List parameters
Every list route accepts the same parameters.
| Parameter | Default | Notes |
|---|---|---|
page |
1 |
|
per_page |
50 |
From 1 to 200 |
search |
Matches the entity’s own search columns | |
orderby |
id |
A column the entity allows sorting by |
order |
desc |
asc or desc |
view |
trash lists trashed records only |
Filters are sent as query parameters too. GET /filters/<entity_type> returns
what a list of that entity may be filtered by.
Record routes
Each entity below has the same set of routes. <base> is its path.
| Method | Route | Needs | What it does |
|---|---|---|---|
GET |
/<base> |
view | List |
POST |
/<base> |
create | Create |
GET |
/<base>/<id> |
view | Read one |
PUT, PATCH |
/<base>/<id> |
edit | Update |
DELETE |
/<base>/<id> |
delete | Move to the trash |
POST |
/<base>/<id>/restore |
delete | Bring back from the trash |
DELETE |
/<base>/<id>/purge |
delete | Delete permanently |
| Entity | <base> |
|---|---|
| Bookings | bookings |
| Locations | locations |
| Spaces at a location | location-spaces |
| Space types | space-types |
| Coupons | coupons |
| Booking forms | booking-forms |
| Extras PRO | booking-extras |
| Tax rates PRO | tax-rates |
| Pricing rules PRO | pricing-rules |
| Customer groups PRO | customer-groups |
The Pro routes exist only when Pro is installed. On a free site they answer with WordPress’s ordinary 404 for an unknown route.
Other admin routes
| Method | Route | Needs | What it does |
|---|---|---|---|
GET |
/bootstrap |
view | What the admin app needs to start: entities, the caller’s permissions, the current user and the display settings |
GET |
/meta/entities |
view | The registered entities |
GET |
/users?search= |
view | Users who may open the app, by display name, for pickers |
GET |
/filters/<entity_type> |
view on that entity | The filters a list of that entity accepts |
POST |
/mass-action/<entity_type> |
edit, or delete for a delete | Apply one action to many records |
GET |
/settings |
manage settings | The settings, with every secret blanked, and the choices each control offers |
POST, PUT |
/settings |
manage settings | Save settings. A partial body is a partial save. |
GET |
/dashboard |
view on bookings and payments | The dashboard figures |
GET |
/calendar |
view | Bookings between from and to, optionally by location_id, space_type_id and status |
GET |
/bookings/export |
export | The bookings CSV, following the same search and filters as the list. At most 10,000 rows. |
POST |
/bookings/quote |
view | Price a selection for the admin booking form |
GET, PUT, POST |
/locations/<id>/hours |
view to read, edit to write | A location’s opening hours |
GET, POST |
/locations/<id>/exceptions |
view to read, edit to write | A location’s date exceptions |
PUT, PATCH, DELETE |
/locations/<id>/exceptions/<exception_id> |
edit | Change or remove one exception |
GET, PUT |
/space-types/<id>/prices |
view to read, edit to write | A space type’s prices |
GET |
/payments |
view on payments and bookings | The payments ledger. booking_id narrows it to one booking. |
POST |
/payments |
create | Record a payment against a booking |
GET |
/payments/<id> |
view on payments and bookings | One payment |
POST |
/payments/<id>/refund |
edit | Refund a payment |
GET |
/notification-log |
view | The message log. booking_id narrows it to one booking. |
GET, POST |
/customer-groups/<id>/members PRO |
view to read, edit to write | The members of a customer group |
DELETE |
/customer-groups/<id>/members/<user_id> PRO |
edit | Remove a member |
Mass actions
POST /mass-action/<entity_type> takes this body:
{
"action": "update",
"ids": [12, 13, 14],
"values": { "status": "confirmed" }
}
action is update or delete. A delete moves the records to the trash. One
request acts on at most 200 records. The permission is asked again for every
record: one the caller may not touch is skipped and counted, and the rest are
still processed. The answer reports done, skipped and failed.
Saving settings
A secret, such as an API key, is never sent to the browser. GET /settings
returns it as an empty string, with a second key ending in _set that says
whether one is stored. Sending a secret back empty leaves it unchanged. To erase
a stored secret, name its key in a clear list:
{ "clear": ["stripe_secret_key"] }
A key the settings schema does not list is dropped from a save.
Public routes
| Method | Route | Rate limit bucket | What it does |
|---|---|---|---|
GET, POST |
/availability |
availability |
What is free at a location between entry and exit, with a price for one space of each type. Needs location_id, entry and exit. space_type_id is optional. |
POST |
/quote |
quote, and quote_coupon when a coupon code is sent |
The full price of a selection |
POST |
/booking |
booking |
Create a booking |
POST |
/coupon/validate |
coupon_validate |
Check a coupon code |
GET |
/account/bookings |
account_list signed in, account_lookup as a guest |
A signed-in customer’s bookings, or one booking found by ref and email |
POST |
/account/bookings/<ref>/cancel |
account_change |
Cancel a booking |
POST |
/account/bookings/<ref>/reschedule PRO |
account_change |
Move a booking to new dates |
POST |
/payment/start |
payment_start |
Start a payment for a booking. Takes booking_ref, gateway and optionally pay_type. |
GET, POST |
/payment/return |
Where a payment provider sends the customer’s browser back to | |
POST |
/payment/webhook/<gateway> |
Where a payment provider reports a payment | |
GET |
/invoice PRO |
invoice |
The PDF invoice of a booking. Takes ref and token. |
A public success always carries a message:
{
"success": true,
"message": "Your booking has been created.",
"data": { }
}
How public routes are protected
A public route cannot ask who is calling, so it checks three things, cheapest first.
- Honeypot. The booking route refuses a request whose hidden
pcp_hpfield is filled in, with “This request could not be processed.” - Rate limit. Counted per visitor address and per bucket.
- CAPTCHA. Nothing is bundled. A site adds one with the
plugixa_carparking_public_request_captchafilter.
The routes that change something (/booking, cancel, reschedule) also need the
public nonce the plugin’s pages print. Send it as security in the body or in
the X-Plugixa-Carparking-Nonce header. Without a valid one the answer is 403:
“Your session expired. Please refresh and try again.” The nonce shows that the
request came from one of the site’s own pages. It identifies nobody.
A booking created through /booking belongs to whoever is signed in. A
user_id in the body is ignored.
Rate limits
| Bucket | Requests | Window |
|---|---|---|
availability |
120 | 10 minutes |
quote |
240 | 10 minutes |
quote_coupon |
30 | 10 minutes |
booking |
10 | 1 hour |
payment_start |
20 | 1 hour |
account_lookup |
10 | 15 minutes |
account_list |
60 | 10 minutes |
account_change |
10 | 1 hour |
coupon_validate |
10 | 10 minutes |
invoice |
30 | 10 minutes |
Over the limit the answer is 429 “Too many requests. Please wait a little while
and try again.”, with a Retry-After header in seconds.
The counter is kept in a transient whose name contains a hash of the address,
never the address itself. The address is read from REMOTE_ADDR only. On a site
behind a proxy every visitor shares the proxy’s address and therefore one
counter: use the plugixa_carparking_client_ip filter to name the header your
proxy sets. The limits themselves are changed with
plugixa_carparking_rate_limits. Both are shown in
Hooks reference.
The old namespace
An earlier line of the plugin served its routes from plugixacarparking/v1. A
few addresses are written down outside the plugin, where an update cannot reach
them, so they still answer there as well:
| Old address | Why it is kept |
|---|---|
plugixacarparking/v1/payment/webhook/<gateway> |
Pasted into a payment provider’s dashboard |
plugixacarparking/v1/payment/return |
Where a provider sends a customer back to |
plugixacarparking/v1/invoice PRO |
Linked from emails customers already hold |
Each is the same route under a second name: the same handler and the same
checks. Every other address on the old namespace answers 404. A module can keep
another path alive with the plugixa_carparking_legacy_route_aliases filter.
Version 1.0.0 itself still writes these three addresses on the old namespace: the Stripe webhook address shown in Settings, the return address given to a payment provider, and the PDF invoice link. Both namespaces reach the same handler, so either works.
Adding your own routes
Register on the plugixa_carparking_register_rest_routes action, which passes
the namespace:
add_action( 'plugixa_carparking_register_rest_routes', function ( $namespace ) {
register_rest_route(
$namespace,
'/gate-status',
array(
'methods' => 'GET',
'callback' => fn() => rest_ensure_response( array( 'open' => true ) ),
'permission_callback' => fn() => current_user_can( 'plugixa_carparking_view' ),
)
);
} );