The problem
A restaurant runs out of basil at 4pm. Service starts at 7. The online catalogs for their three approved suppliers are stale or empty — smaller suppliers do not publish live stock, they answer the phone.
So the buyer calls all three. They ask slightly different questions each time, scribble inconsistent notes, and end up unable to answer the only question that matters: which one is actually cheaper, and which one can actually make it in time? One supplier quoted per kilo, one per case. One mentioned a delivery fee halfway through. One said "about twelve dollars, depends on the market."
That is not a data problem. That is a same-questions problem.
What ProcurePulse does
It calls the approved suppliers through CALL-E, asks every one of them the identical set of outcome-focused questions, and turns each answer into a schema-validated quote you can put side by side.
- Same questions, every supplier. Availability, exact substitute name, quantity, unit price, fees, tax treatment, pickup or delivery, ready time, quote expiry, and who gave the quote.
- Every field traces to one real call. The app re-fetches
GET /v1/calls/{call_id}from CALL-E before it persists anything, then validates against a strictrecipient_result_schema. A result that fails validation is visible and excluded — never silently dropped, never patched up. - It refuses to guess. A comparable total is computed only when quantity, unit, currency, unit price and fees are all unambiguous. Anything vaguer shows Needs review with the specific reason, and stays out of the ranking.
- Completeness ≠ certainty. How much of the schema the supplier filled in is a different signal from how confident CALL-E was about hearing it correctly. Both are shown, separately, because they fail in different ways.
- Deterministic ranking. Lowest complete total and earliest ready time, with supplier name as a stable tiebreak. The same data always ranks the same way.
- Humans make the decisions that cost money. Approving a vendor is an internal record. Asking a supplier to hold stock is a second, separate approval and a second call — one that says out loud that it is not a purchase.
The safety contract
This is a phone agent that calls small businesses. That deserves more care than a feature list:
- Suppliers must have an existing relationship or explicit authorization. It does not cold-call.
- The preflight modal shows the exact numbers to be dialled, the buyer being represented, and the goal — before anything rings.
- The agent identifies itself and the business in the first breath of the call.
- It never asks for payment details, places an order, accepts terms, or treats a hold as a purchase.
- Missing credentials block dispatch. With no
CALLE_API_KEYon the server, the launch button is dead and the header says why. There is no demo mode, no fake transcript, and no automatic success fallback anywhere in the code.
The demo video is the real app, clicked through end to end. For the recording, a
local stand-in answered CALL-E's API in its documented shapes and the calls were
voiced with neural TTS; the video's end card says so. The stand-in lives only in
demo/video/ — the app has no mock path, and the capture refuses to run unless the
app reports it is pointed at the stand-in rather than the real API.
How it is built
A Next-compatible React/TypeScript surface on Vite and Cloudflare, Tailwind,
server-side API routes, and a durable relational schema on D1. CALLE_API_KEY is
read in exactly one place — the server adapter in lib/calle.ts — and never
crosses into the browser. The schema is portable relational SQL; a Postgres
deployment swaps the small repository adapter and keeps the same constraints.
Buyer → preflight → POST /v1/calls (recipients[{phones, locale, region}],
described recipient_result_schema, correlation
metadata, HTTPS webhook_url when public,
Idempotency-Key = requestId:vendorId)
CALL-E → real phone call → terminal webhook (CALL-E-Event-Id), or the app polls
→ event reserved + de-duplicated
→ GET /v1/calls/{call_id} ← authoritative re-fetch
→ strict validation → quote persisted → comparable board
Entities: purchase_requests, vendors, request_vendors, call_tasks,
call_events, vendor_quotes, decisions, audit_entries. Unique constraints
on the idempotency key, the provider call ID, and the event ID mean a double-click
and a duplicated webhook delivery both resolve to exactly one call and one quote.
What the real run proved
(Fill in from your run — the numbers are in the export script's output.)
- Real CALL-E calls placed: [N]
- Provider call IDs persisted: [N]
- Terminal callbacks processed: [N]
- Quotes that passed strict validation: [N]
- Quotes held out of the ranking as Needs review: [N]
- Orders placed: 0. Payments authorized: 0.
What was hard
Making absence visible. The tempting version of this app fills in a plausible number when a supplier is vague, because a clean three-way comparison demos better than one card that says Needs review. That version is also useless the first time a buyer acts on a number nobody actually said. Most of the work here went into the boundary: which fields justify arithmetic, and which ones only justify a warning.
The second hard part was resisting a mock. A fake call path would have made every iteration faster. It also would have made the whole submission meaningless, so there isn't one — the app blocks itself instead.
Next
Provider-native webhook signature verification once the header contract is confirmed (the endpoint currently authenticates terminal data by re-fetching with the server credential), a retry queue for callback delivery at higher volume, and a Hyperdrive-backed Postgres adapter for non-Cloudflare deployments.
Feedback for the CALL-E team
In CALLE_FEEDBACK.md: what worked, the two contract details we had to
centralize behind an adapter, and three reproducible edge cases from the real
calls — including the one where extra_fees: "" correctly produces Needs
review rather than a zero.
Built With
- call-e
Log in or sign up for Devpost to join the conversation.