Table of Contents

Building a Headless Solution on the Dynamicweb Delivery API

Build a headless frontend on the Dynamicweb Delivery API with an AI assistant grounded in the Documentation MCP and your solution's live OpenAPI spec

A headless build talks to the Dynamicweb 10 Delivery API (dwapi) over HTTP: you authenticate, fetch products, run queries, drive a cart, and render it in whatever frontend you like. The work is mostly knowing which endpoint, which parameters, which response shape — and that is exactly the kind of thing an AI assistant gets wrong when it's guessing and gets right when it's grounded.

This article is about building that solution with an AI assistant in the loop, kept honest by two sources of truth:

  • The Documentation MCP Server — connects your assistant to the official Dynamicweb developer docs, so it answers "how does the Delivery API work?" from real documentation instead of inventing it.
  • Your solution's live OpenAPI spec — generated off your own running solution, the exact contract of what this build exposes right now.

The pattern throughout: let the assistant do the work, ground it in the docs MCP for the concepts, and pin it to your live spec for the exact contract. The docs tell it how things work in general; the spec tells it what your solution actually exposes. With both connected, you describe what you want to build and the assistant produces code that targets your real API.

Set up the assistant

Two connections turn your editor into a Delivery-API-aware environment:

1. Connect the Documentation MCP. - Follow the Documentation MCP Server article. Once connected, the assistant can search the Dynamicweb docs mid-task — no credential needed, you just authorize once and pick the Dynamicweb 10 Documentation GPT knowledge base.

2. Point it at your live spec. - Every Dynamicweb 10 solution serves its own OpenAPI specification:

https://<yourHost>/dwapi/api.json

This is generated from the actual API surface of your running solution — it declares its title as the Dynamicweb Headless Delivery API and carries the exact platform version it was built from. Give your assistant this URL as context and its generated code targets your real endpoints, not a generic guess.

With both in place, the assistant has the concepts (docs MCP) and the exact contract (live spec) it needs to write code you can trust.

Tip

Use the spec from the specific solution you're building against — dev, staging, and production may run different platform versions and expose slightly different surfaces. The spec is the contract for that environment, and the assistant should be grounded in the right one.

Why ground the assistant at all

Ask an ungrounded assistant to "authenticate against the Dynamicweb Delivery API" and it will produce something plausible — and likely wrong: a guessed endpoint path, an outdated auth flow, an invented response field. Grounding fixes this in two complementary ways:

  • The docs MCP gives it the conceptual model — how JWT auth works, what the query endpoint is for, how paging behaves — straight from the documentation, with references back to the source articles.
  • The live spec gives it the precise truth — the exact paths, parameters, security requirements, and schemas your solution exposes, including details the static docs may lag behind.

A real example of why both matter: the spec marks the old GET /dwapi/users/authenticate (credentials as query parameters) as Obsolete in favour of the POST flow — even where older docs and samples still show the query-string form. An assistant working from your live spec picks the current flow; one working from memory or stale samples may not.

Build the solution by asking

The workflow is a loop: describe → the assistant grounds itself → it generates → you verify against the live API. Here it is across the pieces of a typical headless build.

Authentication

Consider the following prompt:

Using the Dynamicweb docs and our spec at https://<yourHost>/dwapi/api.json, write a TypeScript auth helper for the Delivery API. Use the current (non-obsolete) flow, store the JWT, and add a Bearer header to subsequent calls.

Grounded in both sources, the assistant explains and implements the right flow. For reference, the documented shape it should land on:

POST /dwapi/users/authenticate
Content-Type: application/json

{ "username": "user@example.com", "password": "••••••••" }

It returns a JWT you pass as Authorization: Bearer <token> on authenticated calls. Tokens default to a 30-minute lifetime (1800 seconds), up to a 24-hour maximum, and refresh via GET /dwapi/users/authenticate/refresh. The spec also exposes service auth (POST /dwapi/serviceauth/token, server-to-server) and a cookie exchange (POST /dwapi/users/token, for a user already logged into an SSR frontend via the Dynamicweb.Extranet cookie) — ask the assistant which fits your scenario and it will pull the right one from the spec.

A minimal helper it might produce:

// dwapi.ts
const BASE = "https://<yourHost>/dwapi";
let token: string | null = null;

