Skip to main content

API Authentication

Every Flavor REST namespace (ec/v1, flavor/v1, flavor-core/v1) is protected by native WordPress authentication — there is no separate token service or JWT layer. Authorization is then a two-axis decision:

  1. Capabilitywho you are (a WordPress capability the current user holds).
  2. License tierwhat the store is entitled to (ERP groups require a Business-tier license, independent of the user's capability).

Both must pass. This page covers each in turn, plus the separate MCP / AI-Access authentication.

First-party reference

This is a first-party developer reference for building against your own store (headless frontends, admin tooling, integrations, internal automation). Routes evolve between releases — pin your integration to a store version and re-check against the OpenAPI spec after upgrades.

Authentication methods

1. WordPress nonce (same-origin)

For requests from the WordPress frontend or admin (the storefront React app, the admin app):

// WordPress provides the nonce automatically
const response = await fetch('/wp-json/ec/v1/cart', {
headers: {
'X-WP-Nonce': window.wpApiSettings?.nonce,
'Content-Type': 'application/json',
},
});

The nonce is exposed through wp_localize_script():

wp_localize_script('my-script', 'wpApiSettings', [
'nonce' => wp_create_nonce('wp_rest'),
'root' => esc_url_raw(rest_url()),
]);

WordPress cookie authentication works automatically for logged-in users when the request also carries the nonce (X-WP-Nonce). Same-origin browser requests from an authenticated admin need nothing more than the two headers above.

3. Application Passwords (external)

For integrations, scripts, mobile apps, or anything off-origin, use WordPress Application Passwords with HTTP Basic auth:

  1. Go to Users → Your Profile → Application Passwords.
  2. Create a new application password.
  3. Send it as Basic auth on every request:
curl -u "admin:xxxx xxxx xxxx xxxx" \
https://your-site.com/wp-json/ec/v1/orders
const credentials = btoa('admin:xxxx xxxx xxxx xxxx');
fetch('https://your-site.com/wp-json/ec/v1/orders', {
headers: { 'Authorization': `Basic ${credentials}` },
});

The user behind the Application Password carries their full WordPress capability set — a password created by a shop manager gets exactly that manager's scoped access (see below), a password created by an administrator gets manage_options.


Authorization axis 1 — capabilities (RBAC)

Admin and staff operations are gated by granular wpec_manage_* capabilities, not by a single "is admin" flag. This lets a store grant scoped REST access to staff without handing out full manage_options. manage_options (administrator) is always accepted as a superset of every capability.

Most controllers check the pattern:

current_user_can('manage_options') || current_user_can('wpec_manage_<area>')

The capability map

These are the capabilities the plugin registers. They are the authoritative set (source: ManagerService::CAPABILITY_GROUPS) — enumerate them live at GET /ec/v1/managers/capabilities.

GroupCapabilityGrants access to
Dashboardwpec_view_dashboardThe eCommerce dashboard / aggregate stats
Catalogwpec_manage_productsProducts, variants, product lookup/search writes
Catalogwpec_manage_categoriesCategories & attributes
Catalogwpec_manage_brandsBrands
Catalogwpec_manage_reviewsProduct reviews
Saleswpec_manage_ordersOrders, line items, payments, shipments, courier, bulk actions
Saleswpec_manage_customersCustomer records & analytics
Saleswpec_manage_couponsCoupon CRUD (/admin/coupons)
Saleswpec_view_reportsSales reports
Operationswpec_manage_shippingShipping zones/methods & courier configuration
Operationswpec_manage_paymentsPayment methods & gateway configuration
Operationswpec_manage_taxesTax classes & rates
Operationswpec_manage_emailsTransactional email templates
ERP (Business tier)wpec_manage_invoicingInvoicing & myDATA
ERP (Business tier)wpec_manage_inventoryInventory / WMS (warehouses, stock)
ERP (Business tier)wpec_manage_purchasingSuppliers & purchase orders
ERP (Business tier)wpec_manage_accountingAccounting / General Ledger
ERP (Business tier)wpec_manage_crmCRM (contacts, leads, activities)
Administrationwpec_manage_settingsStore settings & ERP configuration
Administrationwpec_manage_managersShop managers & custom roles
Administrationwpec_manage_toolsTools (import/export, etc.)
note

A few newer surfaces are not in the canonical capability groups above and gate differently — HR, ΕΡΓΑΝΗ, and Marketplace endpoints. Treat their exact capability requirement as authoritative from the OpenAPI spec / the endpoint's permission_callback, not from this table. (HR/ΕΡΓΑΝΗ are Business-tier and today gate on administrator-level access.)

Predefined shop-manager roles

The plugin registers four ready-made roles (WordPress roles, no custom tables — managers are WP users with shop_* roles). Each also receives the WordPress base caps read, upload_files, edit_posts.

Role (slug)LabelCapabilities
shop_head_managerHead ManagerAll wpec_* capabilities
shop_sales_managerSales Managerdashboard, orders, customers, coupons, reports, invoicing, CRM
shop_content_managerContent Managerdashboard, products, categories, brands, reviews
shop_product_managerProduct Managerdashboard, products, categories, brands, reviews, inventory

Custom roles with any capability subset are created through the Managers API (POST /ec/v1/managers/roles). See the REST overview → Managers (RBAC) group.


Authorization axis 2 — license tier

The wpec_manage_* capability answers "is this user allowed?" The license tier answers "is this feature enabled on this store at all?" — checked independently by the feature gate (LicenseGate::canAccess($featureId)). A user with wpec_manage_invoicing still gets 403 on /invoices if the store is not on a Business licence.

TierFeatures (feature IDs)
Startercore_ecommerce, products, basic_payments (COD + bank transfer), premium_payments, premium_shipping, marketplace_feeds
Businesserp_invoicing, erp_inventory, erp_purchasing, erp_accounting, erp_crm, erp_hr — the full ERP

In practice: the whole ERP surface (invoicing/myDATA, inventory, purchasing, accounting, CRM, HR, ΕΡΓΑΝΗ) returns 403 on an unlicensed or Starter store, in addition to the capability check. Everything else runs on Starter.


MCP / AI-Access authentication

Flavor Core stands up a separate MCP (Model Context Protocol) server that exposes curated, read-only "abilities" to AI agents. It is not a conventional REST group and does not use nonces.

POST /wp-json/flavor/mcp
AspectValue
TransportHTTP (MCP 2025-06-18), JSON-RPC style
AuthenticationWordPress Application Passwords (Basic auth) — nonces/cookies are not used
Transport-level gatecurrent_user_can('manage_options')administrator only
PrerequisitesThe Flavor AI Center component installed, the MCP adapter present, and at least one ability available for the active products (an empty allowlist means no server is stood up)

Authorization is defense-in-depth: after the transport-level manage_options gate, each ability independently re-checks manage_options, verifies the store's license, enforces its own required capability, redacts PII, and applies per-user rate limits (per-minute + per-day). Phase 1 is read-only.

Because the client is an external AI agent/proxy, how that client is configured (env vars, which URL it expects, editor setup) is outside our codebase — see the client's own documentation. The endpoint, route, and auth model here are the parts we control.

→ Full ability contract, the flavor-mcp server, and client setup: Flavor AI (Abilities & MCP).


Error responses

401 Unauthorized

{
"code": "rest_not_logged_in",
"message": "You are not currently logged in.",
"data": { "status": 401 }
}

403 Forbidden

Returned when authenticated but lacking the capability or the required license tier:

{
"code": "rest_forbidden",
"message": "Sorry, you are not allowed to do that.",
"data": { "status": 403 }
}

Branch on the HTTP status first; use code for machine-readable handling.


CORS configuration

For headless setups where the frontend is on a different domain:

// In your theme or plugin
add_filter('rest_pre_serve_request', function ($served, $result, $request) {
header('Access-Control-Allow-Origin: https://your-frontend.com');
header('Access-Control-Allow-Methods: GET, POST, PUT, PATCH, DELETE, OPTIONS');
header('Access-Control-Allow-Headers: Content-Type, X-WP-Nonce, Authorization');
return $served;
}, 10, 3);

Include both X-WP-Nonce and Authorization in Access-Control-Allow-Headers so nonce and Application-Password requests survive the preflight.


Where to go next