Rest Api
REST API
Easy PDF Invoices for WooCommerce exposes three REST endpoints under the epdi/v1 namespace for headless WooCommerce setups, integrations with accounting tools, and custom dashboards. The API is disabled by default and must be enabled in settings.
Enabling the API
Go to WooCommerce > Settings > Invoices > Advanced and toggle Enable REST API.
This was a deliberate design choice: most stores don't need the REST API and exposing additional endpoints unnecessarily increases the attack surface. Enable it only when you actually plan to call it.
Authentication
All endpoints require the manage_woocommerce capability.
For programmatic access, use WooCommerce REST API authentication:
- Application passwords (WordPress 5.6+, recommended for headless setups).
- Basic Auth over HTTPS with WooCommerce REST API consumer key + secret.
- Cookie authentication with a nonce for browser-based JavaScript clients.
401 Unauthorized.
Endpoints
GET /wp-json/epdi/v1/documents/{order_id}
Returns metadata for the document associated with an order.
Path parameters:
order_id(integer, required) — the WooCommerce order ID.
Response (200 OK):
{
"order_id": 123,
"document_type": "receipt",
"invoice_number": 42,
"display_number": "INV-2026-000042",
"generated_date": "2026-04-09T14:23:11+00:00",
"file_size": 23847,
"pdf_url": "https://yourstore.com/?epdi_download=123&key=wc_order_abc123",
"regenerate_url": "https://yourstore.com/wp-json/epdi/v1/documents/123/regenerate"
}
Errors:
401— not authenticated.403— missingmanage_woocommercecapability.404— order doesn't exist.404— order has no PDF yet (call regenerate to create one).
Example with cURL:
curl https://yourstore.com/wp-json/epdi/v1/documents/123 \
-u "ck_xxx:cs_yyy"
GET /wp-json/epdi/v1/documents/{order_id}/pdf
Streams the PDF binary for an order.
Path parameters:
order_id(integer, required).
Response (200 OK):
Content-Type: application/pdfContent-Disposition: attachment; filename="invoice-INV-2026-000042.pdf"Content-Length: 23847- Binary PDF body.
Errors:
401,403,404— same as the metadata endpoint.
Example with cURL:
curl https://yourstore.com/wp-json/epdi/v1/documents/123/pdf \
-u "ck_xxx:cs_yyy" \
--output invoice-123.pdf
POST /wp-json/epdi/v1/documents/{order_id}/regenerate
Forces regeneration of the PDF for an order.
Path parameters:
order_id(integer, required).
Body parameters (optional, JSON):
{
"force_type": "invoice"
}
force_type(string, optional) — one ofinvoice,receipt,credit-note,packing-slip. Overrides the auto-resolved type.
Response (200 OK):
Same structure as GET /documents/{order_id} with the new metadata.
Errors:
401,403,404— same as above.400— invalidforce_type.422— order is in a state that doesn't generate documents (cancelled, failed). Override withforce_typeif you really want to.
Example with cURL:
curl -X POST https://yourstore.com/wp-json/epdi/v1/documents/123/regenerate \
-u "ck_xxx:cs_yyy" \
-H "Content-Type: application/json" \
-d '{"force_type": "receipt"}'
Common patterns
Sync invoices to your accounting tool
After every order completion, fetch the receipt and forward it:
async function syncInvoice(orderId) {
const auth = btoa(`${process.env.WC_CK}:${process.env.WC_CS}`);
const headers = { Authorization: `Basic ${auth}` };
const meta = await fetch(
`https://yourstore.com/wp-json/epdi/v1/documents/${orderId}`,
{ headers }
).then(r => r.json());
const pdf = await fetch(
`https://yourstore.com/wp-json/epdi/v1/documents/${orderId}/pdf`,
{ headers }
).then(r => r.arrayBuffer());
await uploadToQuickBooks({
invoiceNumber: meta.display_number,
orderId: meta.order_id,
generatedDate: meta.generated_date,
pdf: Buffer.from(pdf),
});
}
Hook it to woocommerce_order_status_completed via a webhook or scheduled job.
Bulk regenerate from a script
After updating branding or switching template variants, regenerate the last 30 days of orders:
#!/bin/bash
# requires: jq, curl, WooCommerce REST API credentials in env
CK="$WC_CONSUMER_KEY"
CS="$WC_CONSUMER_SECRET"
SITE="https://yourstore.com"
# Get orders from the last 30 days
SINCE=$(date -u -v-30d +"%Y-%m-%dT%H:%M:%S")
ORDERS=$(curl -s -u "$CK:$CS" \
"$SITE/wp-json/wc/v3/orders?after=$SINCE&per_page=100&status=completed" \
| jq -r '.[].id')
for ID in $ORDERS; do
echo "Regenerating order $ID..."
curl -s -X POST -u "$CK:$CS" \
-H "Content-Type: application/json" \
"$SITE/wp-json/epdi/v1/documents/$ID/regenerate" \
> /dev/null
done
For larger batches, prefer the bulk regenerate action on the order list — it uses Action Scheduler and won't time out.
Headless storefront (Next.js / Remix)
After completing an order in your headless storefront, fetch the receipt and present it inline:
// app/orders/[id]/receipt/route.ts
import { NextRequest } from 'next/server';
export async function GET(
_request: NextRequest,
{ params }: { params: { id: string } }
) {
const auth = Buffer.from(
`${process.env.WC_CK}:${process.env.WC_CS}`
).toString('base64');
const upstream = await fetch(
`${process.env.WC_URL}/wp-json/epdi/v1/documents/${params.id}/pdf`,
{ headers: { Authorization: `Basic ${auth}` } }
);
return new Response(upstream.body, {
headers: {
'Content-Type': 'application/pdf',
'Content-Disposition': 'inline; filename="receipt.pdf"',
},
});
}
The customer-facing route stays clean (/orders/123/receipt) while the auth headers stay server-side.
Rate limiting
The REST endpoints don't have a built-in rate limit. Use a server-level rate limiter (Cloudflare, Nginx, fail2ban) if you're exposing the API to less-trusted callers.
The frontend download endpoint (?epdi_download=...) does have rate limiting — see Customer Downloads.
CORS
The plugin doesn't add custom CORS headers. WooCommerce and WordPress core handle CORS via standard hooks. If you need to call the API from a browser on a different origin, configure CORS at your web server level or via a plugin.
Schema
If you're generating client SDKs, the OpenAPI-style schema lives in the response of:
curl https://yourstore.com/wp-json/epdi/v1/ \
-u "ck_xxx:cs_yyy"
Each route declares its arguments, response shape, and permission callback in the standard WordPress REST schema format.
Extending the API
To add custom endpoints under the same epdi/v1 namespace:
add_action( 'rest_api_init', function() {
register_rest_route( 'epdi/v1', '/documents/(?P<id>\d+)/email', array(
'methods' => 'POST',
'callback' => 'my_email_invoice_handler',
'permission_callback' => function() {
return current_user_can( 'manage_woocommerce' );
},
) );
} );
The plugin's existing endpoints all use the \EasyPDFInvoices\REST\InvoiceController class — read its source if you want to follow the same conventions for argument validation, error responses, and permission checks.
Related
- Customer Downloads — the public download endpoint.
- Admin Tools — bulk operations from the admin UI.
- Developer Guide — full hook and filter reference.