The REST API for PDF Invoices, Pick Lists & Packing Slips: list the documents an order has, issue its invoice, and fetch an issued PDF. It is built for a back-office tool that issues and files invoices, and for a headless store that shows its customers a link to their invoice. The setup and the conventions shared by every endpoint (keys, errors, pagination) are in <a href=”/docs/rest-api/”>REST API overview and authentication</a>.
Base URL and sign-in #
| Namespace | What lives there | Needs |
|---|---|---|
/wp-json/sidocs/v1/ | orders/{id}/documents (list, issue) and documents/{id}/pdf (fetch) | PDF Invoices, Pick Lists & Packing Slips, with WooCommerce |
sidocs/v1does not start withwc-, so a WooCommerce REST API key does not work here. A call with one answers401(invalid_username). Use a WordPress Application Password (Users > Profile > Application Passwords) as HTTP Basic (username:application password), or, inside wp-admin, the logged-in cookie with anX-WP-Nonceheader.- The routes have been in PDF Invoices since 1.0.0.
- Send bodies as JSON (
Content-Type: application/json). - Errors use the WordPress shape
{"code", "message", "data": {"status"}}. The PDF Invoices codes start withsidocs_.
STORE="https://example.com/wp-json"
curl -u "username:YOUR_APP_PASSWORD" "$STORE/sidocs/v1/orders/123/documents"
Who may do what #
| Ability | Who has it | Opens |
|---|---|---|
edit_shop_orders or manage_woocommerce | Shop managers and administrators, and any role given one of them | All three routes, for every order |
| The order’s key (below) | Anyone who holds it | GET /orders/, for that order, and only the documents a customer may have |
A request that is not allowed answers 401 (not signed in) or 403, code rest_forbidden.
The order key for customers and headless stores #
A store front end can read an order’s documents without a staff login by sending the WooCommerce order key as key: the value that starts with wc_order_, which is the order_key field of the order in the WooCommerce API and the key in the order’s thank-you and order-view links. Send it in the query string (?key=). The key works like this:
- It has to match the order in the URL. A wrong or missing key answers
401rest_forbidden. - A guest order (no customer account) opens with the key alone, as long as Guests is ticked in the plugin’s Settings (the box is ticked by default).
- An order that belongs to a customer account opens only when the request is also signed in as that customer (an Application Password or the logged-in cookie of that user). The key alone is refused. The key is not a login.
- The key never opens the other two routes. Issuing and
GET /documents/{id}/pdfare staff only.
What a key holder sees is limited by the Settings tab, My Account shows: by default only the invoice, and only once it has been issued. A customer never makes a number.
Documents of an order #
GET /sidocs/v1/orders/{id}/documents #
Lists the document types this order gets, with the number and issue time of those already issued.
| Parameter | Notes |
|---|---|
id | In the path. The WooCommerce order id |
key | Optional for staff. For a customer or a headless store: the order key (above) |
Staff get every type that applies to the order. A key holder gets only the types a customer may open. Each entry:
| Field | Notes |
|---|---|
type | The document type: invoice, packing-slip, and, on a store with rentals, return-slip (rental orders) and deposit-receipt (when a deposit was refunded). Credit notes and pick lists are not listed here: a credit note is made by a refund, and a pick list covers many orders |
label | The name shown to people (Invoice) |
number | The document number (INV-0082) for a numbered type that has been issued; empty otherwise |
issued | When it was issued, in UTC (Y-m-d H:i:s); empty otherwise |
url | A link the customer can open to get the PDF, built with the order key. Empty when a customer may not open this type yet, for example an invoice that has not been issued |
[
{
"type": "invoice",
"label": "Invoice",
"number": "INV-0082",
"issued": "2026-11-11 01:29:19",
"url": "https://example.com/?sidocs_doc=invoice&order=123&key=wc_order_AbCdEf123456"
},
{
"type": "packing-slip",
"label": "Packing slip",
"number": "",
"issued": "",
"url": ""
}
]
| Error | When |
|---|---|
404 sidocs_order | The order does not exist (staff only: a key holder for a missing order gets 401) |
401 or 403 rest_forbidden | Not staff, and the key is missing, wrong, or the order belongs to an account you are not signed in as |
POST /sidocs/v1/orders/{id}/documents #
Issues a numbered document for the order. Staff only. This is how a back-office tool issues an invoice.
| Parameter | Notes |
|---|---|
type | Required. A numbered, kept type. Use invoice. A type that is not numbered (packing-slip, return-slip) cannot be issued: it is drawn fresh each time it is opened |
It answers 200:
| Field | Notes |
|---|---|
id | The document id, the one GET /documents/{id}/pdf takes |
number | The number taken from the series (INV-0082) |
What issuing does:
- The call takes the next number in the type’s series (a prefix, no gaps, no duplicates, yearly reset if you set one) and freezes what the document says and how it looks. Later changes to the order, the brand or the template do not change it.
- It is safe to repeat. If the order already has an issued invoice, the call returns that document’s
idandnumberand takes no new number. This is also how you look up the id of an invoice that was issued earlier. - Issuing the invoice by this call is the same as printing it on the order screen. The store can also issue it by itself when the order reaches a status, set in Settings under Issue the invoice when an order becomes.
- The PDF is drawn when the number is taken and stored. Stored PDF files are deleted after the retention period you set (30 days by default) and drawn again, identical in content, when someone opens the document.
| Error | When |
|---|---|
404 sidocs_order | The order does not exist, type is missing, or the type is not a numbered one |
401 or 403 rest_forbidden | Not staff |
500 sidocs_busy | The number series was busy, even after retries. Send the call again |
500 sidocs_series | The number series could not be read |
The PDF #
GET /sidocs/v1/documents/{id}/pdf #
Returns one issued document’s PDF inside JSON, as base64. Staff only. The response is application/json, not a PDF, so a client has to decode the pdf field.
| Parameter | Notes |
|---|---|
id | In the path. The document id from POST /orders/ |
| Field | Notes |
|---|---|
number | The document number |
pdf | The file, base64 encoded |
It checks the stored file against the record when it reads it. If the file was removed by the retention clean-up, it is drawn again from the frozen data. Save it from the shell like this:
curl -s -u "username:YOUR_APP_PASSWORD" \
"https://example.com/wp-json/sidocs/v1/documents/456/pdf" \
| python3 -c "import sys, json, base64; d = json.load(sys.stdin); open(d['number'] + '.pdf', 'wb').write(base64.b64decode(d['pdf']))"
| Error | When |
|---|---|
404 sidocs_document | No document with that id |
401 or 403 rest_forbidden | Not staff |
500 sidocs_tampered | The stored file does not match its record, so it was not served |
500 sidocs_draw | The file was gone and could not be drawn again. See WooCommerce > Status > Logs |
Getting the PDF as a file, for a customer #
A customer or a headless store does not use the REST route for the file. It opens the url from GET /orders/. That link is a normal page of the store, not a REST route, and it answers the PDF itself:
Content-Type: application/pdf,Content-Disposition: attachment; filename=."INV-0082. pdf" - Access is the same as the list: the
keyhas to match the order, a guest order needs Guests ticked, and an order of a customer account needs that customer signed in (the cookie). Without those it answers403with an HTML message, not JSON. - Only the types a customer may have are served, and a numbered one only once it has been issued.
Save it with curl’s -o:
curl -s -o INV-0082.pdf \
"https://example.com/?sidocs_doc=invoice&order=123&key=wc_order_AbCdEf123456"
Check what you got with file INV-0082.pdf (it should say PDF document). A 403 page saves as HTML under the same name, so check the status first with -w "%{http_code}" if a script depends on it.
Examples #
A headless store: show the customer the documents of their guest order, then fetch the invoice.
curl "https://example.com/wp-json/sidocs/v1/orders/123/documents?key=wc_order_AbCdEf123456"
A back-office tool: issue the invoice, then store the PDF.
curl -u "username:YOUR_APP_PASSWORD" -X POST \
-H "Content-Type: application/json" \
-d '{"type": "invoice"}' \
"https://example.com/wp-json/sidocs/v1/orders/123/documents"
{
"id": 456,
"number": "INV-0082"
}
Then call GET /documents/456/pdf as shown above.
Hooks #
The plugin has filters for the file name (sidocs_file_name) and for adding document types (sidocs_documents). Neither is needed to use the API.
