P Prime ChatFlow docs

WordPress & WooCommerce

REST API reference

The plugin and the Prime ChatFlow server talk to each other in two directions over HTTP, with two different auth schemes. This reference documents every endpoint involved — request shape, response shape, status codes, and how authentication is enforced.

Auth at a glance

DirectionCredentialWhere it livesReplay window
Plugin → Prime ChatFlow Bearer API token pbar_… Prime ChatFlow stores only the SHA-256 hash. Plugin keeps plaintext in wp_options.
Plugin → Prime ChatFlow (mutating) Bearer + HMAC body signature HMAC key is the bearer plaintext itself. 5 min
Prime ChatFlow → Plugin HMAC body signature Per-token shopper_signing_secret (plaintext on both sides). 5 min

HMAC signature scheme

X-Prime-ChatFlow-Signature: t=<unix_ts>,v1=<hex_sig>
sig = hmac_sha256(secret, "{t}.{raw_body}")

Both sides reject requests whose t is more than 300 seconds away from the verifying server's clock. Both sides constant-time-compare the signature with hash_equals. The same scheme is used for outbound webhooks documented under Outgoing webhooks.

Prime ChatFlow endpoints (plugin → Prime ChatFlow)

Base: your Prime ChatFlow workspace URL. All routes are POST and require Authorization: Bearer pbar_… with the wp:integration ability. Mutating routes additionally require X-Prime-ChatFlow-Signature.

POST /api/v1/wp/handshake

