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:
- Capability — who you are (a WordPress capability the current user holds).
- License tier — what 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.
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()),
]);
2. Cookie authentication
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:
- Go to Users → Your Profile → Application Passwords.
- Create a new application password.
- 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.
| Group | Capability | Grants access to |
|---|---|---|
| Dashboard | wpec_view_dashboard | The eCommerce dashboard / aggregate stats |
| Catalog | wpec_manage_products | Products, variants, product lookup/search writes |
| Catalog | wpec_manage_categories | Categories & attributes |
| Catalog | wpec_manage_brands | Brands |
| Catalog | wpec_manage_reviews | Product reviews |
| Sales | wpec_manage_orders | Orders, line items, payments, shipments, courier, bulk actions |
| Sales | wpec_manage_customers | Customer records & analytics |
| Sales | wpec_manage_coupons | Coupon CRUD (/admin/coupons) |
| Sales | wpec_view_reports | Sales reports |
| Operations | wpec_manage_shipping | Shipping zones/methods & courier configuration |
| Operations | wpec_manage_payments | Payment methods & gateway configuration |
| Operations | wpec_manage_taxes | Tax classes & rates |
| Operations | wpec_manage_emails | Transactional email templates |
| ERP (Business tier) | wpec_manage_invoicing | Invoicing & myDATA |
| ERP (Business tier) | wpec_manage_inventory | Inventory / WMS (warehouses, stock) |
| ERP (Business tier) | wpec_manage_purchasing | Suppliers & purchase orders |
| ERP (Business tier) | wpec_manage_accounting | Accounting / General Ledger |
| ERP (Business tier) | wpec_manage_crm | CRM (contacts, leads, activities) |
| Administration | wpec_manage_settings | Store settings & ERP configuration |
| Administration | wpec_manage_managers | Shop managers & custom roles |
| Administration | wpec_manage_tools | Tools (import/export, etc.) |
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) | Label | Capabilities |
|---|---|---|
shop_head_manager | Head Manager | All wpec_* capabilities |
shop_sales_manager | Sales Manager | dashboard, orders, customers, coupons, reports, invoicing, CRM |
shop_content_manager | Content Manager | dashboard, products, categories, brands, reviews |
shop_product_manager | Product Manager | dashboard, 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.
| Tier | Features (feature IDs) |
|---|---|
| Starter | core_ecommerce, products, basic_payments (COD + bank transfer), premium_payments, premium_shipping, marketplace_feeds |
| Business | erp_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
| Aspect | Value |
|---|---|
| Transport | HTTP (MCP 2025-06-18), JSON-RPC style |
| Authentication | WordPress Application Passwords (Basic auth) — nonces/cookies are not used |
| Transport-level gate | current_user_can('manage_options') — administrator only |
| Prerequisites | The 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
- REST API Overview — the three namespaces, the controller map, and the OpenAPI spec.
- Products · Orders · Cart — worked examples per resource.
- Flavor AI (Abilities & MCP) — the MCP endpoint in depth.