Channel partners on a ghost kitchen: affiliate pickup without a second menu
Delivery apps want you to believe the only way to sell food online is through their tile on a phone screen. That is true for a lot of volume — and at Empanada Empire we still live on DoorDash and friends. But it is not the whole story.
Some of our best orders come from people who already know someone: a church group, an office manager, a neighbor who runs a small resale table. They are not browsing a marketplace. They are placing a pickup window order under an affiliate's link, at a price that reflects wholesale cost plus a fair retail markup, and picking up empanadas on a schedule that repeats every Tuesday if that is what the group wants.
We built that lane on the same stack that powers the public website, the Square terminal, and the delivery tablets. One menu database. No forked spreadsheet. No "partner menu v3_final.xlsx." This post is how channel partners work on a live ghost kitchen — and why we treated them as a field experiment, not a side feature.
Why affiliates are different from marketplaces
A marketplace optimizes discovery: photos, promos, star counts, sponsored placement. An affiliate optimizes trust that already exists. The affiliate is not DoorDash; they are a person or organization whose audience will order because they said so.
That changes the product shape:
- The customer needs a branded storefront that feels like Empanada Empire but prices and attribution belong to the affiliate.
- The affiliate needs a portal to open and close pickup windows, see incoming orders, and not call us for every schedule change.
- The kitchen needs those orders on the same order board as web and Square — same prep flow, same inventory truth.
If we had bolted partners onto a separate Google Form and a manual email to the line, we would have learned nothing useful about software. We would have learned about pain. We wanted the pain in the product so we could fix it.
One menu, three prices (wholesale, retail, public)
The one-menu-database story is the foundation. Website, Square, and delivery channels read the same menu_items rows. Channel partners add a pricing layer, not a second catalog.
For each SKU an affiliate sells, Kitchen POS stores:
- Unit cost — what the affiliate pays us (wholesale basis).
- Customer price — what their buyers see on the partner storefront.
The public menu on empanadaempire.us keeps its own prices. A partner link at /p/{slug}/ loads the affiliate-priced menu from the partner API. Change the base menu once; every channel sees the update. Change an affiliate's margin on one empanada; only that partner's buyers see it.
Operator APIs and SMCP tools let us set cost and customer price per partner per SKU without opening SQLite on production. That matters when you are iterating with real affiliates on real margins — not when you are drawing architecture diagrams.
The partner portal: windows, not always-on checkout
Affiliates do not get an always-open "buy now" button unless we want them to. Pickup programs run on order windows — a delivery date, a cutoff, a cap if we need one.
In the partner portal (same Sanctum-styled shell as the operator admin), an affiliate:
- Opens a window — pickup date, optional message, customer URL generated.
- Shares the URL — short canonical path on empanadaempire.us (
/p/{slug}/or legacy?partner=slug). - Watches orders arrive — scoped to their account, same lifecycle as other Kitchen POS orders.
- Closes the window when the cutoff hits or the cap fills.
June added recurring schedules: opt-in auto-reopen for weekly rhythms (Tuesday pickup every week, for example). That is the difference between "we remember to open the portal" and "the portal remembers for us." Ghost kitchens already juggle enough timers; recurring windows are an operator kindness.
The schedule row stores weekday, optional delivery window text, cutoff rules, and whether auto-reopen is enabled. When the cron path fires, Kitchen POS opens the next window from the template and generates a fresh customer URL if needed. Affiliates still close manually when they want to pause — we are not removing judgment, we are removing forgetfulness.
Failure modes we designed for
Pickup programs fail in predictable ways. We tried to name them in the product instead of in Slack.
No window open. The customer storefront shows an empty state — not a 500, not a blank cart. "No order window scheduled" is a valid state; it trains affiliates to open before they blast a group text.
Kitchen closed vs customer in-area. The main site address checker already knew the difference between "you are outside our radius" and "we are closed right now but you are in-area." Partner pages inherit that honesty so a church member does not think the program is broken when the ghost kitchen is simply between shifts.
Wrong API key kind. Partner routes reject operator machine keys. Operator routes reject partner bridge keys. That sounds pedantic until someone pastes a god-mode key into an affiliate's bookmarklet.
Menu drift. Because partner prices hang off menu_item_id, deleting a SKU without updating partner pricing surfaces as a provisioning task, not a silent wrong price on Sunday morning.
The customer storefront: picture menu, not a PDF
Early partner tests used workable but ugly flows. June's empanadaempire.us work redesigned the partner order page as a picture menu with a cart drawer — the same visual language as the main site, overlay thumbnails, category order preserved, empty states when no window is open.
When no window is scheduled, the page says so clearly instead of failing mysteriously. When the kitchen API hiccups, the error is human-readable. Small things; they are the difference between "this brand is professional" and "my friend sent me a broken link."
Affiliate-priced items load from Kitchen POS get-menu with the partner filter applied. The site does not cache a stale JSON file per partner; it asks the POS at request time so price changes land immediately.
On mobile we tightened partner tile typography — three-line name clamp, smaller price chip, cart glyph — because affiliate links travel in group texts that open on phones. Desktop keeps the same picture-menu grid as the main research layout. The experiment is not "a special mobile site"; it is one brand with two entry paths.
Promo kit: biz cards and four-up flyers
Affiliates market offline too — bulletin boards, break rooms, church lobbies. We generate per-partner promo PDFs: a business-card layout and a four-up flyer, copy tuned with per-partner variants.
This is not glamorous engineering. It is the kind of glue that makes a program real. An affiliate who has to build their own Canva template will not run the program twice. An affiliate who downloads a PDF with their slug and QR-ready link might.
Operator provisioning without heroics
Behind the portal is operator tooling: create partner, pause or revoke, reset password, assign home location, set daily caps. Rico and agents can provision via SMCP (create-partner, list-partners, pricing setters) when Mark wants hands-off onboarding.
Each partner user gets a partner bridge API key (kppb_…) scoped to their account — not an operator machine key. Partner-facing routes return 403 if you bring the wrong key kind. That is intentional: affiliates should never hold keys that can rewrite the global menu.
What lands on the kitchen line
Partner orders are not a separate spreadsheet export. They hit the same order board as website pickup and mirrored Square sales. Prep staff see one queue. Status pills and filters behave the same.
That integration is the point of the experiment. If partners were a side channel that emailed a CSV, we would not stress-test routing, naming, or fulfillment hours. We would defer those bugs until "later." Later is how ghost kitchens die.
Ask Porter (dev): help scoped to one affiliate
Phase 1b added Ask Porter on the dev Kitchen POS host — a Vernal co-pilot that sees only the logged-in partner's menu, windows, and recent orders. Porter does not get operator kitchen_pos__* tools; Porter is not an admin backdoor.
We mention it because it is the natural end state of partner self-service: open a window, share a link, ask the in-app assistant how to read an order list. Production Porter is not live as of this writing; dev uses the same pull-bridge pattern as Ask Q on Tasks. When it ships, it belongs in this chapter; until then, it is forward-looking glue.
Lessons we are selling when we say "field experiment"
- Do not fork the menu — fork pricing and attribution, not catalog rows.
- Windows beat always-on for pickup programs — caps, cutoffs, and recurring schedules are product features, not calendar reminders.
- The public site and the POS must agree — partner URLs are thin clients over Kitchen POS APIs.
- Affiliate flows belong on the line — if prep cannot see it, it is not real.
- Offline marketing assets matter — PDFs are part of the API surface for humans.
Empanada Empire is still a ghost kitchen in Richardson, TX — delivery-first, no dining room. Channel partners are how we test whether a data science company can run relationship-driven pickup with the same discipline we bring to marketplace SEO and paid social. The empanadas are real. The affiliate margins are real. The software is what we measure next.
Questions about the partner program or Kitchen POS: contact us.