View Categories

REST API: Request a Quote & Hide Price

13 min read

The REST API for Request a Quote & Hide Price: list, create, edit and send quotes, add messages, read the version history, and manage the hide-price and quote-only rules. It is for staff and back-office tools (a CRM, a sales dashboard, a quoting script). Shoppers never use it: they request and accept quotes through the storefront. The setup and the conventions shared by every endpoint (keys, errors, pagination) are in REST API overview and authentication.

Base URL and sign-in #

NamespaceWhat lives thereNeeds
/wp-json/wc-sirfq/v1/Quotes, quote actions, messages, versions and the hide-price rulesRequest a Quote & Hide Price
  • wc-sirfq/v1 starts with wc-, so it takes a WooCommerce REST API key (WooCommerce > Settings > Advanced > REST API) sent as HTTP Basic. The key’s user decides what the key may do (see below), so use the key of a user with the quote abilities.
  • A WordPress Application Password (Users > Profile > Application Passwords) as HTTP Basic also works, as does the logged-in cookie plus an X-WP-Nonce header inside wp-admin.
  • Send bodies as JSON (Content-Type: application/json). Dates in responses are RFC 3339 in UTC (2026-11-11T01:24:44Z).
  • Errors use the WordPress shape {"code", "message", "data": {"status"}}. The codes are listed with each endpoint.
  • The routes have been in the plugin since 1.0.0.
STORE="https://example.com/wp-json"
curl -u "ck_your_key:cs_your_secret" "$STORE/wc-sirfq/v1/quotes?status=pending"

Who may do what #

Every route checks a quote ability on the signed-in user. Without it the API returns 401 (not signed in) or 403 (signed in, not allowed) with the code sirfq_forbidden.

AbilityWho has itOpens
sirfq_view_quotesAdministrators and shop managersListing and reading quotes, messages and versions
sirfq_edit_quotesAdministrators and shop managersCreating and changing quotes, adding messages
sirfq_send_quotesAdministrators and shop managersThe actions send, reject, cancel, reopen and extend
sirfq_convert_quotesAdministrators and shop managersCreating the order and marking a quote as ordered
sirfq_manage_quote_settingsAdministrators and shop managersThe /rules endpoints
sirfq_view_all_quotesAdministrators and shop managersSeeing every quote, not only your own (owner scoping, below)

The plugin gives all of its abilities to the administrator and shop manager roles when it is activated. Other roles have none of them. To let another role or user work with quotes, give them the abilities with a role editor or add_cap(); give only sirfq_view_quotes and sirfq_edit_quotes to a salesperson who should see their own quotes.

Owner scoping #

Each quote has an owner (owner_user_id), a staff user. A quote created through the API is owned by the user of the key. A quote from the storefront is assigned by the plugin’s owner rules, or stays unassigned.

  • A user with sirfq_view_all_quotes sees every quote.
  • A user without it sees only the quotes they own and the unassigned ones. In the list, the other quotes are left out. On /quotes/{id} and everything below it, a quote owned by someone else returns 403 sirfq_forbidden.
  • Baskets (a shopper’s quote that has not been submitted) are never listed and always return 404.

Quotes #

A quote has a status: draft, pending (a request from a shopper), sent, changes_requested, accepted, ordered, expired, declined, rejected or cancelled. Amounts the customer cannot see (a hidden price) are null, never 0.

GET /wc-sirfq/v1/quotes #

Lists quotes, newest first, as short rows. Pagination totals are in the X-WP-Total and X-WP-TotalPages headers.

ParameterNotes
statusOne status from the list above. An unknown value, or basket, returns an empty list.
ownerme, unassigned, or a user id. A user id is honored only for users who see all quotes; for others the owner scope applies.
emailCustomer email, exact match.
numberQuote number, exact match, for example Q-1234. A draft shows as #id until it is sent or submitted, and gets its Q- number then.
searchFree text, matched against the number, email, name, company, custom form answers, and the title and SKU of the lines.
date_from, date_toYYYY-MM-DD, on the created date. Any other format returns 400.
pageDefault 1.
per_pageDefault 20, 1 to 100.

Each row has these fields:

