Get Plugin

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.
Anonymous requests return 401 Unauthorized.

Endpoints

GET /wp-json/epdi/v1/documents/{order_id}

Returns metadata for the document associated with an order.

  • order_id (integer, required) — the WooCommerce order ID.
{
  "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"
}
  • 401 — not authenticated.
  • 403 — missing manage_woocommerce capability.
  • 404 — order doesn't exist.
  • 404 — order has no PDF yet (call regenerate to create one).
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.

  • order_id (integer, required).
  • Content-Type: application/pdf
  • Content-Disposition: attachment; filename="invoice-INV-2026-000042.pdf"
  • Content-Length: 23847
  • Binary PDF body.
  • 401, 403, 404 — same as the metadata endpoint.
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.

  • order_id (integer, required).
{
  "force_type": "invoice"
}
  • force_type (string, optional) — one of invoice, receipt, credit-note, packing-slip. Overrides the auto-resolved type.

Same structure as GET /documents/{order_id} with the new metadata.

  • 401, 403, 404 — same as above.
  • 400 — invalid force_type.
  • 422 — order is in a state that doesn't generate documents (cancelled, failed). Override with force_type if you really want to.
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.