01 / Give it something to know.
Create a Copilot Studio agent and select classic orchestration. Add the public menu page as a website knowledge source:
https://pizzas.anthonymsft.com/
Keep the knowledge scope to the menu page, including its ingredients, allergens and FAQs. Do not use this tutorial, the API schema or the incoming-orders board as restaurant knowledge. If your website knowledge configuration includes subpages automatically, use a restricted URL scope or supply the menu page as a curated document instead.
The menu is readable without JavaScript. Public website grounding may depend on Bing indexing and tenant configuration; new domains may take time to become available. Check that the page is accessible and indexed, or upload a saved copy of the menu page while waiting.
Use a generative answers node for menu questions, with this website knowledge selected. Instructions should say: use GBP prices; all pizzas are 12-inch; report declared allergens and the cross-contact warning; never guarantee allergy safety; explain that all orders are fictional.
Try these questions
- How much is a Pepperoni pizza?
- What ingredients are in Garden Party?
- Which pizza is vegan? Is it also gluten-free?
- Does Smoky BBQ contain mustard or soya?
- Can you guarantee a pizza is safe for a milk allergy?
02 / Take the order, deliberately.
Create an Order pizza topic with trigger phrases such as "I'd like to order a pizza" and "Can I get two Margheritas?". Use question nodes to collect:
- A fictional display name. Explain that it will appear on a public board for 24 hours.
- A pizza from a fixed multiple-choice list mapped to the IDs below.
- A whole-number quantity between 1 and 10. For a first exercise, order one pizza type; for an extension, collect more selections, up to 20 pizzas total.
Show the selected name, pizzas and expected total. Ask for explicit confirmation before making the HTTP call. If cancelled, do not call the API. The server calculates the authoritative price; never send a price supplied by the user or model.
| Pizza | pizzaId | Price |
|---|---|---|
| Margherita | margherita | £8.50 |
| Pepperoni | pepperoni | £10.50 |
| Garden Party | garden-party | £10.00 |
| Smoky BBQ | smoky-bbq | £11.50 |
| Plant Powered | plant-powered | £10.50 |
| Hot Honey | hot-honey | £12.00 |
03 / Send it to the kitchen.
Agent API (OpenAPI 3.0): Download the OpenAPI 3.0 definition or import https://pizzas.anthonymsft.com/openapi3.json where URL import is supported. It defines POST /createOrder using the same JSON body shown below, and GET /getOrder?orderId=... to retrieve one confirmation using the full returned id. No authentication is required for this fictional demo. Only retrieve a reference provided by the participant or returned by their order action; do not guess references. An invalid reference returns 400; a missing or expired order returns 404.
Use OpenAPI 3.0 with an agent integration that supports that version. For a Power Platform custom connector requiring Swagger 2.0, use the existing Swagger download below. Existing /orders actions remain compatible; the live board still uses GET /orders to list orders.
After confirmation, use an HTTP request node (where available) or call an agent flow/custom connector from the topic. Configure POST with Content-Type: application/json. This public demo API needs no API key.
POST https://pizzas.anthonymsft.com/orders
Content-Type: application/json
{
"customerName": "Alex Demo",
"items": [
{ "pizzaId": "margherita", "quantity": 2 },
{ "pizzaId": "pepperoni", "quantity": 1 }
]
}
For one pizza selection, set the request body to a Power Fx record, substituting your own topic variable names. Use typed records rather than concatenating strings into JSON:
{
customerName: Topic.CustomerName,
items: Table({
pizzaId: Topic.PizzaId,
quantity: Topic.Quantity
})
}
The equivalent same-origin linked API route is /api/orders. If an environment's connector tooling cannot follow the site's /orders rewrite, use https://pizzas.anthonymsft.com/api/orders instead.
Download the Swagger 2.0 API definition (also available at /openapi.json). In Power Platform, create a custom connector using Import an OpenAPI file, select the downloaded JSON file and choose No authentication. Create a connection, then explicitly call its PlaceOrder action from the classic-orchestration order topic. To import from a URL where supported, use https://pizzas.anthonymsft.com/swagger.json. Your organisation's DLP policies and licensing still apply.
Successful response: HTTP 201
{
"id": "6d0b96a5-6782-485e-a994-75e84f185082",
"customerName": "Alex Demo",
"items": [
{
"pizzaId": "margherita",
"name": "Margherita",
"quantity": 2,
"unitPricePence": 850,
"lineTotalPence": 1700
},
{
"pizzaId": "pepperoni",
"name": "Pepperoni",
"quantity": 1,
"unitPricePence": 1050,
"lineTotalPence": 1050
}
],
"totalPence": 2750,
"currency": "GBP",
"status": "accepted",
"statusLabel": "Order accepted",
"statusUpdatedAt": "2026-10-23T13:30:00.000Z",
"nextStatusAt": "2026-10-23T13:30:30.000Z",
"createdAt": "2026-10-23T13:30:00.000Z",
"expiresAt": "2026-10-24T13:30:00.000Z"
}
Generate the response schema from this example. Only say the order was accepted after receiving HTTP 201 and status: "accepted". Display the reference and totalPence / 100 formatted as GBP. No payment is taken.
When importing the API definition, review Select tool parameters: inputs and outputs include descriptions and examples to help map values. Map a created order's full id to the lookup input orderId, never to a pizza's pizzaId. Price outputs ending in Pence must be divided by 100 for display as GBP. Examples are illustrative, not defaults. Download and re-import the definition to update an existing tool's descriptions; the hosted file does not automatically refresh an already imported tool.
Handle the unhappy paths, too
- 400: invalid JSON, name, pizza ID or quantity. Explain the returned
errorand correct the input. - 413 / 415: body over 8 KB or wrong content type. Fix the action configuration.
- 429: storage throttling. Respect the
Retry-Afterheader. - 503 or a timeout: do not claim success. Ask the participant to check the board before submitting again. A timed-out request may already have been saved.
Every accepted POST creates a new order. There is no idempotency key and no automatic retry: repeated submissions create separate tickets.
04 / Make the moment visible.
Open the incoming-orders board on the presenter's screen. It polls GET /orders every five seconds while visible. Participants can use separate browsers or their agent; accepted orders are shared through the backend, not browser storage.
Progress is simulated from the saved creation time, so refreshing the page does not restart it: order accepted at 0 seconds, order with kitchen at 30 seconds, order ready / waiting for delivery at 60 seconds, order being delivered at 90 seconds and order complete at 120 seconds. The board shows each change on its next refresh, within five seconds while connected.
Use Cancel demo order on an active ticket to demonstrate order cancelled. Cancellation is an alternative terminal state, not a step after completion. Cancelled orders never resume; complete orders cannot be cancelled. All board users can cancel because this is a public, unauthenticated demo. No real food is prepared or delivered.
The agent's GET /getOrder?orderId=<full-uuid> returns the same live status, statusLabel, statusUpdatedAt and nextStatusAt as the board. nextStatusAt is null for complete and cancelled orders. Tell the participant this is simulated progress, not a real delivery.
The GET response is { "orders": [...], "retentionHours": 24, "limit": 100 }. Tickets are newest first. Only the latest 100 unexpired orders appear. Names and pizza selections are public; never collect delivery addresses, email addresses, phone numbers, payment data or allergy notes.
Completed and cancelled tickets move into Archived orders, collapsed by default. Expand it to view them or use Clear Archive. After confirmation, clearing permanently deletes archived orders for everyone, including agent lookups. Other boards update on their next refresh. Active orders are kept; orders completing after clearing starts still enter the archive. This public demo allows anyone to clear the archive.
A quick end-to-end check
- Ask for a Pepperoni price. Expected: £10.50.
- Ask whether Plant Powered is gluten-free. Expected: no, it contains wheat (gluten), with a cross-contact warning.
- Order two Margheritas as "Alex Demo", then confirm. Expected: accepted, £17.00.
- Find "Alex Demo" and "2 × Margherita" on the board.
- Try an invalid pizza ID. Expected: HTTP 400 and no new ticket.