FieldNotes
id, numberQuote id and its display number.
status, sourceStatus; where the quote came from (rest for a quote made through this API, other values for storefront forms).
customer_idWordPress user id, or null for a guest.
email, name, companyCustomer details.
owner_user_idThe staff owner, or null.
total, currencyQuote total; null while a price is unset.
order_idThe WooCommerce order, or null.
datescreated_at, submitted_at, sent_at, viewed_at, expires_at, accepted_at, pay_by, closed_at, updated_at; each null until it happens.
lock_versionCounter for safe updates (see PUT below).
curl -u "ck_your_key:cs_your_secret" \
  "https://example.com/wp-json/wc-sirfq/v1/quotes?status=pending&owner=unassigned&per_page=10"

GET /wc-sirfq/v1/quotes/{id} #

Returns one quote in full: the list fields plus the following.

FieldNotes
status_labelThe status as shown in the admin.
phone, billing, shippingContact and address objects. An empty address is [].
conversioncheckout (the customer accepts and pays at checkout) or order (staff create the pay-for-order order). When the quote has no setting of its own, the store default is shown.
offline_onlytrue when the quote can only be settled outside the store.
versionThe working version: id, n, author, reason, locked, discount, shipping, fees, tax_exempt, message, terms, subtotal, discount_total, shipping_total, fee_total, tax_total, total, created_at.
sent_version_id, accepted_version_idThe version the customer was sent or accepted, or null.
linesThe lines of the working version (below).
totalsDisplay rows: key, label, amount (a discount is negative).
available_actionsThe actions that make sense now, for example ["send", "accept", "cancel", "extend"].
editabletrue while the quote can still be edited (draft, pending, sent, changes requested).
linkscustomer (the signed link the shopper opens), pay, order, admin.
rentalRental dates, hold state and notices, when the quote has rental items.
answersCustom form answers as {key, label, value}.
consent, filesConsent record and uploaded files, or null / [].

A line has id, origin_id, type, custom (a line typed by staff, not a product), title, sku, qty, product_id, variation_id, list_price, requested_price, unit_price, line_total, line_tax, note (the customer’s), staff_note, proposal_type, proposal_value, price_hidden, unavailable, rental, taxable and more. A line’s id changes when a new version is made; origin_id stays the same.

Errors, here and on every route with {id}:

ErrorWhen
404 sirfq_not_foundThe quote does not exist, or is a basket.
403 sirfq_forbiddenOwner scoping: the quote belongs to another user and you lack sirfq_view_all_quotes.

POST /wc-sirfq/v1/quotes #

Creates a draft quote. Needs sirfq_edit_quotes. The draft is owned by the key’s user. Returns 201 with the full quote.

ParameterNotes
emailCustomer email. Required unless customer_id is given.
customer_idWordPress user id. Fills the email from the account when email is empty.
name, company, phoneText.
billing, shippingAddress objects: first_name, last_name, company, address_1, address_2, city, state, postcode, country.
add_linesArray of lines to add: {product_id, qty} (use the variation id as product_id, or variation_id, for a variation) or {custom: true, title, qty, price, taxable}.
ErrorWhen
400 sirfq_emailNo customer_id and no valid email.
400 sirfq_customercustomer_id is not a user.
400 (a line error)A product in add_lines cannot be quoted.
curl -X POST -u "ck_your_key:cs_your_secret" \
  -H "Content-Type: application/json" \
  -d '{"name":"Sarah Miller","email":"[email protected]","company":"Miller Events","add_lines":[{"product_id":123,"qty":2},{"custom":true,"title":"On-site setup","qty":1,"price":"75","taxable":false}]}' \
  "https://example.com/wp-json/wc-sirfq/v1/quotes"

PUT /wc-sirfq/v1/quotes/{id} #

Changes the working copy of a quote. PATCH works the same. Needs sirfq_edit_quotes. Send only the fields you want to change; the rest stay as they are. Returns the full quote.

lock_version is required: read it from the quote just before you edit. If someone else changed the quote since, the request is refused and nothing is saved. Every change (a save, a message, a send) raises the number, and not always by exactly 1, so always re-read it.