Called by the plugin's Test connection button to discover the workspace, list available agents, and capture the token's shopper_signing_secret. No HMAC required (the bearer is sufficient — the handshake doesn't mutate state beyond writing last_used_at).

Request:

{
  "site_url": "https://shop.example.com",
  "plugin_version": "2.0.4",
  "woocommerce_active": true,
  "wordpress_version": "6.6"
}

Response 200:

{
  "data": {
    "workspace": { "id": "01HZ…", "name": "Acme" },
    "agents": [
      { "id": "01HZ…", "name": "Storefront bot", "site_type": "ecommerce", "language_default": "en", "is_published": true }
    ],
    "token": {
      "id": "01HZ…",
      "name": "shop.example.com",
      "abilities": ["wp:integration"],
      "shopper_signing_secret": "sek_…"
    },
    "recommended_site_type": "ecommerce",
    "echo": { "site_url": "https://shop.example.com", "plugin_version": "2.0.4" }
  }
}

recommended_site_type hints at which vertical the agent should adopt: ecommerce when WooCommerce is active on the calling site, otherwise null. shopper_signing_secret is plaintext — the plugin stashes it in wp_options silently.

Errors:

  • 401 invalid_token — bearer missing, malformed, or revoked.
  • 403 insufficient_ability — token doesn't grant wp:integration.

POST /api/v1/wp/posts/sync

Bulk post upsert. Up to 50 posts per request. Requires HMAC.

{
  "agent_id": "01HZ…",
  "site_url": "https://shop.example.com",
  "plugin_version": "2.0.4",
  "posts": [
    {
      "wp_id": 142,
      "post_type": "page",
      "permalink": "https://shop.example/pricing",
      "title": "Pricing",
      "content_html": "<div class=\"elementor-…\">…</div>",
      "excerpt": "Three plans, two outcomes…",
      "content_hash": "ab12…ef90",
      "modified_at": "2026-05-09T14:30:00+00:00",
      "language": "en-us",
      "taxonomy_terms": ["pricing", "plans"]
    }
  ]
}

Response 200:

{
  "data": {
    "queued": 1,
    "skipped_unchanged": 0,
    "deleted": 0
  }
}

queued = number of posts whose hash differed from the stored Document and were queued for embedding. skipped_unchanged = number whose hash matched (no cost). The vector store is updated asynchronously by IndexDocumentJob; subsequent retrievals start finding the new content within seconds.

POST /api/v1/wp/posts/changed

Single-post delta. Same shape as posts/sync but with a posts array of length 1 and an extra action field of "upsert" or "delete". HMAC required.

POST /api/v1/wp/products/sync

Bulk WooCommerce product upsert. Up to 50 per batch. HMAC required.

{
  "agent_id": "01HZ…",
  "site_url": "https://shop.example.com",
  "plugin_version": "2.0.4",
  "products": [
    {
      "wp_id": 9001,
      "sku": "T-BLU-M",
      "name": "Blue tee",
      "permalink": "https://shop.example/product/blue-tee",
      "image_url": "https://shop.example/wp-content/uploads/2026/05/blue-tee-300x300.jpg",
      "short_description": "<p>Crew-neck cotton tee.</p>",
      "description": "<p>100% combed ring-spun cotton…</p>",
      "price": "29.00",
      "regular_price": "39.00",
      "sale_price": "29.00",
      "currency": "USD",
      "stock_status": "instock",
      "on_sale": true,
      "content_hash": "cd34…12ef",
      "modified_at": "2026-05-09T14:30:00+00:00",
      "categories": ["tees", "summer"],
      "attributes": ["color: blue", "size: S, M, L"]
    }
  ]
}

On first upsert against an agent with site_type = "generic", the agent is silently switched to "ecommerce". See Content sync for the full rules.

POST /api/v1/wp/products/changed

Single-product delta. Same as posts/changed but for WC products. HMAC required.

POST /api/v1/wp/coupons/sync

Snapshots the store's active coupons. Idempotent (full-replace).

{
  "agent_id": "01HZ…",
  "site_url": "https://shop.example.com",
  "plugin_version": "2.0.4",
  "coupons": [
    { "code": "WELCOME10", "label": "10% off", "discount": "10%", "expires_at": null },
    { "code": "FREESHIP",  "label": "5 off your order", "discount": "5", "expires_at": "2026-12-31T00:00:00+00:00" }
  ]
}

The list is persisted on the agent's woocommerce_products source under config['coupons']. Subsequent prompt assembly includes the codes verbatim so the LLM never invents them.

POST /api/v1/widget/coupon/apply

Called by the widget's Apply button on a <coupon/> chat block. Auth is the widget JWT (not a bearer token), throttle 30/min/IP.

{ "code": "WELCOME10" }

Prime ChatFlow resolves the agent's WordPress / WooCommerce source, HMAC-signs the body with the workspace's shopper signing secret, and forwards to /wp-json/pitchbar/v1/cart/coupon on the WP site. The forwarded body includes conversation_id so the plugin can stage the coupon in a per-conversation transient.

Plugin endpoints (Prime ChatFlow → plugin)

Base: {wp_site_url}/wp-json/pitchbar/v1/. All routes are POST. Auth: X-Prime-ChatFlow-Signature verified against the plugin's stored shopper_signing_secret. No WordPress nonce or cookie auth — the caller is the Prime ChatFlow server, not a logged-in browser.

Verification path

Every plugin REST controller extends Prime ChatFlow\Rest\RestController which gates each request via verifyOrReject($request):

  1. Read the X-Prime-ChatFlow-Signature header. If missing → 401 missing_signature.
  2. Read the plugin's stored shopper_signing_secret from wp_options. If empty → 401 plugin_unconfigured.
  3. Compute the expected signature over the raw request body. If hash_equals fails → 401 signature_mismatch.
  4. If the timestamp t is > 300s away from time() → reject with signature_mismatch too (the secret never matched the replayed timestamp).

The error response is always JSON-wrapped:

{ "error": { "code": "signature_mismatch", "message": "HMAC signature did not verify." } }

POST /wp-json/pitchbar/v1/orders/lookup

Looks up a customer's recent WooCommerce orders.

{
  "wp_user_id": 42,
  "limit": 5,
  "order_number": "WC-1234"   // optional — filter the result set
}

Response 200:

{
  "data": {
    "count": 2,
    "orders": [
      {
        "id": 9001,
        "number": "9001",
        "status": "completed",
        "total": "49.99",
        "currency": "USD",
        "date_created": "2026-05-08T11:23:00+00:00",
        "items": [{ "name": "Blue tee", "qty": 1, "sku": "T-BLU-M", "total": "29.00" }],
        "tracking_url": "https://aftership.com/…",
        "order_url": "https://shop.example/my-account/view-order/9001/"
      }
    ]
  }
}

tracking_url is best-effort: the controller checks _aftership_tracking_url, _tracking_url, and _st_tracking_link order meta keys. If none match, the field is an empty string and the LLM falls back to surfacing order_url.

When WooCommerce isn't active, the controller returns 200 with { "orders": [], "count": 0, "note": "woocommerce_inactive" } so the agent can answer gracefully.

POST /wp-json/pitchbar/v1/leads

Pushes a captured Prime ChatFlow lead back into WordPress.

{
  "email": "shopper@example.com",
  "name": "Alex Visitor",
  "phone": "+1-555-0123",
  "conversation_id": "01HZ…",
  "pitchbar_lead_id": "01HZ…"
}

Response 200:

{ "data": { "user_id": 199 } }

Behaviour:

  • If a WP user with that email exists, update its first_name, billing_phone, and Prime ChatFlow meta keys.
  • If no user, create a WC customer (wc_create_new_customer) when Woo is active, otherwise a WP subscriber via wp_create_user with a random 24-char password.
  • Username is derived from the local part of the email + a numeric suffix until unique.
  • pitchbar_lead_id + pitchbar_conversation_id are written to user meta so the store owner can correlate.

POST /wp-json/pitchbar/v1/cart/coupon

Stages a coupon code for the visitor's next cart load.

{
  "code": "WELCOME10",
  "conversation_id": "01HZ…"
}

Response 200:

{
  "data": {
    "applied": false,
    "pending": true,
    "message": "Coupon staged. It will apply when the visitor opens their cart."
  }
}

Behaviour:

  1. Validates the coupon exists via new WC_Coupon($code) + get_id() ≠ 0. If not, returns 400 invalid_coupon.
  2. Stages the code in a 15-minute transient: pitchbar_pending_coupon_{conversation_id}.
  3. The plugin's woocommerce_load_cart_from_session hook reads the transient on the visitor's next cart load (located by the pitchbar_conv_id cookie the widget writes at init), calls WC()->cart->apply_coupon($code), and clears the transient.

When WooCommerce isn't active, returns 400 woocommerce_inactive. The coupon-card Apply button in the widget is hidden via a feature flag in that case.

Error envelope

Every Prime ChatFlow endpoint returns errors as:

{ "message": "…", "code": "…", "errors": { "field": ["…"] } }

Every plugin endpoint returns errors as:

{ "error": { "code": "…", "message": "…" } }

The shape difference is intentional — Prime ChatFlow follows Laravel's validator convention, plugin follows WP REST convention. Both sides parse the other transparently.

Throttling

Prime ChatFlow's /api/v1/wp/* routes throttle by API token (default 60 req/min per token). The plugin's /wp-json/pitchbar/v1/* routes don't enforce a quota — the HMAC check + 5-minute replay window already prevents abuse, and the upstream Prime ChatFlow timeout (5s on OrderLookupController) bounds runtime.

Status codes summary

CodeMeaning
200Success or "ignored as no-op" (delete of unknown post).
400Validation error — body shape wrong, missing field, invalid coupon.
401Auth failed (missing token / signature / wrong secret).
403Token missing required ability.
404Resource doesn't exist on this workspace (most often the agent_id).
422Validation error from Laravel's validator (Prime ChatFlow side).
429Throttled.