export async function authenticate(username: string, password: string) {
  const res = await fetch(`${BASE}/users/authenticate`, {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({ username, password }),
  });
  if (!res.ok) throw new Error(`Auth failed: ${res.status}`);
  token = (await res.json()).token;
  return token;
}

function authHeaders(): HeadersInit {
  return token ? { Authorization: `Bearer ${token}` } : {};
}

Products

Consider the following prompt:

What's the products endpoint, does it need auth, and how do I paginate it? Then add a getProducts function to the helper.

The assistant reads the docs and tells you product listing supports anonymous access and pages through PageSize / CurrentPage, returning a product list view model with group context, total counts, page counts, and the product items — enough to render listings with pagination and faceted navigation. The call it builds:

export async function getProducts(params: Record<string, string>) {
  const qs = new URLSearchParams(params).toString();
  const res = await fetch(`${BASE}/ecommerce/products?${qs}`, { headers: { ...authHeaders() } });
  if (!res.ok) throw new Error(`Products failed: ${res.status}`);
  return res.json();
}

// Anonymous — no token needed
const listing = await getProducts({ ShopId: "SHOP1", PageSize: "24", CurrentPage: "1" });

Search and queries

Consider the following prompt:

Using the Dynamicweb docs, explain how the GET /dwapi/query endpoint handles facets and what the QueryResultViewModel looks like, then wire up a search call.

The assistant retrieves that the query endpoint runs a published query and returns a QueryResultViewModel with pageSize, pageCount, currentPage, totalCount, optional spell-checker suggestions, and the results array — the workhorse for search-driven frontends — and generates the call against it.

Cart and checkout

Consider the following prompt:

Check the docs — which commerce endpoints do I use for cart and checkout, and what context do they need?

The assistant explains that commerce endpoints are context-sensitive (they operate against the current cart/order and generally need the user context established via the token) and scaffolds the flow against the endpoints your spec exposes.

Verify against the live API

This is the step that separates "looks right" from "is right." Before trusting generated code, have the assistant — or you — hit the real dwapi and inspect the actual response. Live responses reflect your data, custom fields, configured shops, and real facet values, which no documentation can fully predict.

# Anonymous — inspect the real products response shape
curl "https://<yourHost>/dwapi/ecommerce/products?ShopId=SHOP1&PageSize=2"

# Authenticate, capture the token
TOKEN=$(curl -s -X POST "https://<yourHost>/dwapi/users/authenticate" \
  -H "Content-Type: application/json" \
  -d '{"username":"user@example.com","password":"secret"}' | jq -r .token)

# Use it
curl "https://<yourHost>/dwapi/users/addresses" -H "Authorization: Bearer $TOKEN"

Feed the real response back to the assistant — "here's what the endpoint actually returned, adjust the types to match" — and the generated client converges on the truth instead of the guess.

Generate a typed client from the spec

For a stronger foundation, have the assistant generate a typed client from the spec so the whole app has compile-time safety against the real API:

# Types only
npx openapi-typescript https://<yourHost>/dwapi/api.json -o ./src/dwapi-types.ts

# Or a full client
npx @openapitools/openapi-generator-cli generate \
  -i https://<yourHost>/dwapi/api.json -g typescript-fetch -o ./src/dwapi-client

Commit the generation command, not just the output, so it's repeatable — and re-run it after every platform upgrade, then ask the assistant to diff the spec and flag breaking changes.

A workflow that holds up

  • Describe the goal, name the sources. "Using the Dynamicweb docs and our spec…" makes the assistant ground itself before it writes a line.
  • Lean on the docs MCP for the "why". Concepts, intended usage, and how features fit together come from the documentation.
  • Pin to the live spec for the "what". Exact paths, parameters, and the obsolete-vs-current distinction come from your solution's /dwapi/api.json.
  • Verify with real calls. The running solution's response is the final word; feed it back to the assistant to correct the generated code.
  • Re-ground after upgrades. Re-pull the spec, re-generate the client, and ask the assistant what changed.

Notes and caveats

  • Endpoint paths, parameters, and schemas in this article are illustrative. Your solution's /dwapi/api.json is authoritative — keep the assistant grounded in it.
  • Anonymous vs. authenticated access varies per endpoint; the spec marks security requirements per operation.
  • The Documentation MCP reflects published docs, which can lag the live platform. When the docs and the live spec disagree, trust the spec — and tell the assistant to.
  • The assistant decides when to search. If it answers a Dynamicweb question without checking, prompt it explicitly to use the docs and your spec.
To top