ParameterNotes
lock_versionRequired. Integer, from the latest read.
name, email, company, phone, customer_idCustomer fields.
billing, shipping_addressAddress objects, merged over the current address.
owner_user_idStaff user id, 0 to unassign.
conversioncheckout or order. Anything else clears the quote’s own setting and the store default applies.
expiryYYYY-MM-DD, planned expiry for a quote not yet sent.
discount{mode, value} or null to remove. mode is percent (a percentage off), amount (a fixed amount off) or target (the value is the subtotal you want; the discount is the difference).
shipping{label, cost, taxable, locked}, or null to remove. locked: false lets the customer change it.
tax_exemptBoolean.
message, termsText shown to the customer.
feesArray of {label, amount, taxable, tax_class}. It replaces the extra fees; [] removes them.
linesEdits to lines already on the quote: {id, qty, proposal_type, proposal_value, note, staff_note, remove, title, price}. title and price apply to custom lines. remove: true deletes the line.
add_linesNew lines, same format as on create.
ErrorWhen
400 sirfq_lock_versionlock_version is missing.
400 sirfq_lineA line in lines has no id, or does not belong to this quote.
409 sirfq_stalelock_version is out of date.
409 sirfq_not_editableThe quote is accepted, ordered or closed. Reopen it first.
409 sirfq_saveThe editor refused the values (the message says why).
curl -X PUT -u "ck_your_key:cs_your_secret" \
  -H "Content-Type: application/json" \
  -d '{"lock_version":2,"message":"Here is your quote.","discount":{"mode":"percent","value":10},"shipping":{"label":"Courier","cost":15,"taxable":false,"locked":true},"fees":[{"label":"Handling","amount":5,"taxable":false}],"expiry":"2026-12-31","lines":[{"id":456,"qty":3,"staff_note":"internal only"}]}' \
  "https://example.com/wp-json/wc-sirfq/v1/quotes/123"

Quote actions #

POST /wc-sirfq/v1/quotes/{id}/{action} #

Moves a quote to its next status. action is one of send, reject, cancel, reopen, extend. Needs sirfq_send_quotes. Returns the full quote after the move.

ActionAllowed fromResult
senddraft, pending, sent, changes requestedLocks the working version and sets the status to sent. Every line needs a price. Sending again re-sends the quote.
rejectpending, changes requestedrejected. A reason is required.
canceldraft, pending, sent, changes requested, acceptedcancelled.
reopenaccepted, expired, declined, rejected, cancelledAn accepted quote goes back to sent; the others go to pending.
extendsent, acceptedKeeps the status and moves the date: expires_at for a sent quote, pay_by for an accepted one.
ParameterNotes
notifyBoolean, default true. Whether to email the customer. reject and cancel only email when it is true; send emails the quote unless it is false.
messagesend only: the note that goes with the quote.
reasonreject and cancel: saved on the quote, and shown to the customer when notify is true. Required for reject.
expires_atsend and extend: YYYY-MM-DD (end of that day, store time) or a datetime. Must be in the future.
pay_byextend on an accepted quote: the new pay-by date, same format.
lock_versionOptional on all actions. See the note below.

Do not send lock_version with send. Sending recalculates the quote first, which raises the counter, so a send that carries lock_version is refused as stale in the released code. The other actions check it normally.

ErrorWhen
400 sirfq_dateexpires_at is not in the future (send).
409 not_allowedThe action is not possible from the current status. data.quote_status names the status.
409 stalelock_version is out of date. data.lock_version has the current value.
409 no_pricesend: a line has no price.
409 no_lines, no_emailsend: the quote has no lines or no customer email.
409 reasonreject without a reason.
400 sirfq_dateextend with a missing or past date.
curl -X POST -u "ck_your_key:cs_your_secret" \
  -H "Content-Type: application/json" \
  -d '{"message":"Your quote is ready.","expires_at":"2026-12-31","notify":true}' \
  "https://example.com/wp-json/wc-sirfq/v1/quotes/123/send"

curl -X POST -u "ck_your_key:cs_your_secret" \
  -H "Content-Type: application/json" \
  -d '{"reason":"Customer withdrew the request","notify":false}' \
  "https://example.com/wp-json/wc-sirfq/v1/quotes/123/cancel"

Orders #

POST /wc-sirfq/v1/quotes/{id}/order #

Creates the WooCommerce pay-for-order order from a quote, on the customer’s behalf. Needs sirfq_convert_quotes. The quote must be sent or accepted; a sent quote is accepted by this call. No body. Returns 201:

FieldNotes
order_id, order_numberThe new order.
statusThe order status, usually pending.
totalOrder total.
pay_urlThe payment link to give to the customer.
quoteThe full quote, now accepted with its order_id.

The quote moves to ordered when the order is paid.

ErrorWhen
409 statusThe quote is not sent or accepted.
409 (other)The order could not be created, for example a product is no longer available. The message says why.
curl -X POST -u "ck_your_key:cs_your_secret" \
  "https://example.com/wp-json/wc-sirfq/v1/quotes/123/order"

