The add-ons put their own data on WooCommerce’s Store API, the public API a headless storefront or a mobile app uses for the cart and the checkout. This page lists what each add-on adds under extensions, which fields a client must send so the order is accepted, and the errors an order is refused with. It continues Adding to the cart and creating orders, which covers the rental dates on cart/add-item, the Cart-Token and placing an order; none of that is repeated here. The setup and the conventions shared by every endpoint (keys, errors, pagination) are in REST API overview and authentication.
Base URL and sign-in #
All of it lives in WooCommerce’s Store API, https://example.. Each add-on owns one namespace under extensions.
| Namespace | Where it appears | Needs |
|---|---|---|
sicustomfields | Cart (GET /cart), checkout request | Custom Fields, Damage Waiver & ID Verification |
sicustomfields-waiver | Cart, POST /cart/extensions | Custom Fields, Damage Waiver & ID Verification |
sipdfsign | Checkout request | Rental Contract Signing & PDF Generation |
sidistance | Cart, POST /cart/extensions | Delivery by Distance |
sibooking-delivery | Cart, POST /cart/extensions | Smart Routing (rental stops also need Rentals & Bookings Regular) |
sisub-store-credit | Cart, POST /cart/extensions | Subscriptions & Memberships |
sibooking_stay | Each cart item (items[].extensions) | Rentals & Bookings (Lite or Regular) |
- The Store API needs no API key. A guest cart is identified by the
Cart-Tokenheader, as described in Adding to the cart and creating orders: send the token you got from the first response on every call. - A request made as a signed-in customer (cookie, or an Application Password) must carry the
Nonceheader WooCommerce returns in its responses. Without it WooCommerce answers401withwoocommerce_. The same header is how a page of the store itself calls the API: it is printed asrest_ missing_ nonce wcSettings..storeApiNonce - Send bodies as JSON (
Content-Type: application/json). Amounts are strings in the currency’s minor unit (cents), as for every WooCommerce cart. - Errors use the WordPress shape
{"code", "message", "data": {"status"}}.
Who may do what #
The Store API is public, so none of this needs a capability. Two rules decide what a client sees:
| Rule | Effect |
|---|---|
| Store credit belongs to an account | sisub-store-credit shows an amount and accepts use only for a signed-in customer with credit left. A guest always reads available: "0.00" |
| Questions follow the cart | sicustomfields lists only the questions that apply to the signed-in customer (roles) and to the cart’s contents (products, categories, field groups, conditions) |
How the extension data works #
Three calls carry add-on data:
- Read.
GET /wc/store/v1/cartreturnsextensions.for the cart (and< namespace> items[].for a cart line).extensions. < namespace> - Change the cart.
POST /wc/takesstore/ v1/ cart/ extensions namespaceanddata. The add-on stores the choice in the customer’s session and WooCommerce works out the cart again and returns it, so a fee or a price moves at once. - Place the order.
POST /wc/store/v1/checkouttakesextensions.beside the addresses and< namespace> payment_method. The add-on checks the values and refuses the order when something is missing.
To see exactly what a store accepts, ask the API for its schema: OPTIONS /wc/store/v1/checkout lists every namespace the checkout request may carry under schema..
curl -X OPTIONS https://example.com/wp-json/wc/store/v1/checkout
A cart update with an unknown namespace is answered 400 with woocommerce_ (“There is no such namespace registered”).
curl -X POST https://example.com/wp-json/wc/store/v1/cart/extensions \
-H "Content-Type: application/json" \
-H "Cart-Token: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." \
-d '{"namespace": "sicustomfields-waiver", "data": {"accepted": false}}'
Custom fields, damage waiver and ID verification #
Needs Custom Fields, Damage Waiver & ID Verification. Three kinds of checkout question exist, and they travel differently.
| Kind | Which questions | Where the client sends the answer |
|---|---|---|
| Native | Unpriced questions of a simple type (text, text area, email, phone, URL, number, VAT, date, select, radio, checkbox) | WooCommerce’s own additional_fields object of the checkout request, keyed sicf/<field key> |
| Block | Priced questions, file uploads, multiple choices (multi-select, multiple checkboxes), time, color and password questions | extensions. |
| Identity document | The ID upload, when the cart needs one | extensions. |
Headings and messages are display only: they take no answer.
GET /wc/store/v1/cart: extensions.sicustomfields #
What to ask for this cart. It changes with the cart and the customer.
| Field | Notes |
|---|---|
fields | Array of strings: the keys of the native questions that apply to this cart. WooCommerce 9.9 and newer use it to show or hide those fields |
block.fields | The block questions for this cart, in checkout order (the table below) |
block.native | The native questions, each with key, id (sicf/<key>), location (contact, address or order) and conditions |
block.answers | Object: the answers kept in the session so far, key to value. Files are not listed |
block.id | The ID document settings, or null when this cart needs none (the table below) |
Each entry of block.fields:
| Field | Notes |
|---|---|
key | The question’s key: the key of answers |
type | text, textarea, email, tel, url, number, password, select, multiselect, radio, checkbox, multicheckbox, date, time, color, file, heading, message, vat |
label, description, placeholder | Text in the customer’s language. description is HTML the shop wrote |
required | Boolean |
default | String, or an array of strings for multiselect and multicheckbox |
options | Array of {value, label}. The label includes the price (“Extra driver (+$15.00)”) |
conditions | The rules that show the question: {relation, rules} |
priced | True when the answer changes the total |
reads | For a formula price: the keys of the questions the formula reads |
width, min, max, maxlength | Layout and limits |
accept, maxBytes, maxLabel | Files only: allowed extensions (.jpg,.pdf), size limit in bytes and as text |
block.id:
| Field | Notes |
|---|---|
required | An ID is required to place the order |
onFile | The signed-in customer already has a document on file; none is needed again |
types | Array of {value, label, back}: the accepted document types, and whether this type has a back side |
back | The shop asks for the back side of types that have one |
expiry | The shop asks for the expiry date |
minAge | Minimum age in years, 0 for none. Above 0 the date of birth is required |
accept, maxBytes, maxLabel | Allowed file types and the size limit |
"sicustomfields": {
"fields": [],
"block": {
"fields": [
{ "key": "extra_driver", "type": "select", "label": "Extra driver", "required": false, "priced": true,
"options": [ { "value": "yes", "label": "Add a driver (+$15.00)" } ], "...": "..." }
],
"native": [],
"answers": {},
"id": { "required": true, "onFile": false, "types": [ { "value": "drivers_license", "label": "Driver's license", "back": true } ],
"back": true, "expiry": true, "minAge": 21, "accept": ".jpg,.jpeg,.png,.webp,.pdf", "maxBytes": 5242880, "maxLabel": "5 MB" }
}
}
POST /wc/store/v1/cart/extensions (namespace sicustomfields) #
Keeps the block answers in the session, so a priced answer moves the cart’s fees before checkout. Optional: the checkout request carries the answers again.
Field in data | Notes |
|---|---|
answers | Object, key to answer. Only questions of block.fields are read; other keys are ignored |
Answer values: a string for most types; an array of strings for multiselect and multicheckbox; for a file, the upload token (below). Answers are cleaned as the classic checkout cleans them. A file answer with an unknown or foreign token counts as no file. The response is the whole cart.
POST /wc/store/v1/checkout: extensions.sicustomfields #
| Field | Notes |
|---|---|
answers | Object, key to answer: every block question the cart asks. Send it with the request even if you sent it to the cart before |
id | Object, the identity document (below). Leave it out when block.id is null, or when the customer has a document on file |
id:
| Field | Notes |
|---|---|
type | A value from block.id.types |
name | Name as shown on the document. Optional |
expiry | YYYY-MM-DD. Required when block.id.expiry is true; an expired date is refused |
dob | YYYY-MM-DD. Required when block.id.minAge is above 0 |
token_front | Upload token of the front photo |
token_back | Upload token of the back photo. Required when block.id.back is true and the type has a back |
curl -X POST https://example.com/wp-json/wc/store/v1/checkout \
-H "Content-Type: application/json" \
-H "Cart-Token: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." \
-d '{
"billing_address": { "first_name": "Sarah", "last_name": "Miller", "address_1": "12 Example Street", "city": "Springfield", "state": "CA", "postcode": "90210", "country": "US", "email": "[email protected]" },
"payment_method": "bacs",
"additional_fields": { "sicf/driver_name": "James Cooper" },
"extensions": {
"sicustomfields": {
"answers": { "extra_driver": "yes", "licence_scan": "3f1b0c9a5d7e4a2b8c6d1e0f9a8b7c6d" },
"id": { "type": "drivers_license", "expiry": "2030-05-01", "dob": "1990-03-14",
"token_front": "a1b2c3d4e5f60718293a4b5c6d7e8f90", "token_back": "0f9e8d7c6b5a49382716f5e4d3c2b1a0" }
}
}
}'
Uploading a file or an ID photo #
A file answer and an ID photo are not sent in the checkout request. The client uploads the file first, gets a token, and sends the token as the answer. The upload is a WordPress AJAX call, not a Store API route:
POST https://example., as multipart/form-data.
| Upload | action | Other fields |
|---|---|---|
| A checkout file question | sicf_field_upload | _ajax_nonce, field (the question’s key), product = 0, file |
| An ID photo | sicf_id_upload | _ajax_nonce, side (front or back), file |
The nonce is not part of the Store API. The store’s Checkout block page prints it in its script data: wcSettings setting sicustomfields_data, keys uploadNonce (files) and idNonce (ID), with uploadAction and idAction naming the actions. Uploads are limited to 8 a minute per client address.
The answer is JSON. On success: {"success": true, "data": {"token": "...", "name": "licence.pdf", "size_label": "412 KB"}} (an ID upload also returns side and size). Errors: {"success": false, "data": {"message": "..."}}.
| Status | When |
|---|---|
403 | The nonce is missing or expired (“Your session has expired. Please reload the page and try again.”); or ID uploads are switched off (“ID uploads are not enabled.”) |
400 | No file sent (“No file was uploaded.”), the cart does not ask that question (“This product does not take that file.”), or the file is refused (type, size). The body carries a code and a message |
429 | Too many uploads in a row |
A token is tied to the session that uploaded it, or to the signed-in customer, and lives two hours. A token that another session presents does not resolve. Because the upload is a browser AJAX call that shares the store’s cookie session, file and ID uploads suit a client that runs on the store’s own pages (a theme or a block), together with the Nonce header. A client that only holds a Cart-Token and has no page of the store to read the upload nonce from cannot upload files; questions without a file work for it.
To discard an upload, post action = sicf_field_discard (or sicf_id_discard), _ajax_nonce, token (and field for a question).
Errors at checkout #
The order is refused with POST /wc/store/v1/checkout and status 400, code sicustomfields_. message holds every problem, joined with a space, in the classic checkout’s wording.
| Message | When |
|---|---|
<Label> is a required field. | A required question has no answer (and its conditions show it) |
Please upload a file for <Label>. | A required file question has no valid token |
Please choose a valid option for <Label>. | The answer is not one of the options |
<Label> is not a valid email address., ... URL., ... phone number. | The format check of that type failed |
<Label> must be a number., must be <min> or more., must be <max> or less. | Number limits |
<Label> must be <n> characters or fewer. | Longer than maxlength |
Please upload a photo of your ID to complete this order. | An ID is required, none is on file and none was sent |
Please choose which type of ID you are uploading., That is not one of the accepted document types. | id.type missing or not in block.id.types |
Please enter the expiry date printed on your ID., That expiry date is not a real date. Please use YYYY-MM-DD., That ID has expired. Please upload one that is still valid. | id.expiry |
Please enter your date of birth., That date of birth is not a real date. Please use YYYY-MM-DD., That date of birth is in the future., You must be at least <n> years old to place this order. | id.dob and minAge |
Please attach a photo of the front of your ID., Please attach a photo of the back of your ID as well. | A side is missing |
Your uploaded file is no longer available. Please attach it again. | A token is unknown, expired or from another session |
The damage waiver for your order could not be worked out. Please review your cart and try again. | The waiver fee on the order differs from what the cart must charge: read the cart again and retry |
A native question that is required and missing is refused by WooCommerce itself, before this check, with its own additional_fields error.
The damage waiver: extensions.sicustomfields-waiver #
A required waiver is always in the cart as a fee. An optional waiver is on by default and the customer may decline it. Read the offer from the cart:
| Field | Notes |
|---|---|
optional | True when the cart has a waiver the customer may decline |
accepted | True when the waiver is in the cart. true when there is no optional waiver |
name, description | The waiver’s name and what it covers |
amount | The waiver’s price as text (“$8.00”) |
"sicustomfields-waiver": { "optional": true, "accepted": true, "name": "Damage waiver", "amount": "$8.00", "description": "Covers accidental damage to your rental." }
To decline or take it, call POST /wc/ with namespace = sicustomfields-waiver and data = {"accepted": true|false}. The cart answers with the fee added or removed. The choice is kept in the session and stored on the order; nothing needs to be sent again at checkout. A required waiver ignores the call.
Rental contract signing #
Needs Rental Contract Signing & PDF Generation. The signature travels in the checkout request as extensions.sipdfsign. The add-on adds no cart data: the client decides when to show a signing screen from the store’s own settings.
POST /wc/store/v1/checkout: extensions.sipdfsign #
| Field | Notes |
|---|---|
sitext | The typed signature: the signer’s full name |
sisignature | The drawn signature: a PNG data URL (data:image/) of at most 1 MB |
consent | Boolean: the signer agrees to sign electronically. Always required |
method | type or draw, a hint when both are sent; what was captured decides |
strokes | The drawn signature’s stroke data (signature_pad toData()) as a JSON string |
consent_at, terms_opened_at, signed_at | Browser timestamps, ISO 8601 UTC (2026-11-11T10:00:20Z). Kept in the signature’s audit trail |
terms_hash | SHA-256 of the contract terms as shown |
Which signature counts depends on the store’s setting “Signature type”:
| Setting | Counts as signed |
|---|---|
Typed (type) | sitext only |
Drawn (sign) | sisignature only |
Typed or drawn (typedsigned, the default) | Either one |
| None | No signature is asked; nothing is checked |
By default the signature is asked only when the cart contains rentals (“show only if the order contains rentals”); a cart of sold products needs none. The signature, the signer’s IP address and user agent, and the consent are recorded on the order, and the signed PDF is made when the order is placed.
curl -X POST https://example.com/wp-json/wc/store/v1/checkout \
-H "Content-Type: application/json" \
-H "Cart-Token: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." \
-d '{
"billing_address": { "first_name": "Sarah", "last_name": "Miller", "address_1": "12 Example Street", "city": "Springfield", "state": "CA", "postcode": "90210", "country": "US", "email": "[email protected]" },
"payment_method": "bacs",
"extensions": { "sipdfsign": { "sitext": "Sarah Miller", "method": "type", "consent": true, "consent_at": "2026-11-11T10:00:00Z", "signed_at": "2026-11-11T10:00:20Z" } }
}'
Errors #
The order is refused before it is placed, status 400. code is the first problem’s code, and message joins all of them.
| Code | Message | When |
|---|---|---|
sipdfsign_signature | Please sign the rental contract to place your order. | The cart needs a signature and the request carries none that counts for the signature type |
sipdfsign_signature | The drawn signature could not be read. Please clear the signature box and sign again. | sisignature is not a PNG data URL of at most 1 MB |
sipdfsign_consent | Please confirm that you agree to sign the rental contract electronically. | consent is not true |
When both are missing, the code is sipdfsign_signature and the message holds both sentences.
Only POST /checkout is checked. A PUT or PATCH that updates the draft order is not, and neither is POST /checkout/{order_id}, which pays an order that exists: its contract is signed on the signing page the customer gets with the order, never recorded from a payment request.
Delivery by Distance #
Needs Delivery by Distance. The delivery price depends on the distance from the shop to the customer’s address, so it appears as a shipping rate once the cart has a shipping address. The add-on adds one read-only value to the cart.
GET /wc/store/v1/cart: extensions.sidistance #
| Field | Notes |
|---|---|
refusal | String. Why delivery is not offered to the address now (“We deliver up to 40 km from Downtown.”), "" when it is offered or no address is known yet |
When shipping_rates has no delivery rate, show refusal instead of WooCommerce’s generic “no shipping options” message. The cart reads the address saved on the cart’s customer, so set it first with POST /wc/ (WooCommerce’s own call), then read the cart again.
The pin on the map #
With “Check the pin on the map” switched on, a customer can move the delivery pin up to the shop’s limit (2 km by default) from the point found for the address; the price is then measured from the pin. The pin is saved by two browser calls (?wc-ajax= and ?wc-ajax=, with the page’s sidistance_pin nonce), not by the Store API. The Store API only refreshes the cart afterwards: POST /wc/ with namespace = sidistance and any data ({}) makes WooCommerce price the cart again. A client that has no page of the store to read the nonce from prices from the address alone.
curl -X POST https://example.com/wp-json/wc/store/v1/cart/extensions \
-H "Content-Type: application/json" \
-H "Cart-Token: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." \
-d '{"namespace": "sidistance", "data": {}}'
Delivery by Distance refuses nothing in the checkout request itself; an address it cannot serve has no delivery rate, so the customer cannot choose it.
Smart Routing: delivery slots and days #
Needs Smart Routing. Rental stops (delivery, collection, pick-up, return) come from Rentals & Bookings Regular when it is active; with Smart Routing alone the cart carries no rental stops (way is null, stops is []), and only the sold-products delivery day below applies.
GET /wc/store/v1/cart: extensions.sibooking-delivery #
| Field | Notes |
|---|---|
way | How the order gets its rentals: p pick up, d delivery, s transport. null without rentals |
stops | The order’s stops. Rentals handed over on the same day in the same slot share one |
title | The panel’s title (“Delivery plan”), "" when the plan is off |
notes | Array of strings: shipping options left out for these dates and why, and why the address gets no Delivery option |
sale | A cart of sold products only: the days it can be delivered; null otherwise |
Each entry of stops:
| Field | Notes |
|---|---|
key | The stop’s key (out|2026-12-14|08:00-10:00): send it back to change the slot |
kind | delivery, collection, pickup, return, dispatch or due |
label | “Delivery: Mon, Dec 14, 8:00 AM – 10:00 AM” |
window | The current slot (HH:MM-HH:MM), "" for no slot |
options | Array of {id, label, full}: the slots the stop can move to. id is the value to send; full marks a slot with no room left |
items | The names of the rentals handed over at this stop |
sale (sold products delivered by the Delivery option, when the shop lets customers choose the day):
| Field | Notes |
|---|---|
title | “Delivery day” |
days | Array of {day, slots: [{id, label, full}]}. day is Y-m-d. When the shop allows “Any time”, the first slot of a day has id "" |
day, window | The current choice. The first open day and slot until the customer chooses |
arrange | Text, when the shop arranges the day itself and the customer chooses none; then days is [] |
POST /wc/store/v1/cart/extensions (namespace sibooking-delivery) #
Field in data | Notes |
|---|---|
stop, window | Move the rentals of the stop stop (a key from stops) to the slot window (an id from that stop’s options) |
saleDay, saleWindow | Choose the delivery day (Y-m-d) and slot of a sold-products cart |
The cart is priced again afterwards (a slot may carry a surcharge) and returned. A stop that does not exist, a full slot or a closed day is ignored without an error: the cart comes back unchanged, so compare window or day with what you sent.
curl -X POST https://example.com/wp-json/wc/store/v1/cart/extensions \
-H "Content-Type: application/json" \
-H "Cart-Token: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." \
-d '{"namespace": "sibooking-delivery", "data": {"stop": "out|2026-12-14|08:00-10:00", "window": "10:00-12:00"}}'
Errors at checkout #
POST /wc/store/v1/checkout is refused with status 400. Slots held in the cart are claimed again at this moment, so a slot filled by someone else since the cart was built is refused here.
| Code | When |
|---|---|
sibooking_sale_day | “Choose a delivery day.” A sold-products cart delivered by the Delivery option has no open day, or a day with slots has no slot and “Any time” is off |
sibooking_capacity | A stop’s slot or day is full. The message names it |
sibooking_handover | Rentals & Bookings Regular’s delivery rules refuse the cart (a start that is too soon, no Delivery option for the address). The message is the first problem |
After a refusal the held slots are released. Read the cart again, let the customer pick another slot or day, and send the checkout again.
Store credit #
Needs Subscriptions & Memberships. A customer with an active credit membership may pay part of an order with the credit of the current billing period. Credit never pays for a membership or subscription product itself.
GET /wc/store/v1/cart: extensions.sisub-store-credit #
| Field | Notes |
|---|---|
available | String: credit left this billing period, as a decimal ("25.00"). "0.00" for a guest |
label | The checkbox text (“Use my store credit ($25.00 available)”), "" when there is no credit |
use | True when the credit is applied to this cart |
applied | String: the credit taken off the cart, as a decimal. The credit shows as a negative fee named “Store Credit” |
POST /wc/store/v1/cart/extensions (namespace sisub-store-credit) #
Field in data | Notes |
|---|---|
use | Boolean. true applies the credit, up to what the cart’s items cost; false removes it |
The call works only for a signed-in customer who has credit left. Otherwise use stays false and no error is returned, so check use in the cart that comes back. Nothing needs to be sent at checkout: the credit fee is on the order and the usage is recorded when the order is placed.
curl -X POST https://example.com/wp-json/wc/store/v1/cart/extensions \
-H "Content-Type: application/json" \
-H "Nonce: NONCE_FROM_THE_HEADER" \
-u "username:YOUR_APP_PASSWORD" \
-d '{"namespace": "sisub-store-credit", "data": {"use": true}}'
The checkout has no store credit error of its own. WooCommerce refuses an order whose total changed between the cart and the checkout.
Nights on a cart line #
Needs Rentals & Bookings (Lite 1.0.9 or newer, or Regular). When a date-only product is sold by the night (“stays”), each cart line carries the stay in items[].. It is read only. The dates are sent with cart/add-item as described in Adding to the cart and creating orders.
| Field | Notes |
|---|---|
nightly | True when the line is a stay of nights |
check_in | Check-in date (Y-m-d), null when nightly is false |
check_out | Check-out date (Y-m-d): the morning the stay ends |
nights | Number of nights, an integer |
"extensions": { "sibooking_stay": { "nightly": true, "check_in": "2026-12-14", "check_out": "2026-12-17", "nights": 3 } }
A line that is not a stay answers {"nightly": false, "check_in": null, "check_out": null, "nights": null}.
A checkout with several add-ons #
GET /wc/store/v1/cart: keep theCart-Token.POST /cart/add-itemfor each rental, with its dates.GET /cart: readextensionsto see which questions, waiver, delivery slots and credit apply.POST /cart/extensionsfor each choice that moves the price: the waiver, a priced answer, a slot, the credit.POST /checkoutwith the addresses,payment_method,additional_fields, and everyextensionsnamespace the cart asked for (sicustomfields,sipdfsign).
If a checkout is refused, fix what the error names and send the same request again: a refused request leaves the cart as it was, and uploaded files stay valid for two hours.