POST /wc-sirfq/v1/quotes/{id}/mark-ordered #

Marks an accepted quote as ordered without creating a WooCommerce order. Use it when staff took the order outside the store. Needs sirfq_convert_quotes. Returns the full quote.

ParameterNotes
noteInternal note saved on the quote.
lock_versionOptional.
ErrorWhen
409 mark_orderedThe quote is not accepted, already has an order, or has rental items (create its order instead, so the rentals are booked).
409 stalelock_version is out of date.

Messages and versions #

GET /wc-sirfq/v1/quotes/{id}/messages #

Lists the quote’s messages, oldest first, internal notes included. Needs sirfq_view_quotes.

FieldNotes
idMessage id.
authorstaff or customer.
user_idThe staff user, or null.
kindmessage, note, reject_reason and so on.
internaltrue for a note the customer cannot see.
bodyText.
version_idThe version it belongs to, or null.
created_atRFC 3339, UTC.

POST /wc-sirfq/v1/quotes/{id}/messages #

Adds a staff message. Needs sirfq_edit_quotes. Returns 201 with the message. It does not email the customer. A message with internal: false shows on the customer’s quote page.

ParameterNotes
bodyRequired text.
internalBoolean, default true. false makes it visible to the customer.
ErrorWhen
400 emptybody is empty.
curl -X POST -u "ck_your_key:cs_your_secret" \
  -H "Content-Type: application/json" \
  -d '{"body":"Customer prefers delivery on Friday."}' \
  "https://example.com/wp-json/wc-sirfq/v1/quotes/123/messages"

GET /wc-sirfq/v1/quotes/{id}/versions #

Lists every version of the quote, oldest first. A version is a snapshot of the lines and prices; one is locked when the quote is sent, and a customer’s change request or a staff edit after sending starts a new one. Needs sirfq_view_quotes. Each item has the version fields shown under version above, plus lines: the version’s lines in the same shape as on the full quote.

Rules #

A rule hides the price or Add to Cart, or makes a product quote-only, for a scope and an audience. These are the same rules as WooCommerce > Settings > Quotes > Rules. All /rules endpoints need sirfq_manage_quote_settings. Rule changes take effect on the storefront right away, so create a new rule with enabled: false and switch it on when it is ready.

A rule has these fields:

FieldNotes
idRead only.
nameText.
enabledBoolean.
priorityInteger, default 10.
scopeall, products, categories, tags or out_of_stock. Anything else is stored as all.
idsProduct, category or tag ids, for the matching scope.
audienceeveryone, guests, logged_in, roles or users. Anything else is stored as everyone.
rolesRole slugs, when audience is roles.
usersUser ids, when audience is users.
actionshide_price (boolean), cart (show, hide, replace or empty), quote_only (boolean), hidden_text (shown instead of the price), replacement_label (the button text when cart is replace).

The endpoints do not return validation errors: unknown values are cleaned or replaced by the defaults above, so read the response to see what was saved.

GET /wc-sirfq/v1/rules #

Lists all rules, ordered by priority. Not paginated.

GET /wc-sirfq/v1/rules/{id} #

Returns one rule. 404 sirfq_not_found when it does not exist.

POST /wc-sirfq/v1/rules #

Creates a rule from the fields above. Returns 201 with the rule.

curl -X POST -u "ck_your_key:cs_your_secret" \
  -H "Content-Type: application/json" \
  -d '{"name":"Quote only for guests","enabled":false,"priority":20,"scope":"categories","ids":[15],"audience":"guests","actions":{"hide_price":true,"cart":"replace","quote_only":true,"hidden_text":"Ask for a price","replacement_label":"Request a quote"}}' \
  "https://example.com/wp-json/wc-sirfq/v1/rules"

PUT /wc-sirfq/v1/rules/{id} #

Changes a rule. The fields you send replace the rule’s; the others stay. Inside actions, the keys you send are merged over the current ones. Returns the rule. 404 sirfq_not_found when it does not exist.

DELETE /wc-sirfq/v1/rules/{id} #

Deletes a rule. Returns {"deleted": true, "previous": {...}} with the rule as it was. 404 sirfq_not_found when it does not exist.

Hooks #

The sirfq_rest_quote filter changes the quote array before the API returns it (it receives the array and the quote object). Quote status changes made through the API fire the same sirfq_quote_transition and sirfq_quote_status_{status} hooks as the admin screens. The hooks are listed in the quote emails, privacy and developer notes.